summary refs log tree commit diff
path: root/crypto/src
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/src')
-rw-r--r--crypto/src/AssemblyInfo.cs26
-rw-r--r--crypto/src/BouncyCastle.Crypto.csproj66
-rw-r--r--crypto/src/asn1/Asn1EncodableVector.cs8
-rw-r--r--crypto/src/asn1/Asn1GeneralizedTime.cs438
-rw-r--r--crypto/src/asn1/Asn1InputStream.cs4
-rw-r--r--crypto/src/asn1/Asn1OutputStream.cs22
-rw-r--r--crypto/src/asn1/Asn1RelativeOid.cs18
-rw-r--r--crypto/src/asn1/Asn1Sequence.cs30
-rw-r--r--crypto/src/asn1/Asn1Set.cs17
-rw-r--r--crypto/src/asn1/Asn1UniversalTypes.cs4
-rw-r--r--crypto/src/asn1/Asn1UtcTime.cs264
-rw-r--r--crypto/src/asn1/BEROctetStringGenerator.cs68
-rw-r--r--crypto/src/asn1/ConstructedBitStream.cs52
-rw-r--r--crypto/src/asn1/ConstructedOctetStream.cs52
-rw-r--r--crypto/src/asn1/DefiniteLengthInputStream.cs21
-rw-r--r--crypto/src/asn1/DerGeneralizedTime.cs309
-rw-r--r--crypto/src/asn1/DerOctetString.cs17
-rw-r--r--crypto/src/asn1/DerUTCTime.cs259
-rw-r--r--crypto/src/asn1/IndefiniteLengthInputStream.cs23
-rw-r--r--crypto/src/asn1/bc/BCObjectIdentifiers.cs64
-rw-r--r--crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs51
-rw-r--r--crypto/src/asn1/cmp/CertAnnContent.cs72
-rw-r--r--crypto/src/asn1/cmp/CertConfirmContent.cs35
-rw-r--r--crypto/src/asn1/cmp/CertOrEncCert.cs86
-rw-r--r--crypto/src/asn1/cmp/CertRepMessage.cs59
-rw-r--r--crypto/src/asn1/cmp/CertReqTemplateContent.cs67
-rw-r--r--crypto/src/asn1/cmp/CertResponse.cs90
-rw-r--r--crypto/src/asn1/cmp/CertStatus.cs122
-rw-r--r--crypto/src/asn1/cmp/CertifiedKeyPair.cs90
-rw-r--r--crypto/src/asn1/cmp/Challenge.cs131
-rw-r--r--crypto/src/asn1/cmp/CmpCertificate.cs93
-rw-r--r--crypto/src/asn1/cmp/CmpObjectIdentifiers.cs350
-rw-r--r--crypto/src/asn1/cmp/CrlAnnContent.cs43
-rw-r--r--crypto/src/asn1/cmp/CrlSource.cs72
-rw-r--r--crypto/src/asn1/cmp/CrlStatus.cs61
-rw-r--r--crypto/src/asn1/cmp/DhbmParameter.cs56
-rw-r--r--crypto/src/asn1/cmp/ErrorMsgContent.cs78
-rw-r--r--crypto/src/asn1/cmp/GenMsgContent.cs51
-rw-r--r--crypto/src/asn1/cmp/GenRepContent.cs46
-rw-r--r--crypto/src/asn1/cmp/InfoTypeAndValue.cs52
-rw-r--r--crypto/src/asn1/cmp/KeyRecRepContent.cs92
-rw-r--r--crypto/src/asn1/cmp/NestedMessageContent.cs35
-rw-r--r--crypto/src/asn1/cmp/OobCert.cs68
-rw-r--r--crypto/src/asn1/cmp/OobCertHash.cs65
-rw-r--r--crypto/src/asn1/cmp/PKIBody.cs192
-rw-r--r--crypto/src/asn1/cmp/PKIConfirmContent.cs34
-rw-r--r--crypto/src/asn1/cmp/PKIFailureInfo.cs8
-rw-r--r--crypto/src/asn1/cmp/PKIFreeText.cs73
-rw-r--r--crypto/src/asn1/cmp/PKIHeader.cs62
-rw-r--r--crypto/src/asn1/cmp/PKIHeaderBuilder.cs4
-rw-r--r--crypto/src/asn1/cmp/PKIMessages.cs2
-rw-r--r--crypto/src/asn1/cmp/PbmParameter.cs95
-rw-r--r--crypto/src/asn1/cmp/PollRepContent.cs111
-rw-r--r--crypto/src/asn1/cmp/PollReqContent.cs117
-rw-r--r--crypto/src/asn1/cmp/PopoDecKeyChallContent.cs35
-rw-r--r--crypto/src/asn1/cmp/PopoDecKeyRespContent.cs39
-rw-r--r--crypto/src/asn1/cmp/ProtectedPart.cs50
-rw-r--r--crypto/src/asn1/cmp/RevAnnContent.cs92
-rw-r--r--crypto/src/asn1/cmp/RevDetails.cs71
-rw-r--r--crypto/src/asn1/cmp/RevRepContent.cs84
-rw-r--r--crypto/src/asn1/cmp/RevRepContentBuilder.cs26
-rw-r--r--crypto/src/asn1/cmp/RevReqContent.cs44
-rw-r--r--crypto/src/asn1/cmp/RootCaKeyUpdateContent.cs91
-rw-r--r--crypto/src/asn1/cms/CMSObjectIdentifiers.cs3
-rw-r--r--crypto/src/asn1/cms/KEKIdentifier.cs12
-rw-r--r--crypto/src/asn1/cms/RecipientKeyIdentifier.cs14
-rw-r--r--crypto/src/asn1/cms/Time.cs42
-rw-r--r--crypto/src/asn1/crmf/EncryptedKey.cs58
-rw-r--r--crypto/src/asn1/crmf/EncryptedValue.cs140
-rw-r--r--crypto/src/asn1/crmf/PKIPublicationInfo.cs92
-rw-r--r--crypto/src/asn1/cryptlib/CryptlibObjectIdentifiers.cs11
-rw-r--r--crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs8
-rw-r--r--crypto/src/asn1/esf/CrlIdentifier.cs47
-rw-r--r--crypto/src/asn1/esf/OcspIdentifier.cs25
-rw-r--r--crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs6
-rw-r--r--crypto/src/asn1/misc/MiscObjectIdentifiers.cs32
-rw-r--r--crypto/src/asn1/ocsp/CrlID.cs6
-rw-r--r--crypto/src/asn1/ocsp/ResponseData.cs10
-rw-r--r--crypto/src/asn1/ocsp/RevokedInfo.cs10
-rw-r--r--crypto/src/asn1/ocsp/SingleResponse.cs18
-rw-r--r--crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs11
-rw-r--r--crypto/src/asn1/sec/ECPrivateKeyStructure.cs50
-rw-r--r--crypto/src/asn1/tsp/TSTInfo.cs8
-rw-r--r--crypto/src/asn1/util/Asn1Dump.cs4
-rw-r--r--crypto/src/asn1/x509/AttCertValidityPeriod.cs16
-rw-r--r--crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs10
-rw-r--r--crypto/src/asn1/x509/TBSCertList.cs4
-rw-r--r--crypto/src/asn1/x509/Time.cs39
-rw-r--r--crypto/src/asn1/x509/V1TBSCertificateGenerator.cs4
-rw-r--r--crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs10
-rw-r--r--crypto/src/asn1/x509/V2TBSCertListGenerator.cs8
-rw-r--r--crypto/src/asn1/x509/V3TBSCertificateGenerator.cs4
-rw-r--r--crypto/src/asn1/x509/X509DefaultEntryConverter.cs2
-rw-r--r--crypto/src/asn1/x509/sigi/PersonalData.cs8
-rw-r--r--crypto/src/bcpg/ArmoredInputStream.cs29
-rw-r--r--crypto/src/bcpg/ArmoredOutputStream.cs38
-rw-r--r--crypto/src/bcpg/BcpgInputStream.cs73
-rw-r--r--crypto/src/bcpg/BcpgOutputStream.cs122
-rw-r--r--crypto/src/bcpg/ECDHPublicBCPGKey.cs33
-rw-r--r--crypto/src/bcpg/ECPublicBCPGKey.cs2
-rw-r--r--crypto/src/bcpg/ECSecretBCPGKey.cs11
-rw-r--r--crypto/src/bcpg/EdDsaPublicBcpgKey.cs25
-rw-r--r--crypto/src/bcpg/EdSecretBcpgKey.cs43
-rw-r--r--crypto/src/bcpg/MPInteger.cs65
-rw-r--r--crypto/src/bcpg/PublicKeyPacket.cs47
-rw-r--r--crypto/src/bcpg/SignaturePacket.cs110
-rw-r--r--crypto/src/bcpg/sig/Exportable.cs12
-rw-r--r--crypto/src/bcpg/sig/Features.cs18
-rw-r--r--crypto/src/bcpg/sig/IssuerKeyId.cs37
-rw-r--r--crypto/src/bcpg/sig/KeyExpirationTime.cs25
-rw-r--r--crypto/src/bcpg/sig/NotationData.cs2
-rw-r--r--crypto/src/bcpg/sig/PrimaryUserId.cs15
-rw-r--r--crypto/src/bcpg/sig/Revocable.cs15
-rw-r--r--crypto/src/bcpg/sig/RevocationReason.cs2
-rw-r--r--crypto/src/bcpg/sig/SignatureCreationTime.cs27
-rw-r--r--crypto/src/bcpg/sig/SignatureExpirationTime.cs32
-rw-r--r--crypto/src/bcpg/sig/SignerUserId.cs4
-rw-r--r--crypto/src/cmp/CertificateConfirmationContent.cs16
-rw-r--r--crypto/src/cmp/CertificateConfirmationContentBuilder.cs37
-rw-r--r--crypto/src/cmp/CertificateStatus.cs12
-rw-r--r--crypto/src/cmp/GeneralPkiMessage.cs28
-rw-r--r--crypto/src/cmp/ProtectedPkiMessage.cs97
-rw-r--r--crypto/src/cmp/ProtectedPkiMessageBuilder.cs96
-rw-r--r--crypto/src/cmp/RevocationDetails.cs30
-rw-r--r--crypto/src/cmp/RevocationDetailsBuilder.cs18
-rw-r--r--crypto/src/cms/CMSAuthenticatedDataGenerator.cs36
-rw-r--r--crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs49
-rw-r--r--crypto/src/cms/CMSAuthenticatedGenerator.cs15
-rw-r--r--crypto/src/cms/CMSCompressedData.cs8
-rw-r--r--crypto/src/cms/CMSCompressedDataGenerator.cs17
-rw-r--r--crypto/src/cms/CMSCompressedDataParser.cs5
-rw-r--r--crypto/src/cms/CMSCompressedDataStreamGenerator.cs64
-rw-r--r--crypto/src/cms/CMSEnvelopedDataGenerator.cs17
-rw-r--r--crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs67
-rw-r--r--crypto/src/cms/CMSEnvelopedGenerator.cs22
-rw-r--r--crypto/src/cms/CMSEnvelopedHelper.cs16
-rw-r--r--crypto/src/cms/CMSSignedDataGenerator.cs23
-rw-r--r--crypto/src/cms/CMSSignedDataStreamGenerator.cs34
-rw-r--r--crypto/src/cms/CMSSignedGenerator.cs17
-rw-r--r--crypto/src/cms/CMSSignedHelper.cs24
-rw-r--r--crypto/src/cms/CMSTypedStream.cs19
-rw-r--r--crypto/src/cms/CMSUtils.cs22
-rw-r--r--crypto/src/cms/KeyAgreeRecipientInformation.cs4
-rw-r--r--crypto/src/cms/RecipientInformation.cs6
-rw-r--r--crypto/src/cms/SignerInformation.cs10
-rw-r--r--crypto/src/crmf/CertificateRequestMessage.cs4
-rw-r--r--crypto/src/crmf/CertificateRequestMessageBuilder.cs9
-rw-r--r--crypto/src/crmf/PKMacBuilder.cs20
-rw-r--r--crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs8
-rw-r--r--crypto/src/crypto/AesUtilities.cs10
-rw-r--r--crypto/src/crypto/BufferedAeadBlockCipher.cs45
-rw-r--r--crypto/src/crypto/BufferedAeadCipher.cs33
-rw-r--r--crypto/src/crypto/BufferedAsymmetricBlockCipher.cs57
-rw-r--r--crypto/src/crypto/BufferedBlockCipher.cs204
-rw-r--r--crypto/src/crypto/BufferedCipherBase.cs21
-rw-r--r--crypto/src/crypto/BufferedIesCipher.cs46
-rw-r--r--crypto/src/crypto/BufferedStreamCipher.cs120
-rw-r--r--crypto/src/crypto/CipherKeyGenerator.cs16
-rw-r--r--crypto/src/crypto/CryptoServicesRegistrar.cs17
-rw-r--r--crypto/src/crypto/IBlockCipher.cs16
-rw-r--r--crypto/src/crypto/IBlockResult.cs12
-rw-r--r--crypto/src/crypto/IBufferedCipher.cs15
-rw-r--r--crypto/src/crypto/IDSA.cs39
-rw-r--r--crypto/src/crypto/IDerivationFunction.cs11
-rw-r--r--crypto/src/crypto/IDigest.cs84
-rw-r--r--crypto/src/crypto/IDigestFactory.cs2
-rw-r--r--crypto/src/crypto/IDsaExt.cs17
-rw-r--r--crypto/src/crypto/IEncapsulatedSecretExtractor.cs5
-rw-r--r--crypto/src/crypto/IEntropySource.cs4
-rw-r--r--crypto/src/crypto/IMac.cs84
-rw-r--r--crypto/src/crypto/IMacDerivationFunction.cs7
-rw-r--r--crypto/src/crypto/IMacFactory.cs2
-rw-r--r--crypto/src/crypto/IRawAgreement.cs4
-rw-r--r--crypto/src/crypto/ISignatureFactory.cs8
-rw-r--r--crypto/src/crypto/ISigner.cs61
-rw-r--r--crypto/src/crypto/IStreamCalculator.cs7
-rw-r--r--crypto/src/crypto/IStreamCipher.cs45
-rw-r--r--crypto/src/crypto/IVerifierFactory.cs6
-rw-r--r--crypto/src/crypto/IWrapper.cs4
-rw-r--r--crypto/src/crypto/IXof.cs31
-rw-r--r--crypto/src/crypto/Security.cs76
-rw-r--r--crypto/src/crypto/SimpleBlockResult.cs9
-rw-r--r--crypto/src/crypto/StreamBlockCipher.cs69
-rw-r--r--crypto/src/crypto/agreement/DHAgreement.cs17
-rw-r--r--crypto/src/crypto/agreement/X25519Agreement.cs17
-rw-r--r--crypto/src/crypto/agreement/X448Agreement.cs17
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakeParticipant.cs7
-rw-r--r--crypto/src/crypto/agreement/kdf/ConcatenationKdfGenerator.cs130
-rw-r--r--crypto/src/crypto/agreement/kdf/DHKekGenerator.cs120
-rw-r--r--crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs36
-rw-r--r--crypto/src/crypto/agreement/srp/SRP6Utilities.cs147
-rw-r--r--crypto/src/crypto/digests/Blake2bDigest.cs181
-rw-r--r--crypto/src/crypto/digests/Blake2sDigest.cs339
-rw-r--r--crypto/src/crypto/digests/Blake2xsDigest.cs368
-rw-r--r--crypto/src/crypto/digests/Blake3Digest.cs1047
-rw-r--r--crypto/src/crypto/digests/CSHAKEDigest.cs23
-rw-r--r--crypto/src/crypto/digests/DSTU7564Digest.cs131
-rw-r--r--crypto/src/crypto/digests/GOST3411Digest.cs54
-rw-r--r--crypto/src/crypto/digests/GOST3411_2012Digest.cs92
-rw-r--r--crypto/src/crypto/digests/GOST3411_2012_256Digest.cs12
-rw-r--r--crypto/src/crypto/digests/GeneralDigest.cs50
-rw-r--r--crypto/src/crypto/digests/Haraka256Digest.cs288
-rw-r--r--crypto/src/crypto/digests/Haraka256_X86.cs144
-rw-r--r--crypto/src/crypto/digests/Haraka512Digest.cs376
-rw-r--r--crypto/src/crypto/digests/Haraka512_X86.cs240
-rw-r--r--crypto/src/crypto/digests/HarakaBase.cs196
-rw-r--r--crypto/src/crypto/digests/KeccakDigest.cs155
-rw-r--r--crypto/src/crypto/digests/LongDigest.cs289
-rw-r--r--crypto/src/crypto/digests/MD2Digest.cs59
-rw-r--r--crypto/src/crypto/digests/MD4Digest.cs161
-rw-r--r--crypto/src/crypto/digests/MD5Digest.cs32
-rw-r--r--crypto/src/crypto/digests/NonMemoableDigest.cs14
-rw-r--r--crypto/src/crypto/digests/NullDigest.cs35
-rw-r--r--crypto/src/crypto/digests/ParallelHash.cs126
-rw-r--r--crypto/src/crypto/digests/RipeMD128Digest.cs28
-rw-r--r--crypto/src/crypto/digests/RipeMD160Digest.cs29
-rw-r--r--crypto/src/crypto/digests/RipeMD256Digest.cs32
-rw-r--r--crypto/src/crypto/digests/RipeMD320Digest.cs34
-rw-r--r--crypto/src/crypto/digests/SHA3Digest.cs9
-rw-r--r--crypto/src/crypto/digests/SM3Digest.cs34
-rw-r--r--crypto/src/crypto/digests/Sha1Digest.cs34
-rw-r--r--crypto/src/crypto/digests/Sha224Digest.cs44
-rw-r--r--crypto/src/crypto/digests/Sha256Digest.cs59
-rw-r--r--crypto/src/crypto/digests/Sha384Digest.cs25
-rw-r--r--crypto/src/crypto/digests/Sha512Digest.cs21
-rw-r--r--crypto/src/crypto/digests/Sha512tDigest.cs47
-rw-r--r--crypto/src/crypto/digests/ShakeDigest.cs40
-rw-r--r--crypto/src/crypto/digests/ShortenedDigest.cs28
-rw-r--r--crypto/src/crypto/digests/SkeinDigest.cs18
-rw-r--r--crypto/src/crypto/digests/SkeinEngine.cs105
-rw-r--r--crypto/src/crypto/digests/TigerDigest.cs70
-rw-r--r--crypto/src/crypto/digests/TupleHash.cs55
-rw-r--r--crypto/src/crypto/digests/WhirlpoolDigest.cs24
-rw-r--r--crypto/src/crypto/digests/XofUtils.cs50
-rw-r--r--crypto/src/crypto/encodings/ISO9796d1Encoding.cs26
-rw-r--r--crypto/src/crypto/encodings/OaepEncoding.cs25
-rw-r--r--crypto/src/crypto/encodings/Pkcs1Encoding.cs20
-rw-r--r--crypto/src/crypto/engines/AesEngine.cs150
-rw-r--r--crypto/src/crypto/engines/AesEngine_X86.cs830
-rw-r--r--crypto/src/crypto/engines/AesLightEngine.cs150
-rw-r--r--crypto/src/crypto/engines/AesX86Engine.cs377
-rw-r--r--crypto/src/crypto/engines/AriaEngine.cs34
-rw-r--r--crypto/src/crypto/engines/BlowfishEngine.cs112
-rw-r--r--crypto/src/crypto/engines/CamelliaEngine.cs216
-rw-r--r--crypto/src/crypto/engines/CamelliaLightEngine.cs215
-rw-r--r--crypto/src/crypto/engines/Cast5Engine.cs200
-rw-r--r--crypto/src/crypto/engines/Cast6Engine.cs74
-rw-r--r--crypto/src/crypto/engines/ChaCha7539Engine.cs154
-rw-r--r--crypto/src/crypto/engines/ChaChaEngine.cs24
-rw-r--r--crypto/src/crypto/engines/DesEdeEngine.cs55
-rw-r--r--crypto/src/crypto/engines/DesEdeWrapEngine.cs5
-rw-r--r--crypto/src/crypto/engines/DesEngine.cs62
-rw-r--r--crypto/src/crypto/engines/Dstu7624Engine.cs230
-rw-r--r--crypto/src/crypto/engines/ElGamalEngine.cs22
-rw-r--r--crypto/src/crypto/engines/GOST28147Engine.cs145
-rw-r--r--crypto/src/crypto/engines/Grain128AEADEngine.cs564
-rw-r--r--crypto/src/crypto/engines/HC128Engine.cs15
-rw-r--r--crypto/src/crypto/engines/HC256Engine.cs15
-rw-r--r--crypto/src/crypto/engines/ISAACEngine.cs21
-rw-r--r--crypto/src/crypto/engines/IdeaEngine.cs125
-rw-r--r--crypto/src/crypto/engines/NoekeonEngine.cs180
-rw-r--r--crypto/src/crypto/engines/NullEngine.cs69
-rw-r--r--crypto/src/crypto/engines/RC2Engine.cs196
-rw-r--r--crypto/src/crypto/engines/RC2WrapEngine.cs13
-rw-r--r--crypto/src/crypto/engines/RC4Engine.cs34
-rw-r--r--crypto/src/crypto/engines/RC532Engine.cs186
-rw-r--r--crypto/src/crypto/engines/RC564Engine.cs186
-rw-r--r--crypto/src/crypto/engines/RC6Engine.cs300
-rw-r--r--crypto/src/crypto/engines/RFC3211WrapEngine.cs16
-rw-r--r--crypto/src/crypto/engines/RSABlindedEngine.cs10
-rw-r--r--crypto/src/crypto/engines/RSACoreEngine.cs24
-rw-r--r--crypto/src/crypto/engines/RijndaelEngine.cs92
-rw-r--r--crypto/src/crypto/engines/SEEDEngine.cs97
-rw-r--r--crypto/src/crypto/engines/SM2Engine.cs250
-rw-r--r--crypto/src/crypto/engines/SM4Engine.cs34
-rw-r--r--crypto/src/crypto/engines/Salsa20Engine.cs274
-rw-r--r--crypto/src/crypto/engines/SerpentEngine.cs153
-rw-r--r--crypto/src/crypto/engines/SerpentEngineBase.cs80
-rw-r--r--crypto/src/crypto/engines/SkipjackEngine.cs213
-rw-r--r--crypto/src/crypto/engines/TEAEngine.cs109
-rw-r--r--crypto/src/crypto/engines/ThreefishEngine.cs41
-rw-r--r--crypto/src/crypto/engines/TnepresEngine.cs153
-rw-r--r--crypto/src/crypto/engines/TwofishEngine.cs131
-rw-r--r--crypto/src/crypto/engines/VMPCEngine.cs37
-rw-r--r--crypto/src/crypto/engines/XTEAEngine.cs87
-rw-r--r--crypto/src/crypto/fpe/SP80038G.cs99
-rw-r--r--crypto/src/crypto/generators/BaseKdfBytesGenerator.cs112
-rw-r--r--crypto/src/crypto/generators/DesEdeKeyGenerator.cs5
-rw-r--r--crypto/src/crypto/generators/DesKeyGenerator.cs7
-rw-r--r--crypto/src/crypto/generators/ECKeyPairGenerator.cs2
-rw-r--r--crypto/src/crypto/generators/HKdfBytesGenerator.cs78
-rw-r--r--crypto/src/crypto/generators/KDFCounterBytesGenerator.cs134
-rw-r--r--crypto/src/crypto/generators/KDFDoublePipelineIterationBytesGenerator.cs135
-rw-r--r--crypto/src/crypto/generators/KDFFeedbackBytesGenerator.cs130
-rw-r--r--crypto/src/crypto/generators/Kdf1BytesGenerator.cs9
-rw-r--r--crypto/src/crypto/generators/Kdf2BytesGenerator.cs7
-rw-r--r--crypto/src/crypto/generators/Mgf1BytesGenerator.cs159
-rw-r--r--crypto/src/crypto/generators/Poly1305KeyGenerator.cs6
-rw-r--r--crypto/src/crypto/generators/RSABlindingFactorGenerator.cs9
-rw-r--r--crypto/src/crypto/generators/SCrypt.cs120
-rw-r--r--crypto/src/crypto/io/CipherStream.cs236
-rw-r--r--crypto/src/crypto/io/DigestSink.cs25
-rw-r--r--crypto/src/crypto/io/DigestStream.cs109
-rw-r--r--crypto/src/crypto/io/MacSink.cs25
-rw-r--r--crypto/src/crypto/io/MacStream.cs109
-rw-r--r--crypto/src/crypto/io/SignerSink.cs25
-rw-r--r--crypto/src/crypto/io/SignerStream.cs73
-rw-r--r--crypto/src/crypto/macs/CMac.cs106
-rw-r--r--crypto/src/crypto/macs/CbcBlockCipherMac.cs113
-rw-r--r--crypto/src/crypto/macs/CfbBlockCipherMac.cs162
-rw-r--r--crypto/src/crypto/macs/DSTU7564Mac.cs35
-rw-r--r--crypto/src/crypto/macs/DSTU7624Mac.cs221
-rw-r--r--crypto/src/crypto/macs/GMac.cs28
-rw-r--r--crypto/src/crypto/macs/GOST28147Mac.cs266
-rw-r--r--crypto/src/crypto/macs/HMac.cs45
-rw-r--r--crypto/src/crypto/macs/ISO9797Alg3Mac.cs96
-rw-r--r--crypto/src/crypto/macs/KMac.cs81
-rw-r--r--crypto/src/crypto/macs/Poly1305.cs132
-rw-r--r--crypto/src/crypto/macs/SipHash.cs55
-rw-r--r--crypto/src/crypto/macs/SkeinMac.cs15
-rw-r--r--crypto/src/crypto/macs/VMPCMac.cs73
-rw-r--r--crypto/src/crypto/modes/CbcBlockCipher.cs162
-rw-r--r--crypto/src/crypto/modes/CcmBlockCipher.cs294
-rw-r--r--crypto/src/crypto/modes/CfbBlockCipher.cs145
-rw-r--r--crypto/src/crypto/modes/ChaCha20Poly1305.cs284
-rw-r--r--crypto/src/crypto/modes/CtsBlockCipher.cs198
-rw-r--r--crypto/src/crypto/modes/EAXBlockCipher.cs225
-rw-r--r--crypto/src/crypto/modes/EcbBlockCipher.cs58
-rw-r--r--crypto/src/crypto/modes/GCMBlockCipher.cs781
-rw-r--r--crypto/src/crypto/modes/GOFBBlockCipher.cs120
-rw-r--r--crypto/src/crypto/modes/GcmSivBlockCipher.cs299
-rw-r--r--crypto/src/crypto/modes/IAeadBlockCipher.cs2
-rw-r--r--crypto/src/crypto/modes/IAeadCipher.cs19
-rw-r--r--crypto/src/crypto/modes/IBlockCipherMode.cs19
-rw-r--r--crypto/src/crypto/modes/KCcmBlockCipher.cs228
-rw-r--r--crypto/src/crypto/modes/KCtrBlockCipher.cs65
-rw-r--r--crypto/src/crypto/modes/OCBBlockCipher.cs229
-rw-r--r--crypto/src/crypto/modes/OfbBlockCipher.cs68
-rw-r--r--crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs230
-rw-r--r--crypto/src/crypto/modes/SicBlockCipher.cs37
-rw-r--r--crypto/src/crypto/modes/gcm/GcmUtilities.cs50
-rw-r--r--crypto/src/crypto/operators/Asn1CipherBuilder.cs5
-rw-r--r--crypto/src/crypto/operators/Asn1DigestFactory.cs10
-rw-r--r--crypto/src/crypto/operators/Asn1Signature.cs5
-rw-r--r--crypto/src/crypto/operators/DefaultSignatureCalculator.cs4
-rw-r--r--crypto/src/crypto/operators/DefaultSignatureResult.cs9
-rw-r--r--crypto/src/crypto/operators/DefaultVerifierCalculator.cs4
-rw-r--r--crypto/src/crypto/paddings/BlockCipherPadding.cs43
-rw-r--r--crypto/src/crypto/paddings/IBlockCipherPadding.cs43
-rw-r--r--crypto/src/crypto/paddings/ISO10126d2Padding.cs65
-rw-r--r--crypto/src/crypto/paddings/ISO7816d4Padding.cs78
-rw-r--r--crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs193
-rw-r--r--crypto/src/crypto/paddings/Pkcs7Padding.cs64
-rw-r--r--crypto/src/crypto/paddings/TbcPadding.cs80
-rw-r--r--crypto/src/crypto/paddings/X923Padding.cs69
-rw-r--r--crypto/src/crypto/paddings/ZeroBytePadding.cs58
-rw-r--r--crypto/src/crypto/parameters/Blake3Parameters.cs58
-rw-r--r--crypto/src/crypto/parameters/DesParameters.cs10
-rw-r--r--crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs26
-rw-r--r--crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs20
-rw-r--r--crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs26
-rw-r--r--crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs20
-rw-r--r--crypto/src/crypto/parameters/KdfParameters.cs20
-rw-r--r--crypto/src/crypto/parameters/KeyParameter.cs36
-rw-r--r--crypto/src/crypto/parameters/MgfParameters.cs35
-rw-r--r--crypto/src/crypto/parameters/ParametersWithID.cs27
-rw-r--r--crypto/src/crypto/parameters/ParametersWithIV.cs31
-rw-r--r--crypto/src/crypto/parameters/ParametersWithRandom.cs37
-rw-r--r--crypto/src/crypto/parameters/ParametersWithSalt.cs33
-rw-r--r--crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs40
-rw-r--r--crypto/src/crypto/parameters/X25519PublicKeyParameters.cs20
-rw-r--r--crypto/src/crypto/parameters/X448PrivateKeyParameters.cs40
-rw-r--r--crypto/src/crypto/parameters/X448PublicKeyParameters.cs20
-rw-r--r--crypto/src/crypto/prng/BasicEntropySourceProvider.cs15
-rw-r--r--crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs9
-rw-r--r--crypto/src/crypto/prng/CryptoApiRandomGenerator.cs36
-rw-r--r--crypto/src/crypto/prng/DigestRandomGenerator.cs91
-rw-r--r--crypto/src/crypto/prng/IRandomGenerator.cs14
-rw-r--r--crypto/src/crypto/prng/ReversedWindowGenerator.cs98
-rw-r--r--crypto/src/crypto/prng/SP800SecureRandom.cs42
-rw-r--r--crypto/src/crypto/prng/SP800SecureRandomBuilder.cs5
-rw-r--r--crypto/src/crypto/prng/VMPCRandomGenerator.cs132
-rw-r--r--crypto/src/crypto/prng/X931Rng.cs63
-rw-r--r--crypto/src/crypto/prng/X931SecureRandom.cs34
-rw-r--r--crypto/src/crypto/prng/X931SecureRandomBuilder.cs5
-rw-r--r--crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs630
-rw-r--r--crypto/src/crypto/prng/drbg/DrbgUtilities.cs106
-rw-r--r--crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs188
-rw-r--r--crypto/src/crypto/prng/drbg/HashSP800Drbg.cs359
-rw-r--r--crypto/src/crypto/prng/drbg/ISP80090Drbg.cs20
-rw-r--r--crypto/src/crypto/signers/DsaDigestSigner.cs58
-rw-r--r--crypto/src/crypto/signers/DsaSigner.cs4
-rw-r--r--crypto/src/crypto/signers/ECDsaSigner.cs5
-rw-r--r--crypto/src/crypto/signers/ECGOST3410Signer.cs50
-rw-r--r--crypto/src/crypto/signers/ECNRSigner.cs12
-rw-r--r--crypto/src/crypto/signers/Ed25519Signer.cs7
-rw-r--r--crypto/src/crypto/signers/Ed25519ctxSigner.cs7
-rw-r--r--crypto/src/crypto/signers/Ed25519phSigner.cs7
-rw-r--r--crypto/src/crypto/signers/Ed448Signer.cs7
-rw-r--r--crypto/src/crypto/signers/Ed448phSigner.cs9
-rw-r--r--crypto/src/crypto/signers/GOST3410DigestSigner.cs42
-rw-r--r--crypto/src/crypto/signers/GOST3410Signer.cs12
-rw-r--r--crypto/src/crypto/signers/GenericSigner.cs25
-rw-r--r--crypto/src/crypto/signers/HMacDsaKCalculator.cs53
-rw-r--r--crypto/src/crypto/signers/Iso9796d2PssSigner.cs63
-rw-r--r--crypto/src/crypto/signers/Iso9796d2Signer.cs46
-rw-r--r--crypto/src/crypto/signers/PssSigner.cs49
-rw-r--r--crypto/src/crypto/signers/RsaDigestSigner.cs34
-rw-r--r--crypto/src/crypto/signers/SM2Signer.cs17
-rw-r--r--crypto/src/crypto/signers/X931Signer.cs47
-rw-r--r--crypto/src/crypto/util/CipherFactory.cs2
-rw-r--r--crypto/src/crypto/util/CipherKeyGeneratorFactory.cs6
-rw-r--r--crypto/src/crypto/util/Pack.cs279
-rw-r--r--crypto/src/math/BigInteger.cs251
-rw-r--r--crypto/src/math/ec/ECAlgorithms.cs13
-rw-r--r--crypto/src/math/ec/ECCurve.cs167
-rw-r--r--crypto/src/math/ec/ECFieldElement.cs27
-rw-r--r--crypto/src/math/ec/ECPoint.cs78
-rw-r--r--crypto/src/math/ec/LongArray.cs157
-rw-r--r--crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs4
-rw-r--r--crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs1
-rw-r--r--crypto/src/math/ec/custom/sec/SecT113Field.cs23
-rw-r--r--crypto/src/math/ec/custom/sec/SecT131Field.cs31
-rw-r--r--crypto/src/math/ec/custom/sec/SecT163Field.cs32
-rw-r--r--crypto/src/math/ec/custom/sec/SecT193Field.cs36
-rw-r--r--crypto/src/math/ec/custom/sec/SecT233Field.cs52
-rw-r--r--crypto/src/math/ec/custom/sec/SecT239Field.cs52
-rw-r--r--crypto/src/math/ec/custom/sec/SecT283Field.cs54
-rw-r--r--crypto/src/math/ec/custom/sec/SecT409Field.cs18
-rw-r--r--crypto/src/math/ec/custom/sec/SecT571Field.cs30
-rw-r--r--crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs15
-rw-r--r--crypto/src/math/ec/rfc7748/X25519.cs144
-rw-r--r--crypto/src/math/ec/rfc7748/X25519Field.cs204
-rw-r--r--crypto/src/math/ec/rfc7748/X448.cs148
-rw-r--r--crypto/src/math/ec/rfc7748/X448Field.cs229
-rw-r--r--crypto/src/math/ec/rfc8032/Ed25519.cs330
-rw-r--r--crypto/src/math/ec/rfc8032/Ed448.cs315
-rw-r--r--crypto/src/math/raw/Bits.cs65
-rw-r--r--crypto/src/math/raw/Interleave.cs45
-rw-r--r--crypto/src/math/raw/Mod.cs328
-rw-r--r--crypto/src/math/raw/Nat.cs1682
-rw-r--r--crypto/src/math/raw/Nat256.cs71
-rw-r--r--crypto/src/math/raw/Nat512.cs315
-rw-r--r--crypto/src/ocsp/BasicOCSPRespGenerator.cs13
-rw-r--r--crypto/src/ocsp/RevokedStatus.cs45
-rw-r--r--crypto/src/ocsp/SingleResp.cs11
-rw-r--r--crypto/src/openpgp/EdDsaSigner.cs72
-rw-r--r--crypto/src/openpgp/PgpCompressedData.cs23
-rw-r--r--crypto/src/openpgp/PgpCompressedDataGenerator.cs90
-rw-r--r--crypto/src/openpgp/PgpEncryptedData.cs31
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataGenerator.cs172
-rw-r--r--crypto/src/openpgp/PgpKdfParameters.cs21
-rw-r--r--crypto/src/openpgp/PgpObjectFactory.cs5
-rw-r--r--crypto/src/openpgp/PgpOnePassSignature.cs78
-rw-r--r--crypto/src/openpgp/PgpPbeEncryptedData.cs5
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs325
-rw-r--r--crypto/src/openpgp/PgpPublicKeyEncryptedData.cs156
-rw-r--r--crypto/src/openpgp/PgpPublicKeyRing.cs26
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs105
-rw-r--r--crypto/src/openpgp/PgpSecretKeyRing.cs47
-rw-r--r--crypto/src/openpgp/PgpSignature.cs153
-rw-r--r--crypto/src/openpgp/PgpSignatureGenerator.cs193
-rw-r--r--crypto/src/openpgp/PgpUtilities.cs57
-rw-r--r--crypto/src/openpgp/PgpV3SignatureGenerator.cs104
-rw-r--r--crypto/src/openpgp/Rfc6637Utilities.cs14
-rw-r--r--crypto/src/openpgp/WrappedGeneratorStream.cs11
-rw-r--r--crypto/src/openssl/PEMUtilities.cs12
-rw-r--r--crypto/src/openssl/Pkcs8Generator.cs5
-rw-r--r--crypto/src/pkcs/Pkcs10CertificationRequest.cs8
-rw-r--r--crypto/src/pkix/CertStatus.cs6
-rw-r--r--crypto/src/pkix/PkixCertPath.cs1
-rw-r--r--crypto/src/pkix/PkixCertPathValidatorResult.cs20
-rw-r--r--crypto/src/pkix/PkixCertPathValidatorUtilities.cs9
-rw-r--r--crypto/src/pkix/PkixCrlUtilities.cs3
-rw-r--r--crypto/src/pkix/PkixParameters.cs5
-rw-r--r--crypto/src/pqc/crypto/bike/BikeEngine.cs504
-rw-r--r--crypto/src/pqc/crypto/bike/BikeKemExtractor.cs43
-rw-r--r--crypto/src/pqc/crypto/bike/BikeKemGenerator.cs86
-rw-r--r--crypto/src/pqc/crypto/bike/BikeKeyGenerationParameters.cs19
-rw-r--r--crypto/src/pqc/crypto/bike/BikeKeyPairGenerator.cs71
-rw-r--r--crypto/src/pqc/crypto/bike/BikeKeyParameters.cs18
-rw-r--r--crypto/src/pqc/crypto/bike/BikeParameters.cs52
-rw-r--r--crypto/src/pqc/crypto/bike/BikePrivateKeyParameters.cs54
-rw-r--r--crypto/src/pqc/crypto/bike/BikePublicKeyParameters.cs28
-rw-r--r--crypto/src/pqc/crypto/bike/BikeRing.cs301
-rw-r--r--crypto/src/pqc/crypto/bike/BikeUtilities.cs111
-rw-r--r--crypto/src/pqc/crypto/cmce/CmceEngine.cs97
-rw-r--r--crypto/src/pqc/crypto/cmce/CmceKemExtractor.cs7
-rw-r--r--crypto/src/pqc/crypto/cmce/CmceKemGenerator.cs2
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumEngine.cs361
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyGenerationParameters.cs18
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyPairGenerator.cs34
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyParameters.cs17
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumParameters.cs32
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumPrivateKeyParameters.cs49
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumPublicKeyParameters.cs35
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/DilithiumSigner.cs55
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/Ntt.cs90
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/Packing.cs149
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/Poly.cs678
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/PolyVecK.cs150
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/PolyVecL.cs103
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/PolyVecMatrix.cs41
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/Reduce.cs30
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/Rounding.cs90
-rw-r--r--crypto/src/pqc/crypto/crystals/dilithium/Symmetric.cs126
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/CBD.cs52
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs180
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs238
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs28
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs83
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs19
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs42
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs18
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs34
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs36
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs34
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Ntt.cs91
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Poly.cs248
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs228
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Reduce.cs29
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs165
-rw-r--r--crypto/src/pqc/crypto/falcon/FPREngine.cs1311
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconCodec.cs576
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconCommon.cs304
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconConversions.cs66
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconFFT.cs711
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconFPR.cs14
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconKeyGenerationParameters.cs22
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconKeyPairGenerator.cs51
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconKeyParameters.cs22
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconKeygen.cs3673
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconNIST.cs317
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconParameters.cs29
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconPrivateKeyParameters.cs48
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconPublicKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconRNG.cs261
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconSign.cs974
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconSigner.cs69
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconSmallPrime.cs46
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconSmallPrimes.cs531
-rw-r--r--crypto/src/pqc/crypto/falcon/FalconVrfy.cs860
-rw-r--r--crypto/src/pqc/crypto/falcon/SHAKE256.cs569
-rw-r--r--crypto/src/pqc/crypto/falcon/SamplerZ.cs229
-rw-r--r--crypto/src/pqc/crypto/frodo/FrodoEngine.cs68
-rw-r--r--crypto/src/pqc/crypto/frodo/FrodoKEMExtractor.cs5
-rw-r--r--crypto/src/pqc/crypto/frodo/FrodoMatrixGenerator.cs102
-rw-r--r--crypto/src/pqc/crypto/frodo/FrodoParameters.cs26
-rw-r--r--crypto/src/pqc/crypto/hqc/FastFourierTransform.cs279
-rw-r--r--crypto/src/pqc/crypto/hqc/GF2PolynomialCalculator.cs134
-rw-r--r--crypto/src/pqc/crypto/hqc/GFCalculator.cs29
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcEngine.cs436
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcKeccakRandomGenerator.cs321
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcKemExtractor.cs37
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcKemGenerator.cs89
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcKeyGenerationParameters.cs19
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcKeyPairGenerator.cs69
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcKeyParameters.cs19
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcParameters.cs71
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcPrivateKeyParameters.cs20
-rw-r--r--crypto/src/pqc/crypto/hqc/HqcPublicKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/hqc/ReedMuller.cs196
-rw-r--r--crypto/src/pqc/crypto/hqc/ReedSolomon.cs263
-rw-r--r--crypto/src/pqc/crypto/hqc/Utils.cs155
-rw-r--r--crypto/src/pqc/crypto/lms/Composer.cs117
-rw-r--r--crypto/src/pqc/crypto/lms/HSS.cs136
-rw-r--r--crypto/src/pqc/crypto/lms/HSSKeyGenerationParameters.cs40
-rw-r--r--crypto/src/pqc/crypto/lms/HSSKeyPairGenerator.cs14
-rw-r--r--crypto/src/pqc/crypto/lms/HSSPrivateKeyParameters.cs272
-rw-r--r--crypto/src/pqc/crypto/lms/HSSPublicKeyParameters.cs103
-rw-r--r--crypto/src/pqc/crypto/lms/HSSSignature.cs124
-rw-r--r--crypto/src/pqc/crypto/lms/HSSSigner.cs14
-rw-r--r--crypto/src/pqc/crypto/lms/ILMSContextBasedSigner.cs6
-rw-r--r--crypto/src/pqc/crypto/lms/ILMSContextBasedVerifier.cs6
-rw-r--r--crypto/src/pqc/crypto/lms/LMOtsParameters.cs94
-rw-r--r--crypto/src/pqc/crypto/lms/LMOtsPrivateKey.cs75
-rw-r--r--crypto/src/pqc/crypto/lms/LMOtsPublicKey.cs158
-rw-r--r--crypto/src/pqc/crypto/lms/LMOtsSignature.cs110
-rw-r--r--crypto/src/pqc/crypto/lms/LMS.cs67
-rw-r--r--crypto/src/pqc/crypto/lms/LMSContext.cs137
-rw-r--r--crypto/src/pqc/crypto/lms/LMSException.cs10
-rw-r--r--crypto/src/pqc/crypto/lms/LMSKeyGenerationParameters.cs17
-rw-r--r--crypto/src/pqc/crypto/lms/LMSKeyPairGenerator.cs17
-rw-r--r--crypto/src/pqc/crypto/lms/LMSKeyParameters.cs14
-rw-r--r--crypto/src/pqc/crypto/lms/LMSParameters.cs24
-rw-r--r--crypto/src/pqc/crypto/lms/LMSPrivateKeyParameters.cs210
-rw-r--r--crypto/src/pqc/crypto/lms/LMSPublicKeyParameters.cs88
-rw-r--r--crypto/src/pqc/crypto/lms/LMSSignature.cs73
-rw-r--r--crypto/src/pqc/crypto/lms/LMSSignedPubKey.cs15
-rw-r--r--crypto/src/pqc/crypto/lms/LMSSigner.cs16
-rw-r--r--crypto/src/pqc/crypto/lms/LMSigParameters.cs65
-rw-r--r--crypto/src/pqc/crypto/lms/LM_OTS.cs138
-rw-r--r--crypto/src/pqc/crypto/lms/LmsUtils.cs17
-rw-r--r--crypto/src/pqc/crypto/lms/SeedDerive.cs105
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruEncapsulation.cs57
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruKemExtractor.cs84
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruKemGenerator.cs70
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruKeyGenerationParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruKeyPairGenerator.cs44
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruParameters.cs33
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruPrivateKeyParamaters.cs24
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruPublicKeyParameters.cs23
-rw-r--r--crypto/src/pqc/crypto/ntru/NtruSampling.cs207
-rw-r--r--crypto/src/pqc/crypto/ntru/PolynomialPair.cs36
-rw-r--r--crypto/src/pqc/crypto/ntru/owcpa/NtruOwcpa.cs246
-rw-r--r--crypto/src/pqc/crypto/ntru/owcpa/OwcpaDecryptResult.cs14
-rw-r--r--crypto/src/pqc/crypto/ntru/owcpa/OwcpaKeyPair.cs14
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048509.cs9
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048677.cs9
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruHps4096821.cs16
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruHpsParameterSet.cs32
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruHrss701.cs9
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruHrssParameterSet.cs27
-rw-r--r--crypto/src/pqc/crypto/ntru/parametersets/NtruParameterSet.cs88
-rw-r--r--crypto/src/pqc/crypto/ntru/polynomials/Hps4096Polynomial.cs42
-rw-r--r--crypto/src/pqc/crypto/ntru/polynomials/HpsPolynomial.cs162
-rw-r--r--crypto/src/pqc/crypto/ntru/polynomials/HrssPolynomial.cs217
-rw-r--r--crypto/src/pqc/crypto/ntru/polynomials/Polynomial.cs417
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemExtractor.cs30
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemGenerator.cs (renamed from crypto/src/pqc/crypto/ntruprime/NtruPrimeKemGenerator.cs)16
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyGenerationParameters.cs18
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyPairGenerator.cs49
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyParameters.cs17
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimeParameters.cs67
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimePrivateKeyParameters.cs (renamed from crypto/src/pqc/crypto/ntruprime/NtruPrimePrivateKeyParameters.cs)11
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruLPRimePublicKeyParameters.cs (renamed from crypto/src/pqc/crypto/ntruprime/NtruPrimePublicKeyParameters.cs)11
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruPrimeEngine.cs11
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruPrimeKemExtractor.cs35
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyGenerationParameters.cs18
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyPairGenerator.cs69
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyParameters.cs17
-rw-r--r--crypto/src/pqc/crypto/ntruprime/NtruPrimeParameters.cs72
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemExtractor.cs30
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemGenerator.cs77
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyGenerationParameters.cs18
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyPairGenerator.cs49
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyParameters.cs17
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimeParameters.cs66
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimePrivateKeyParameters.cs25
-rw-r--r--crypto/src/pqc/crypto/ntruprime/SNtruPrimePublicKeyParameters.cs24
-rw-r--r--crypto/src/pqc/crypto/picnic/KMatrices.cs24
-rw-r--r--crypto/src/pqc/crypto/picnic/Msg.cs6
-rw-r--r--crypto/src/pqc/crypto/picnic/PicnicEngine.cs451
-rw-r--r--crypto/src/pqc/crypto/picnic/PicnicParameters.cs14
-rw-r--r--crypto/src/pqc/crypto/picnic/Signature2.cs11
-rw-r--r--crypto/src/pqc/crypto/picnic/Tape.cs187
-rw-r--r--crypto/src/pqc/crypto/picnic/Tree.cs39
-rw-r--r--crypto/src/pqc/crypto/picnic/View.cs8
-rw-r--r--crypto/src/pqc/crypto/saber/Poly.cs81
-rw-r--r--crypto/src/pqc/crypto/saber/SABEREngine.cs197
-rw-r--r--crypto/src/pqc/crypto/saber/SABERKEMExtractor.cs21
-rw-r--r--crypto/src/pqc/crypto/saber/SABERKEMGenerator.cs18
-rw-r--r--crypto/src/pqc/crypto/saber/SABERKeyGenerationParameters.cs16
-rw-r--r--crypto/src/pqc/crypto/saber/SABERKeyPairGenerator.cs17
-rw-r--r--crypto/src/pqc/crypto/saber/SABERKeyParameters.cs16
-rw-r--r--crypto/src/pqc/crypto/saber/SABERParameters.cs68
-rw-r--r--crypto/src/pqc/crypto/saber/SABERPrivateKeyParameters.cs20
-rw-r--r--crypto/src/pqc/crypto/saber/SABERPublicKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/saber/SaberUtilities.cs (renamed from crypto/src/pqc/crypto/saber/Utils.cs)153
-rw-r--r--crypto/src/pqc/crypto/saber/Symmetric.cs99
-rw-r--r--crypto/src/pqc/crypto/sike/Fpx.cs155
-rw-r--r--crypto/src/pqc/crypto/sike/Internal.cs401
-rw-r--r--crypto/src/pqc/crypto/sike/Isogeny.cs243
-rw-r--r--crypto/src/pqc/crypto/sike/P434.cs2
-rw-r--r--crypto/src/pqc/crypto/sike/P503.cs4
-rw-r--r--crypto/src/pqc/crypto/sike/P610.cs2
-rw-r--r--crypto/src/pqc/crypto/sike/P751.cs2
-rw-r--r--crypto/src/pqc/crypto/sike/PointProj.cs17
-rw-r--r--crypto/src/pqc/crypto/sike/PointProjFull.cs20
-rw-r--r--crypto/src/pqc/crypto/sike/SIDH.cs160
-rw-r--r--crypto/src/pqc/crypto/sike/SIDH_Compressed.cs447
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEEngine.cs415
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEKEMExtractor.cs73
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEKEMGenerator.cs55
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEKeyGenerationParameters.cs31
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEKeyPairGenerator.cs19
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEKeyParameters.cs30
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEParameters.cs47
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEPrivateKeyParameters.cs38
-rw-r--r--crypto/src/pqc/crypto/sike/SIKEPublicKeyParameters.cs38
-rw-r--r--crypto/src/pqc/crypto/sike/SikeUtilities.cs (renamed from crypto/src/pqc/crypto/sike/Utils.cs)14
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/Adrs.cs70
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/Fors.cs124
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HT.cs81
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaS256Digest.cs76
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaS512Digest.cs86
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaSBase.cs778
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs134
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs209
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/IndexedDigest.cs4
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/NodeEntry.cs8
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/PK.cs4
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SIG.cs7
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SIG_FORS.cs4
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SIG_XMSS.cs16
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SK.cs4
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs638
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyGenerationParameters.cs12
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyPairGenerator.cs50
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyParameters.cs15
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusParameters.cs321
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPrivateKeyParameters.cs40
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPublicKeyParameters.cs28
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusSigner.cs64
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs249
-rw-r--r--crypto/src/pqc/crypto/utils/PqcUtilities.cs242
-rw-r--r--crypto/src/pqc/crypto/utils/PrivateKeyFactory.cs157
-rw-r--r--crypto/src/pqc/crypto/utils/PrivateKeyInfoFactory.cs153
-rw-r--r--crypto/src/pqc/crypto/utils/PublicKeyFactory.cs308
-rw-r--r--crypto/src/pqc/crypto/utils/SecretWithEncapsulationImpl.cs76
-rw-r--r--crypto/src/pqc/crypto/utils/SubjectPublicKeyInfoFactory.cs143
-rw-r--r--crypto/src/security/CipherUtilities.cs46
-rw-r--r--crypto/src/security/DigestUtilities.cs40
-rw-r--r--crypto/src/security/DotNetUtilities.cs27
-rw-r--r--crypto/src/security/GeneratorUtilities.cs6
-rw-r--r--crypto/src/security/JksStore.cs596
-rw-r--r--crypto/src/security/ParameterUtilities.cs2
-rw-r--r--crypto/src/security/PrivateKeyFactory.cs18
-rw-r--r--crypto/src/security/PublicKeyFactory.cs12
-rw-r--r--crypto/src/security/SecureRandom.cs80
-rw-r--r--crypto/src/security/SignerUtilities.cs30
-rw-r--r--crypto/src/security/WrapperUtilities.cs5
-rw-r--r--crypto/src/tls/AbstractTlsClient.cs37
-rw-r--r--crypto/src/tls/AbstractTlsContext.cs13
-rw-r--r--crypto/src/tls/AbstractTlsServer.cs70
-rw-r--r--crypto/src/tls/ByteQueue.cs80
-rw-r--r--crypto/src/tls/ByteQueueInputStream.cs20
-rw-r--r--crypto/src/tls/ByteQueueOutputStream.cs7
-rw-r--r--crypto/src/tls/Certificate.cs74
-rw-r--r--crypto/src/tls/ChannelBinding.cs5
-rw-r--r--crypto/src/tls/CombinedHash.cs8
-rw-r--r--crypto/src/tls/DeferredHash.cs16
-rw-r--r--crypto/src/tls/DtlsClientProtocol.cs6
-rw-r--r--crypto/src/tls/DtlsReliableHandshake.cs1
-rw-r--r--crypto/src/tls/DtlsServerProtocol.cs8
-rw-r--r--crypto/src/tls/RecordStream.cs51
-rw-r--r--crypto/src/tls/SecurityParameters.cs9
-rw-r--r--crypto/src/tls/TlsClientProtocol.cs17
-rw-r--r--crypto/src/tls/TlsExtensionsUtilities.cs10
-rw-r--r--crypto/src/tls/TlsProtocol.cs153
-rw-r--r--crypto/src/tls/TlsServerProtocol.cs12
-rw-r--r--crypto/src/tls/TlsStream.cs26
-rw-r--r--crypto/src/tls/TlsUtilities.cs57
-rw-r--r--crypto/src/tls/crypto/TlsCipher.cs5
-rw-r--r--crypto/src/tls/crypto/TlsCrypto.cs11
-rw-r--r--crypto/src/tls/crypto/TlsCryptoUtilities.cs50
-rw-r--r--crypto/src/tls/crypto/TlsHash.cs4
-rw-r--r--crypto/src/tls/crypto/TlsHashSink.cs10
-rw-r--r--crypto/src/tls/crypto/TlsMac.cs8
-rw-r--r--crypto/src/tls/crypto/TlsMacSink.cs10
-rw-r--r--crypto/src/tls/crypto/TlsNullNullCipher.cs10
-rw-r--r--crypto/src/tls/crypto/TlsSecret.cs14
-rw-r--r--crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs11
-rw-r--r--crypto/src/tls/crypto/impl/AbstractTlsSecret.cs41
-rw-r--r--crypto/src/tls/crypto/impl/TlsAeadCipher.cs110
-rw-r--r--crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs4
-rw-r--r--crypto/src/tls/crypto/impl/TlsBlockCipher.cs139
-rw-r--r--crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs8
-rw-r--r--crypto/src/tls/crypto/impl/TlsImplUtilities.cs18
-rw-r--r--crypto/src/tls/crypto/impl/TlsNullCipher.cs43
-rw-r--r--crypto/src/tls/crypto/impl/TlsSuiteHmac.cs26
-rw-r--r--crypto/src/tls/crypto/impl/TlsSuiteMac.cs4
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs15
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs17
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs11
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs14
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs485
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs87
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs2
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsHash.cs7
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs14
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsRawKeyCertificate.cs507
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs146
-rw-r--r--crypto/src/tsp/TimeStampResponseGenerator.cs23
-rw-r--r--crypto/src/tsp/TimeStampTokenGenerator.cs71
-rw-r--r--crypto/src/util/Arrays.cs119
-rw-r--r--crypto/src/util/BigIntegers.cs129
-rw-r--r--crypto/src/util/Bytes.cs2
-rw-r--r--crypto/src/util/Enums.cs33
-rw-r--r--crypto/src/util/Integers.cs38
-rw-r--r--crypto/src/util/Longs.cs38
-rw-r--r--crypto/src/util/Platform.cs2
-rw-r--r--crypto/src/util/Shorts.cs54
-rw-r--r--crypto/src/util/Spans.cs42
-rw-r--r--crypto/src/util/Strings.cs2
-rw-r--r--crypto/src/util/Times.cs14
-rw-r--r--crypto/src/util/bzip2/BZip2Constants.cs (renamed from crypto/src/bzip2/BZip2Constants.cs)4
-rw-r--r--crypto/src/util/bzip2/CBZip2InputStream.cs (renamed from crypto/src/bzip2/CBZip2InputStream.cs)3
-rw-r--r--crypto/src/util/bzip2/CBZip2OutputStream.cs (renamed from crypto/src/bzip2/CBZip2OutputStream.cs)65
-rw-r--r--crypto/src/util/bzip2/CRC.cs (renamed from crypto/src/bzip2/CRC.cs)5
-rw-r--r--crypto/src/util/collections/CollectionUtilities.cs1
-rw-r--r--crypto/src/util/date/DateTimeObject.cs25
-rw-r--r--crypto/src/util/date/DateTimeUtilities.cs58
-rw-r--r--crypto/src/util/encoders/Base64Encoder.cs194
-rw-r--r--crypto/src/util/encoders/HexEncoder.cs127
-rw-r--r--crypto/src/util/encoders/IEncoder.cs8
-rw-r--r--crypto/src/util/io/BaseInputStream.cs4
-rw-r--r--crypto/src/util/io/BaseOutputStream.cs11
-rw-r--r--crypto/src/util/io/BinaryReaders.cs94
-rw-r--r--crypto/src/util/io/BinaryWriters.cs86
-rw-r--r--crypto/src/util/io/FilterStream.cs65
-rw-r--r--crypto/src/util/io/LimitedInputStream.cs56
-rw-r--r--crypto/src/util/io/NullOutputStream.cs17
-rw-r--r--crypto/src/util/io/PushbackStream.cs36
-rw-r--r--crypto/src/util/io/Streams.cs66
-rw-r--r--crypto/src/util/io/TeeInputStream.cs25
-rw-r--r--crypto/src/util/io/TeeOutputStream.cs19
-rw-r--r--crypto/src/util/io/compression/Bzip2.cs21
-rw-r--r--crypto/src/util/io/compression/ZLib.cs46
-rw-r--r--crypto/src/util/io/compression/Zip.cs33
-rw-r--r--crypto/src/util/zlib/ZInputStream.cs26
-rw-r--r--crypto/src/util/zlib/ZOutputStream.cs108
-rw-r--r--crypto/src/x509/X509Certificate.cs6
-rw-r--r--crypto/src/x509/X509Crl.cs20
-rw-r--r--crypto/src/x509/X509V1CertificateGenerator.cs4
-rw-r--r--crypto/src/x509/X509V2AttributeCertificate.cs9
-rw-r--r--crypto/src/x509/X509V2AttributeCertificateGenerator.cs8
-rw-r--r--crypto/src/x509/X509V2CRLGenerator.cs7
-rw-r--r--crypto/src/x509/X509V3CertificateGenerator.cs4
-rw-r--r--crypto/src/x509/store/X509AttrCertStoreSelector.cs5
-rw-r--r--crypto/src/x509/store/X509CertStoreSelector.cs9
-rw-r--r--crypto/src/x509/store/X509CrlStoreSelector.cs7
833 files changed, 58194 insertions, 16037 deletions
diff --git a/crypto/src/AssemblyInfo.cs b/crypto/src/AssemblyInfo.cs
index f89d58fbd..dfc22336a 100644
--- a/crypto/src/AssemblyInfo.cs
+++ b/crypto/src/AssemblyInfo.cs
@@ -3,29 +3,3 @@ using System.Runtime.InteropServices;
 
 [assembly: CLSCompliant(true)]
 [assembly: ComVisible(false)]
-
-// Start with no permissions
-//[assembly: PermissionSet(SecurityAction.RequestOptional, Unrestricted=false)]
-//...and explicitly add those we need
-
-// see Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding.StrictLengthEnabledProperty
-//[assembly: EnvironmentPermission(SecurityAction.RequestOptional, Read="Org.BouncyCastle.Pkcs1.Strict")]
-
-#if !(NET45_OR_GREATER || NETSTANDARD1_0_OR_GREATER)
-namespace System.Reflection
-{
-    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
-    internal sealed class AssemblyMetadataAttribute : Attribute
-    {
-        public AssemblyMetadataAttribute(string key, string value)
-        {
-            Key = key;
-            Value = value;
-        }
-
-        public string Key { get; }
-
-        public string Value { get; }
-    }
-}
-#endif
diff --git a/crypto/src/BouncyCastle.Crypto.csproj b/crypto/src/BouncyCastle.Crypto.csproj
index bc17aae6a..8c77860b1 100644
--- a/crypto/src/BouncyCastle.Crypto.csproj
+++ b/crypto/src/BouncyCastle.Crypto.csproj
@@ -1,17 +1,62 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net6.0;netstandard2.0;net40</TargetFrameworks>
+    <TargetFrameworks>net6.0;netstandard2.0;net461</TargetFrameworks>
     <RootNamespace>Org.BouncyCastle</RootNamespace>
-    <AssemblyOriginatorKeyFile>..\..\BouncyCastle.snk</AssemblyOriginatorKeyFile>
+    <AssemblyOriginatorKeyFile>..\..\BouncyCastle.NET.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
+	<NoWarn>1591</NoWarn>
+
+    <AssemblyName>BouncyCastle.Cryptography</AssemblyName>
+    <AssemblyTitle>BouncyCastle.NET Cryptography ($(TargetFramework))</AssemblyTitle>
+    <Authors>Legion of the Bouncy Castle Inc.</Authors>
+    <Company>Legion of the Bouncy Castle Inc.</Company>
+    <Copyright>Copyright © Legion of the Bouncy Castle Inc. 2000-2022</Copyright>
+    <Description>BouncyCastle.NET is a popular cryptography library for .NET</Description>
+    <PackageIcon>packageIcon.png</PackageIcon>
+    <PackageIconUrl>https://www.bouncycastle.org/images/nuget_packageIcon.png</PackageIconUrl>
+    <PackageId>BouncyCastle.Cryptography</PackageId>
+    <PackageLicenseFile>LICENSE.md</PackageLicenseFile>
+    <PackageProjectUrl>https://www.bouncycastle.org/csharp/</PackageProjectUrl>
+    <PackageReadmeFile>README.md</PackageReadmeFile>
+    <PackageReleaseNotes>https://www.bouncycastle.org/csharp/</PackageReleaseNotes>
+    <PackageTags>bouncycastle cryptography dtls encryption open-source post-quantum security tls</PackageTags>
+	<Product>BouncyCastle.NET</Product>
+    <RepositoryType>git</RepositoryType>
+    <RepositoryUrl>https://github.com/bcgit/bc-csharp</RepositoryUrl>
+    <Title>BouncyCastle.NET Cryptography</Title>
+  </PropertyGroup>
+
+  <!--Source-Level Debugging-->
+  <PropertyGroup>
+    <DebugType>embedded</DebugType>
+    <EmbedAllSources>true</EmbedAllSources>
+  </PropertyGroup>
+
+  <!--Package Validation-->
+  <PropertyGroup>
+    <EnablePackageValidation>true</EnablePackageValidation>
+
+    <!-- TODO: Enable this once there is a baseline version to compare to. -->
+    <!--<PackageValidationBaselineVersion>2.0.0</PackageValidationBaselineVersion>-->
+
+	<!-- In case we disable signing for local builds, ignore identity mismatch with baseline version. -->
+    <NoWarn Condition="'$(SignAssembly)' != 'true'">$(NoWarn);CP0003</NoWarn>
+
+	<!--
+	  We added Span-based variant methods to several APIs. Code that uses those methods or implements the
+	  affected interfaces (or abstract classes) will not be backward compatible.
+
+	  TODO: Use suppressions for each individual case of a Span-based method.
+    -->
+    <NoWarn>$(NoWarn);CP0005;CP0006</NoWarn>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
     <DefineConstants>DEBUG;TRACE</DefineConstants>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
     <DefineConstants />
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
@@ -19,16 +64,25 @@
   <ItemGroup>
     <None Remove="**\*.properties" />
     <EmbeddedResource Include="**\*.properties" />
+    <None Include="..\..\LICENSE.md" Pack="true" PackagePath="\" />
+    <None Include="..\..\packageIcon.png" Pack="true" PackagePath="\" />
+    <None Include="..\..\README.md" Pack="true" PackagePath="\" />
   </ItemGroup>
+
   <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Nerdbank.GitVersioning" Version="3.5.108">
+    <PackageReference Include="Nerdbank.GitVersioning" Version="3.5.119">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
   </ItemGroup>
 
+  <Target Name="FixAssemblyAttributes" AfterTargets="GetBuildVersion">
+    <PropertyGroup>
+		<!-- Here we can override/use any MSBuild properties set by Nerdbank.GitVersioning -->
+    </PropertyGroup>
+  </Target>
 </Project>
diff --git a/crypto/src/asn1/Asn1EncodableVector.cs b/crypto/src/asn1/Asn1EncodableVector.cs
index f50eb6f98..bf8d324ad 100644
--- a/crypto/src/asn1/Asn1EncodableVector.cs
+++ b/crypto/src/asn1/Asn1EncodableVector.cs
@@ -133,6 +133,14 @@ namespace Org.BouncyCastle.Asn1
             }
         }
 
+        public void AddOptionalTagged(bool isExplicit, int tagClass, int tagNo, Asn1Encodable obj)
+        {
+            if (null != obj)
+            {
+                Add(new DerTaggedObject(isExplicit, tagClass, tagNo, obj));
+            }
+        }
+
         public void AddAll(Asn1EncodableVector other)
         {
             if (null == other)
diff --git a/crypto/src/asn1/Asn1GeneralizedTime.cs b/crypto/src/asn1/Asn1GeneralizedTime.cs
new file mode 100644
index 000000000..139384c1a
--- /dev/null
+++ b/crypto/src/asn1/Asn1GeneralizedTime.cs
@@ -0,0 +1,438 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Asn1
+{
+    /**
+     * Base class representing the ASN.1 GeneralizedTime type.
+     * <p>
+     * The main difference between these and UTC time is a 4 digit year.
+     * </p><p>
+     * One second resolution date+time on UTC timezone (Z)
+     * with 4 digit year (valid from 0001 to 9999).
+     * </p><p>
+     * Timestamp format is:  yyyymmddHHMMSS'Z'
+     * </p><p>
+     * <h2>X.690</h2>
+     * This is what is called "restricted string",
+     * and it uses ASCII characters to encode digits and supplemental data.
+     *
+     * <h3>11: Restrictions on BER employed by both CER and DER</h3>
+     * <h4>11.7 GeneralizedTime </h4>
+     * </p><p>
+     * <b>11.7.1</b> The encoding shall terminate with a "Z",
+     * as described in the ITU-T Rec. X.680 | ISO/IEC 8824-1 clause on
+     * GeneralizedTime.
+     * </p><p>
+     * <b>11.7.2</b> The seconds element shall always be present.
+     * </p><p>
+     * <b>11.7.3</b> The fractional-seconds elements, if present,
+     * shall omit all trailing zeros; if the elements correspond to 0,
+     * they shall be wholly omitted, and the decimal point element also
+     * shall be omitted.
+     * </p>
+     */
+    public class Asn1GeneralizedTime
+        : Asn1Object
+    {
+        internal class Meta : Asn1UniversalType
+        {
+            internal static readonly Asn1UniversalType Instance = new Meta();
+
+            private Meta() : base(typeof(Asn1GeneralizedTime), Asn1Tags.GeneralizedTime) { }
+
+            internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
+            {
+                return CreatePrimitive(octetString.GetOctets());
+            }
+        }
+
+        public static Asn1GeneralizedTime GetInstance(object obj)
+        {
+            if (obj == null)
+                return null;
+
+            if (obj is Asn1GeneralizedTime asn1GeneralizedTime)
+                return asn1GeneralizedTime;
+
+            if (obj is IAsn1Convertible asn1Convertible)
+            {
+                Asn1Object asn1Object = asn1Convertible.ToAsn1Object();
+                if (asn1Object is Asn1GeneralizedTime converted)
+                    return converted;
+            }
+            else if (obj is byte[] bytes)
+            {
+                try
+                {
+                    return (Asn1GeneralizedTime)Meta.Instance.FromByteArray(bytes);
+                }
+                catch (IOException e)
+                {
+                    throw new ArgumentException("failed to construct generalized time from byte[]: " + e.Message);
+                }
+            }
+
+            throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), nameof(obj));
+        }
+
+        public static Asn1GeneralizedTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+        {
+            return (Asn1GeneralizedTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
+        }
+
+        internal readonly byte[] m_contents;
+
+        public Asn1GeneralizedTime(string time)
+        {
+            m_contents = Strings.ToByteArray(time);
+
+            try
+            {
+                ToDateTime();
+            }
+            catch (FormatException e)
+            {
+                throw new ArgumentException("invalid date string: " + e.Message);
+            }
+        }
+
+        public Asn1GeneralizedTime(DateTime time)
+        {
+            DateTime utc = time.ToUniversalTime();
+            var formatStr = @"yyyyMMddHHmmss\Z";
+            var formatProvider = DateTimeFormatInfo.InvariantInfo;
+            string utcString = utc.ToString(formatStr, formatProvider);
+            m_contents = Strings.ToByteArray(utcString);
+        }
+
+        // TODO Custom locale constructor?
+        //public Asn1GeneralizedTime(DateTime time, Locale locale)
+        //{
+        //    SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss\Z", locale);
+
+        //    dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
+
+        //    this.contents = Strings.toByteArray(dateF.format(time));
+        //}
+
+        internal Asn1GeneralizedTime(byte[] bytes)
+        {
+            if (bytes == null)
+                throw new ArgumentNullException(nameof(bytes));
+            if (bytes.Length < 4)
+                throw new ArgumentException("GeneralizedTime string too short", nameof(bytes));
+
+            m_contents = bytes;
+
+            if (!(IsDigit(0) && IsDigit(1) && IsDigit(2) && IsDigit(3)))
+                throw new ArgumentException("illegal characters in GeneralizedTime string", nameof(bytes));
+        }
+
+        public string TimeString => Strings.FromByteArray(m_contents);
+
+        public string GetTime()
+        {
+            string stime = Strings.FromByteArray(m_contents);
+
+            //
+            // standardise the format.
+            //
+            if (stime[stime.Length - 1] == 'Z')
+                return stime.Substring(0, stime.Length - 1) + "GMT+00:00";
+
+            int signPos = stime.Length - 6;
+            char sign = stime[signPos];
+            if ((sign == '-' || sign == '+') && stime.IndexOf("GMT") == signPos - 3)
+            {
+                // already a GMT string!
+                return stime;
+            }
+
+            signPos = stime.Length - 5;
+            sign = stime[signPos];
+            if (sign == '-' || sign == '+')
+            {
+                return stime.Substring(0, signPos)
+                    + "GMT"
+                    + stime.Substring(signPos, 3)
+                    + ":"
+                    + stime.Substring(signPos + 3);
+            }
+
+            signPos = stime.Length - 3;
+            sign = stime[signPos];
+            if (sign == '-' || sign == '+')
+            {
+                return stime.Substring(0, signPos)
+                    + "GMT"
+                    + stime.Substring(signPos)
+                    + ":00";
+            }
+
+            return stime + CalculateGmtOffset(stime);
+        }
+
+        private string CalculateGmtOffset(string stime)
+        {
+            TimeZoneInfo timeZone = TimeZoneInfo.Local;
+            TimeSpan offset = timeZone.BaseUtcOffset;
+
+            string sign = "+";
+            if (offset.CompareTo(TimeSpan.Zero) < 0)
+            {
+                sign = "-";
+                offset = offset.Duration();
+            }
+
+            int hours = offset.Hours;
+            int minutes = offset.Minutes;
+
+            try
+            {
+                if (timeZone.SupportsDaylightSavingTime)
+                {
+                    string d = stime + "GMT" + sign + Convert(hours) + ":" + Convert(minutes);
+                    string formatStr = CalculateGmtFormatString(d);
+
+                    DateTime dateTime = ParseDateString(d, formatStr, makeUniversal: true);
+
+                    if (timeZone.IsDaylightSavingTime(dateTime))
+                    {
+                        hours += sign.Equals("+") ? 1 : -1;
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                // we'll do our best and ignore daylight savings
+            }
+
+            return "GMT" + sign + Convert(hours) + ":" + Convert(minutes);
+        }
+
+        private string CalculateGmtFormatString(string d)
+        {
+            if (HasFractionalSeconds())
+            {
+                int fCount = Platform.IndexOf(d, "GMT") - 1 - d.IndexOf('.');
+                return @"yyyyMMddHHmmss." + FString(fCount) + @"'GMT'zzz";
+            }
+
+            if (HasSeconds())
+                return @"yyyyMMddHHmmss'GMT'zzz";
+
+            if (HasMinutes())
+                return @"yyyyMMddHHmm'GMT'zzz";
+
+            return @"yyyyMMddHH'GMT'zzz";
+        }
+
+        private string Convert(int time)
+        {
+            if (time < 10)
+                return "0" + time;
+
+            return time.ToString();
+        }
+
+        public DateTime ToDateTime()
+        {
+            string formatStr;
+            string stime = Strings.FromByteArray(m_contents);
+            string d = stime;
+            bool makeUniversal = false;
+
+            if (Platform.EndsWith(stime, "Z"))
+            {
+                if (HasFractionalSeconds())
+                {
+                    int fCount = d.Length - d.IndexOf('.') - 2;
+                    formatStr = @"yyyyMMddHHmmss." + FString(fCount) + @"\Z";
+                }
+                else if (HasSeconds())
+                {
+                    formatStr = @"yyyyMMddHHmmss\Z";
+                }
+                else if (HasMinutes())
+                {
+                    formatStr = @"yyyyMMddHHmm\Z";
+                }
+                else
+                {
+                    formatStr = @"yyyyMMddHH\Z";
+                }
+            }
+            else if (stime.IndexOf('-') > 0 || stime.IndexOf('+') > 0)
+            {
+                d = GetTime();
+                formatStr = CalculateGmtFormatString(d);
+                makeUniversal = true;
+            }
+            else
+            {
+                if (HasFractionalSeconds())
+                {
+                    int fCount = d.Length - 1 - d.IndexOf('.');
+                    formatStr = @"yyyyMMddHHmmss." + FString(fCount);
+                }
+                else if (HasSeconds())
+                {
+                    formatStr = @"yyyyMMddHHmmss";
+                }
+                else if (HasMinutes())
+                {
+                    formatStr = @"yyyyMMddHHmm";
+                }
+                else
+                {
+                    formatStr = @"yyyyMMddHH";
+                }
+            }
+
+            // TODO Epoch adjustment?
+            //return DateUtil.epochAdjust(dateF.parse(d));
+            return ParseDateString(d, formatStr, makeUniversal);
+        }
+
+        protected bool HasFractionalSeconds()
+        {
+            return m_contents.Length > 14 && m_contents[14] == '.';
+        }
+
+        protected bool HasSeconds()
+        {
+            return IsDigit(12) && IsDigit(13);
+        }
+
+        protected bool HasMinutes()
+        {
+            return IsDigit(10) && IsDigit(11);
+        }
+
+        private bool IsDigit(int pos)
+        {
+            return m_contents.Length > pos && m_contents[pos] >= '0' && m_contents[pos] <= '9';
+        }
+
+        internal override IAsn1Encoding GetEncoding(int encoding)
+        {
+            if (Asn1OutputStream.EncodingDer == encoding)
+                return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, GetDerTime());
+
+            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, m_contents);
+        }
+
+        internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
+        {
+            if (Asn1OutputStream.EncodingDer == encoding)
+                return new PrimitiveEncoding(tagClass, tagNo, GetDerTime());
+
+            return new PrimitiveEncoding(tagClass, tagNo, m_contents);
+        }
+
+        protected override bool Asn1Equals(Asn1Object asn1Object)
+        {
+            if (!(asn1Object is Asn1GeneralizedTime that))
+                return false;
+
+            return Arrays.AreEqual(m_contents, that.m_contents);
+        }
+
+        protected override int Asn1GetHashCode()
+        {
+            return Arrays.GetHashCode(m_contents);
+        }
+
+        internal static Asn1GeneralizedTime CreatePrimitive(byte[] contents)
+        {
+            return new Asn1GeneralizedTime(contents);
+        }
+
+        internal byte[] GetDerTime()
+        {
+            if (m_contents[m_contents.Length - 1] != 'Z')
+            {
+                return m_contents; // TODO: is there a better way?
+            }
+
+            if (!HasMinutes())
+            {
+                byte[] derTime = new byte[m_contents.Length + 4];
+
+                Array.Copy(m_contents, 0, derTime, 0, m_contents.Length - 1);
+                Array.Copy(Strings.ToByteArray("0000Z"), 0, derTime, m_contents.Length - 1, 5);
+
+                return derTime;
+            }
+            else if (!HasSeconds())
+            {
+                byte[] derTime = new byte[m_contents.Length + 2];
+
+                Array.Copy(m_contents, 0, derTime, 0, m_contents.Length - 1);
+                Array.Copy(Strings.ToByteArray("00Z"), 0, derTime, m_contents.Length - 1, 3);
+
+                return derTime;
+            }
+            else if (HasFractionalSeconds())
+            {
+                int ind = m_contents.Length - 2;
+                while (ind > 0 && m_contents[ind] == '0')
+                {
+                    ind--;
+                }
+
+                if (m_contents[ind] == '.')
+                {
+                    byte[] derTime = new byte[ind + 1];
+
+                    Array.Copy(m_contents, 0, derTime, 0, ind);
+                    derTime[ind] = (byte)'Z';
+
+                    return derTime;
+                }
+                else
+                {
+                    byte[] derTime = new byte[ind + 2];
+
+                    Array.Copy(m_contents, 0, derTime, 0, ind + 1);
+                    derTime[ind + 1] = (byte)'Z';
+
+                    return derTime;
+                }
+            }
+            else
+            {
+                return m_contents;
+            }
+        }
+
+        private static string FString(int count)
+        {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < count; ++i)
+            {
+                sb.Append('f');
+            }
+            return sb.ToString();
+        }
+
+        private static DateTime ParseDateString(string s, string format, bool makeUniversal)
+        {
+            DateTimeStyles dateTimeStyles = DateTimeStyles.None;
+            if (Platform.EndsWith(format, "Z"))
+            {
+                dateTimeStyles |= DateTimeStyles.AdjustToUniversal;
+                dateTimeStyles |= DateTimeStyles.AssumeUniversal;
+            }
+
+            DateTime dt = DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, dateTimeStyles);
+
+            return makeUniversal ? dt.ToUniversalTime() : dt;
+        }
+    }
+}
diff --git a/crypto/src/asn1/Asn1InputStream.cs b/crypto/src/asn1/Asn1InputStream.cs
index aa91cdf62..0e772010f 100644
--- a/crypto/src/asn1/Asn1InputStream.cs
+++ b/crypto/src/asn1/Asn1InputStream.cs
@@ -426,7 +426,7 @@ namespace Org.BouncyCastle.Asn1
             case Asn1Tags.BitString:
                 return DerBitString.CreatePrimitive(bytes);
             case Asn1Tags.GeneralizedTime:
-                return DerGeneralizedTime.CreatePrimitive(bytes);
+                return Asn1GeneralizedTime.CreatePrimitive(bytes);
             case Asn1Tags.GeneralString:
                 return DerGeneralString.CreatePrimitive(bytes);
             case Asn1Tags.GraphicString:
@@ -452,7 +452,7 @@ namespace Org.BouncyCastle.Asn1
             case Asn1Tags.UniversalString:
                 return DerUniversalString.CreatePrimitive(bytes);
             case Asn1Tags.UtcTime:
-                return DerUtcTime.CreatePrimitive(bytes);
+                return Asn1UtcTime.CreatePrimitive(bytes);
             case Asn1Tags.Utf8String:
                 return DerUtf8String.CreatePrimitive(bytes);
             case Asn1Tags.VideotexString:
diff --git a/crypto/src/asn1/Asn1OutputStream.cs b/crypto/src/asn1/Asn1OutputStream.cs
index 1a69d7c2c..163e3848c 100644
--- a/crypto/src/asn1/Asn1OutputStream.cs
+++ b/crypto/src/asn1/Asn1OutputStream.cs
@@ -1,5 +1,10 @@
 using System;
 using System.IO;
+using System.Diagnostics;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers.Binary;
+using System.Numerics;
+#endif
 
 using Org.BouncyCastle.Utilities.IO;
 
@@ -73,10 +78,18 @@ namespace Org.BouncyCastle.Asn1
         {
             if (dl < 128)
             {
+                Debug.Assert(dl >= 0);
                 WriteByte((byte)dl);
                 return;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> encoding = stackalloc byte[5];
+            BinaryPrimitives.WriteUInt32BigEndian(encoding[1..], (uint)dl);
+            int leadingZeroBytes = BitOperations.LeadingZeroCount((uint)dl) / 8;
+            encoding[leadingZeroBytes] = (byte)(0x84 - leadingZeroBytes);
+            Write(encoding[leadingZeroBytes..]);
+#else
             byte[] stack = new byte[5];
             int pos = stack.Length;
 
@@ -91,6 +104,7 @@ namespace Org.BouncyCastle.Asn1
             stack[--pos] = (byte)(0x80 | count);
 
             Write(stack, pos, count + 1);
+#endif
         }
 
         internal void WriteIdentifier(int tagClass, int tagNo)
@@ -101,7 +115,11 @@ namespace Org.BouncyCastle.Asn1
                 return;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> stack = stackalloc byte[6];
+#else
             byte[] stack = new byte[6];
+#endif
             int pos = stack.Length;
 
             stack[--pos] = (byte)(tagNo & 0x7F);
@@ -113,7 +131,11 @@ namespace Org.BouncyCastle.Asn1
 
             stack[--pos] = (byte)(tagClass | 0x1F);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Write(stack[pos..]);
+#else
             Write(stack, pos, stack.Length - pos);
+#endif
         }
 
         internal static IAsn1Encoding[] GetContentsEncodings(int encoding, Asn1Encodable[] elements)
diff --git a/crypto/src/asn1/Asn1RelativeOid.cs b/crypto/src/asn1/Asn1RelativeOid.cs
index d3c031913..a1997864d 100644
--- a/crypto/src/asn1/Asn1RelativeOid.cs
+++ b/crypto/src/asn1/Asn1RelativeOid.cs
@@ -196,7 +196,11 @@ namespace Org.BouncyCastle.Asn1
 
         internal static void WriteField(Stream outputStream, long fieldValue)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> result = stackalloc byte[9];
+#else
             byte[] result = new byte[9];
+#endif
             int pos = 8;
             result[pos] = (byte)((int)fieldValue & 0x7F);
             while (fieldValue >= (1L << 7))
@@ -204,7 +208,11 @@ namespace Org.BouncyCastle.Asn1
                 fieldValue >>= 7;
                 result[--pos] = (byte)((int)fieldValue | 0x80);
             }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            outputStream.Write(result[pos..]);
+#else
             outputStream.Write(result, pos, 9 - pos);
+#endif
         }
 
         internal static void WriteField(Stream outputStream, BigInteger fieldValue)
@@ -217,14 +225,24 @@ namespace Org.BouncyCastle.Asn1
             else
             {
                 BigInteger tmpValue = fieldValue;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<byte> tmp = byteCount <= 16
+                    ? stackalloc byte[byteCount]
+                    : new byte[byteCount];
+#else
                 byte[] tmp = new byte[byteCount];
+#endif
                 for (int i = byteCount - 1; i >= 0; i--)
                 {
                     tmp[i] = (byte)(tmpValue.IntValue | 0x80);
                     tmpValue = tmpValue.ShiftRight(7);
                 }
                 tmp[byteCount - 1] &= 0x7F;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                outputStream.Write(tmp);
+#else
                 outputStream.Write(tmp, 0, tmp.Length);
+#endif
             }
         }
 
diff --git a/crypto/src/asn1/Asn1Sequence.cs b/crypto/src/asn1/Asn1Sequence.cs
index 3ceea4521..1a123e26d 100644
--- a/crypto/src/asn1/Asn1Sequence.cs
+++ b/crypto/src/asn1/Asn1Sequence.cs
@@ -195,6 +195,18 @@ namespace Org.BouncyCastle.Asn1
             get { return elements.Length; }
         }
 
+        public virtual T[] MapElements<T>(Func<Asn1Encodable, T> func)
+        {
+            // NOTE: Call Count here to 'force' a LazyDerSequence
+            int count = Count;
+            T[] result = new T[count];
+            for (int i = 0; i < count; ++i)
+            {
+                result[i] = func(elements[i]);
+            }
+            return result;
+        }
+
         public virtual Asn1Encodable[] ToArray()
         {
             return Asn1EncodableVector.CloneElements(elements);
@@ -246,26 +258,12 @@ namespace Org.BouncyCastle.Asn1
         // TODO[asn1] Preferably return an Asn1BitString[] (doesn't exist yet)
         internal DerBitString[] GetConstructedBitStrings()
         {
-            // NOTE: Call Count here to 'force' a LazyDerSequence
-            int count = Count;
-            DerBitString[] bitStrings = new DerBitString[count];
-            for (int i = 0; i < count; ++i)
-            {
-                bitStrings[i] = DerBitString.GetInstance(elements[i]);
-            }
-            return bitStrings;
+            return MapElements(DerBitString.GetInstance);
         }
 
         internal Asn1OctetString[] GetConstructedOctetStrings()
         {
-            // NOTE: Call Count here to 'force' a LazyDerSequence
-            int count = Count;
-            Asn1OctetString[] octetStrings = new Asn1OctetString[count];
-            for (int i = 0; i < count; ++i)
-            {
-                octetStrings[i] = Asn1OctetString.GetInstance(elements[i]);
-            }
-            return octetStrings;
+            return MapElements(Asn1OctetString.GetInstance);
         }
 
         // TODO[asn1] Preferably return an Asn1BitString (doesn't exist yet)
diff --git a/crypto/src/asn1/Asn1Set.cs b/crypto/src/asn1/Asn1Set.cs
index 1045b7f74..faec50eb0 100644
--- a/crypto/src/asn1/Asn1Set.cs
+++ b/crypto/src/asn1/Asn1Set.cs
@@ -162,6 +162,18 @@ namespace Org.BouncyCastle.Asn1
             get { return elements.Length; }
         }
 
+        public virtual T[] MapElements<T>(Func<Asn1Encodable, T> func)
+        {
+            // NOTE: Call Count here to 'force' a LazyDerSet
+            int count = Count;
+            T[] result = new T[count];
+            for (int i = 0; i < count; ++i)
+            {
+                result[i] = func(elements[i]);
+            }
+            return result;
+        }
+
         public virtual Asn1Encodable[] ToArray()
         {
             return Asn1EncodableVector.CloneElements(elements);
@@ -294,6 +306,10 @@ namespace Org.BouncyCastle.Asn1
                 if (a0 != b0)
                     return a0 < b0 ? -1 : 1;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                int compareLength = System.Math.Min(a.Length, b.Length) - 1;
+                return a.AsSpan(1, compareLength).SequenceCompareTo(b.AsSpan(1, compareLength));
+#else
                 int len = System.Math.Min(a.Length, b.Length);
                 for (int i = 1; i < len; ++i)
                 {
@@ -303,6 +319,7 @@ namespace Org.BouncyCastle.Asn1
                 }
                 Debug.Assert(a.Length == b.Length);
                 return 0;
+#endif
             }
         }
     }
diff --git a/crypto/src/asn1/Asn1UniversalTypes.cs b/crypto/src/asn1/Asn1UniversalTypes.cs
index 214918bcd..f5b5d0498 100644
--- a/crypto/src/asn1/Asn1UniversalTypes.cs
+++ b/crypto/src/asn1/Asn1UniversalTypes.cs
@@ -49,9 +49,9 @@ namespace Org.BouncyCastle.Asn1
             case Asn1Tags.IA5String:                // [UNIVERSAL 22] IMPLICIT OCTET STRING (encode as if)
                 return DerIA5String.Meta.Instance;
             case Asn1Tags.UtcTime:                  // [UNIVERSAL 23] IMPLICIT VisibleString (restricted values)
-                return DerUtcTime.Meta.Instance;
+                return Asn1UtcTime.Meta.Instance;
             case Asn1Tags.GeneralizedTime:          // [UNIVERSAL 24] IMPLICIT VisibleString (restricted values)
-                return DerGeneralizedTime.Meta.Instance;
+                return Asn1GeneralizedTime.Meta.Instance;
             case Asn1Tags.GraphicString:            // [UNIVERSAL 25] IMPLICIT OCTET STRING (encode as if)
                 return DerGraphicString.Meta.Instance;
             case Asn1Tags.VisibleString:            // [UNIVERSAL 26] IMPLICIT OCTET STRING (encode as if)
diff --git a/crypto/src/asn1/Asn1UtcTime.cs b/crypto/src/asn1/Asn1UtcTime.cs
new file mode 100644
index 000000000..05de430c4
--- /dev/null
+++ b/crypto/src/asn1/Asn1UtcTime.cs
@@ -0,0 +1,264 @@
+using System;
+using System.Globalization;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Asn1
+{
+    /**
+     * UTC time object.
+     */
+    public class Asn1UtcTime
+        : Asn1Object
+    {
+        internal class Meta : Asn1UniversalType
+        {
+            internal static readonly Asn1UniversalType Instance = new Meta();
+
+            private Meta() : base(typeof(Asn1UtcTime), Asn1Tags.UtcTime) {}
+
+            internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
+            {
+                return CreatePrimitive(octetString.GetOctets());
+            }
+        }
+
+		/**
+         * return a UTC Time from the passed in object.
+         *
+         * @exception ArgumentException if the object cannot be converted.
+         */
+        public static Asn1UtcTime GetInstance(object obj)
+        {
+            if (obj == null)
+                return null;
+
+            if (obj is Asn1UtcTime asn1UtcTime)
+                return asn1UtcTime;
+
+            if (obj is IAsn1Convertible asn1Convertible)
+            {
+                Asn1Object asn1Object = asn1Convertible.ToAsn1Object();
+                if (asn1Object is Asn1UtcTime converted)
+                    return converted;
+            }
+            else if (obj is byte[] bytes)
+            {
+                try
+                {
+                    return (Asn1UtcTime)Meta.Instance.FromByteArray(bytes);
+                }
+                catch (IOException e)
+                {
+                    throw new ArgumentException("failed to construct UTC time from byte[]: " + e.Message);
+                }
+            }
+
+            throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj));
+        }
+
+        /**
+         * return a UTC Time from a tagged object.
+         *
+         * @param taggedObject the tagged object holding the object we want
+         * @param declaredExplicit true if the object is meant to be explicitly tagged false otherwise.
+         * @exception ArgumentException if the tagged object cannot be converted.
+         */
+        public static Asn1UtcTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+        {
+            return (Asn1UtcTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
+        }
+
+        private readonly string time;
+
+        /**
+         * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
+         * never encoded. When you're creating one of these objects from scratch, that's
+         * what you want to use, otherwise we'll try to deal with whatever Gets read from
+         * the input stream... (this is why the input format is different from the GetTime()
+         * method output).
+         * <p>
+         * @param time the time string.</p>
+         */
+        public Asn1UtcTime(string time)
+        {
+			if (time == null)
+				throw new ArgumentNullException("time");
+
+			this.time = time;
+
+			try
+			{
+				ToDateTime();
+			}
+			catch (FormatException e)
+			{
+				throw new ArgumentException("invalid date string: " + e.Message);
+			}
+        }
+
+		/**
+         * base constructor from a DateTime object
+         */
+        public Asn1UtcTime(DateTime time)
+        {
+            this.time = time.ToUniversalTime().ToString("yyMMddHHmmss", CultureInfo.InvariantCulture) + "Z";
+        }
+
+		internal Asn1UtcTime(byte[] contents)
+        {
+            //
+            // explicitly convert to characters
+            //
+            this.time = Strings.FromAsciiByteArray(contents);
+        }
+
+		/**
+		 * return the time as a date based on whatever a 2 digit year will return. For
+		 * standardised processing use ToAdjustedDateTime().
+		 *
+		 * @return the resulting date
+		 * @exception ParseException if the date string cannot be parsed.
+		 */
+		public DateTime ToDateTime()
+		{
+			return ParseDateString(TimeString, @"yyMMddHHmmss'GMT'zzz");
+		}
+
+		/**
+		* return the time as an adjusted date
+		* in the range of 1950 - 2049.
+		*
+		* @return a date in the range of 1950 to 2049.
+		* @exception ParseException if the date string cannot be parsed.
+		*/
+		public DateTime ToAdjustedDateTime()
+		{
+			return ParseDateString(AdjustedTimeString, @"yyyyMMddHHmmss'GMT'zzz");
+		}
+
+		private DateTime ParseDateString(string dateStr, string formatStr)
+		{
+			DateTime dt = DateTime.ParseExact(
+				dateStr,
+				formatStr,
+				DateTimeFormatInfo.InvariantInfo);
+
+			return dt.ToUniversalTime();
+		}
+
+		/**
+         * return the time - always in the form of
+         *  YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
+         * <p>
+         * Normally in a certificate we would expect "Z" rather than "GMT",
+         * however adding the "GMT" means we can just use:
+         * <pre>
+         *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
+         * </pre>
+         * To read in the time and Get a date which is compatible with our local
+         * time zone.</p>
+         * <p>
+         * <b>Note:</b> In some cases, due to the local date processing, this
+         * may lead to unexpected results. If you want to stick the normal
+         * convention of 1950 to 2049 use the GetAdjustedTime() method.</p>
+         */
+        public string TimeString
+        {
+			get
+			{
+				//
+				// standardise the format.
+				//
+				if (time.IndexOf('-') < 0 && time.IndexOf('+') < 0)
+				{
+					if (time.Length == 11)
+					{
+						return time.Substring(0, 10) + "00GMT+00:00";
+					}
+					else
+					{
+						return time.Substring(0, 12) + "GMT+00:00";
+					}
+				}
+				else
+				{
+					int index = time.IndexOf('-');
+					if (index < 0)
+					{
+						index = time.IndexOf('+');
+					}
+					string d = time;
+
+					if (index == time.Length - 3)
+					{
+						d += "00";
+					}
+
+					if (index == 10)
+					{
+						return d.Substring(0, 10) + "00GMT" + d.Substring(10, 3) + ":" + d.Substring(13, 2);
+					}
+					else
+					{
+						return d.Substring(0, 12) + "GMT" + d.Substring(12, 3) + ":" +  d.Substring(15, 2);
+					}
+				}
+			}
+        }
+
+		/// <summary>
+		/// Return a time string as an adjusted date with a 4 digit year.
+		/// This goes in the range of 1950 - 2049.
+		/// </summary>
+		public string AdjustedTimeString
+		{
+			get
+			{
+				string d = TimeString;
+				string c = d[0] < '5' ? "20" : "19";
+
+				return c + d;
+			}
+		}
+
+        internal byte[] GetOctets()
+        {
+            return Strings.ToAsciiByteArray(time);
+        }
+
+        internal override IAsn1Encoding GetEncoding(int encoding)
+        {
+            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.UtcTime, GetOctets());
+        }
+
+        internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
+        {
+            return new PrimitiveEncoding(tagClass, tagNo, GetOctets());
+        }
+
+        protected override bool Asn1Equals(Asn1Object asn1Object)
+		{
+            if (!(asn1Object is Asn1UtcTime that))
+                return false;
+
+            return this.time == that.time;
+        }
+
+		protected override int Asn1GetHashCode()
+		{
+            return time.GetHashCode();
+        }
+
+		public override string ToString()
+		{
+			return time;
+		}
+
+        internal static Asn1UtcTime CreatePrimitive(byte[] contents)
+        {
+            return new Asn1UtcTime(contents);
+        }
+    }
+}
diff --git a/crypto/src/asn1/BEROctetStringGenerator.cs b/crypto/src/asn1/BEROctetStringGenerator.cs
index 7893139d6..b07576e7d 100644
--- a/crypto/src/asn1/BEROctetStringGenerator.cs
+++ b/crypto/src/asn1/BEROctetStringGenerator.cs
@@ -64,6 +64,9 @@ namespace Org.BouncyCastle.Asn1
 			{
 				Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Write(buffer.AsSpan(offset, count));
+#else
                 int bufLen = _buf.Length;
                 int available = bufLen - _off;
                 if (count < available)
@@ -77,8 +80,9 @@ namespace Org.BouncyCastle.Asn1
                 if (_off > 0)
                 {
                     Array.Copy(buffer, offset, _buf, _off, available);
-                    pos += available;
+                    pos = available;
                     DerOctetString.Encode(_derOut, _buf, 0, bufLen);
+                    //_off = 0;
                 }
 
                 int remaining;
@@ -90,9 +94,40 @@ namespace Org.BouncyCastle.Asn1
 
                 Array.Copy(buffer, offset + pos, _buf, 0, remaining);
                 this._off = remaining;
+#endif
+            }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void Write(ReadOnlySpan<byte> buffer)
+			{
+                int bufLen = _buf.Length;
+                int available = bufLen - _off;
+                if (buffer.Length < available)
+                {
+                    buffer.CopyTo(_buf.AsSpan(_off));
+                    _off += buffer.Length;
+                    return;
+                }
+
+                if (_off > 0)
+                {
+                    DerOctetString.Encode(_derOut, _buf.AsSpan(0, _off), buffer[..available]);
+                    buffer = buffer[available..];
+                    //_off = 0;
+                }
+
+                while (buffer.Length >= bufLen)
+                {
+                    DerOctetString.Encode(_derOut, buffer[..bufLen]);
+                    buffer = buffer[bufLen..];
+                }
+
+                buffer.CopyTo(_buf.AsSpan());
+                _off = buffer.Length;
             }
+#endif
 
-			public override void WriteByte(byte value)
+            public override void WriteByte(byte value)
 			{
 				_buf[_off++] = value;
 
@@ -103,33 +138,20 @@ namespace Org.BouncyCastle.Asn1
 				}
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
                 {
-                    ImplClose();
-                }
-                base.Dispose(disposing);
-            }
-#else
-            public override void Close()
-            {
-                ImplClose();
-                base.Close();
-            }
-#endif
-
-            private void ImplClose()
-            {
-                if (_off != 0)
-                {
-                    DerOctetString.Encode(_derOut, _buf, 0, _off);
-                }
+                    if (_off != 0)
+                    {
+                        DerOctetString.Encode(_derOut, _buf, 0, _off);
+                    }
 
-                _derOut.FlushInternal();
+                    _derOut.FlushInternal();
 
-                _gen.WriteBerEnd();
+                    _gen.WriteBerEnd();
+                }
+                base.Dispose(disposing);
             }
         }
     }
diff --git a/crypto/src/asn1/ConstructedBitStream.cs b/crypto/src/asn1/ConstructedBitStream.cs
index 49f54fc1b..f089dac75 100644
--- a/crypto/src/asn1/ConstructedBitStream.cs
+++ b/crypto/src/asn1/ConstructedBitStream.cs
@@ -33,6 +33,9 @@ namespace Org.BouncyCastle.Asn1
         {
             Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Read(buffer.AsSpan(offset, count));
+#else
             if (count < 1)
                 return 0;
 
@@ -75,8 +78,57 @@ namespace Org.BouncyCastle.Asn1
                     m_currentStream = m_currentParser.GetBitStream();
                 }
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            if (buffer.IsEmpty)
+                return 0;
+
+            if (m_currentStream == null)
+            {
+                if (!m_first)
+                    return 0;
+
+                m_currentParser = GetNextParser();
+                if (m_currentParser == null)
+                    return 0;
+
+                m_first = false;
+                m_currentStream = m_currentParser.GetBitStream();
+            }
+
+            int totalRead = 0;
+
+            for (;;)
+            {
+                int numRead = m_currentStream.Read(buffer[totalRead..]);
+
+                if (numRead > 0)
+                {
+                    totalRead += numRead;
+
+                    if (totalRead == buffer.Length)
+                        return totalRead;
+                }
+                else
+                {
+                    m_padBits = m_currentParser.PadBits;
+                    m_currentParser = GetNextParser();
+                    if (m_currentParser == null)
+                    {
+                        m_currentStream = null;
+                        return totalRead;
+                    }
+
+                    m_currentStream = m_currentParser.GetBitStream();
+                }
+            }
+        }
+#endif
+
         public override int ReadByte()
         {
             if (m_currentStream == null)
diff --git a/crypto/src/asn1/ConstructedOctetStream.cs b/crypto/src/asn1/ConstructedOctetStream.cs
index 12aa14e74..d005f9fe7 100644
--- a/crypto/src/asn1/ConstructedOctetStream.cs
+++ b/crypto/src/asn1/ConstructedOctetStream.cs
@@ -1,3 +1,4 @@
+using System;
 using System.IO;
 
 using Org.BouncyCastle.Utilities;
@@ -22,6 +23,9 @@ namespace Org.BouncyCastle.Asn1
 		{
 			Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return Read(buffer.AsSpan(offset, count));
+#else
 			if (count < 1)
                 return 0;
 
@@ -63,8 +67,56 @@ namespace Org.BouncyCastle.Asn1
 					m_currentStream = next.GetOctetStream();
 				}
 			}
+#endif
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int Read(Span<byte> buffer)
+		{
+			if (buffer.IsEmpty)
+                return 0;
+
+			if (m_currentStream == null)
+			{
+				if (!m_first)
+					return 0;
+
+                Asn1OctetStringParser next = GetNextParser();
+                if (next == null)
+                    return 0;
+
+				m_first = false;
+				m_currentStream = next.GetOctetStream();
+			}
+
+			int totalRead = 0;
+
+			for (;;)
+			{
+				int numRead = m_currentStream.Read(buffer[totalRead..]);
+
+				if (numRead > 0)
+				{
+					totalRead += numRead;
+
+					if (totalRead == buffer.Length)
+						return totalRead;
+				}
+				else
+				{
+                    Asn1OctetStringParser next = GetNextParser();
+                    if (next == null)
+					{
+						m_currentStream = null;
+						return totalRead;
+					}
+
+					m_currentStream = next.GetOctetStream();
+				}
+			}
+		}
+#endif
+
 		public override int ReadByte()
 		{
 			if (m_currentStream == null)
diff --git a/crypto/src/asn1/DefiniteLengthInputStream.cs b/crypto/src/asn1/DefiniteLengthInputStream.cs
index ed5bd2446..89f0d5a62 100644
--- a/crypto/src/asn1/DefiniteLengthInputStream.cs
+++ b/crypto/src/asn1/DefiniteLengthInputStream.cs
@@ -79,6 +79,27 @@ namespace Org.BouncyCastle.Asn1
             return numRead;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            if (_remaining == 0)
+                return 0;
+
+            int toRead = System.Math.Min(buffer.Length, _remaining);
+            int numRead = _in.Read(buffer[..toRead]);
+
+            if (numRead < 1)
+                throw new EndOfStreamException("DEF length " + _originalLength + " object truncated by " + _remaining);
+
+            if ((_remaining -= numRead) == 0)
+            {
+                SetParentEofDetect();
+            }
+
+            return numRead;
+        }
+#endif
+
         internal void ReadAllIntoByteArray(byte[] buf)
         {
             if (_remaining != buf.Length)
diff --git a/crypto/src/asn1/DerGeneralizedTime.cs b/crypto/src/asn1/DerGeneralizedTime.cs
index 16774cb02..0386ecb02 100644
--- a/crypto/src/asn1/DerGeneralizedTime.cs
+++ b/crypto/src/asn1/DerGeneralizedTime.cs
@@ -1,326 +1,33 @@
 using System;
-using System.Globalization;
-using System.IO;
-using System.Text;
-
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1
 {
-    /**
-     * Generalized time object.
-     */
     public class DerGeneralizedTime
-        : Asn1Object
+        : Asn1GeneralizedTime
     {
-        internal class Meta : Asn1UniversalType
-        {
-            internal static readonly Asn1UniversalType Instance = new Meta();
-
-            private Meta() : base(typeof(DerGeneralizedTime), Asn1Tags.GeneralizedTime) {}
-
-            internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
-            {
-                return CreatePrimitive(octetString.GetOctets());
-            }
-        }
-
-        /**
-         * return a generalized time from the passed in object
-         *
-         * @exception ArgumentException if the object cannot be converted.
-         */
-        public static DerGeneralizedTime GetInstance(object obj)
-        {
-            if (obj == null || obj is DerGeneralizedTime)
-            {
-                return (DerGeneralizedTime)obj;
-            }
-            else if (obj is IAsn1Convertible)
-            {
-                Asn1Object asn1Object = ((IAsn1Convertible)obj).ToAsn1Object();
-                if (asn1Object is DerGeneralizedTime)
-                    return (DerGeneralizedTime)asn1Object;
-            }
-            else if (obj is byte[])
-            {
-                try
-                {
-                    return (DerGeneralizedTime)Meta.Instance.FromByteArray((byte[])obj);
-                }
-                catch (IOException e)
-                {
-                    throw new ArgumentException("failed to construct generalized time from byte[]: " + e.Message);
-                }
-            }
-
-            throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj");
-        }
-
-        /**
-         * return a generalized Time object from a tagged object.
-         *
-         * @param taggedObject the tagged object holding the object we want
-         * @param declaredExplicit true if the object is meant to be explicitly tagged false otherwise.
-         * @exception ArgumentException if the tagged object cannot be converted.
-         */
-        public static DerGeneralizedTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
-        {
-            return (DerGeneralizedTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
-        }
-
-        private readonly string time;
-
-        /**
-         * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z
-         * for local time, or Z+-HHMM on the end, for difference between local
-         * time and UTC time. The fractional second amount f must consist of at
-         * least one number with trailing zeroes removed.
-         *
-         * @param time the time string.
-         * @exception ArgumentException if string is an illegal format.
-         */
-        public DerGeneralizedTime(
-            string time)
+        public DerGeneralizedTime(byte[] time)
+            : base(time)
         {
-            this.time = time;
-
-            try
-            {
-                ToDateTime();
-            }
-            catch (FormatException e)
-            {
-                throw new ArgumentException("invalid date string: " + e.Message);
-            }
         }
 
-        /**
-         * base constructor from a local time object
-         */
         public DerGeneralizedTime(DateTime time)
+            : base(time)
         {
-            this.time = time.ToUniversalTime().ToString(@"yyyyMMddHHmmss\Z");
-        }
-
-        internal DerGeneralizedTime(
-            byte[] bytes)
-        {
-            //
-            // explicitly convert to characters
-            //
-            this.time = Strings.FromAsciiByteArray(bytes);
-        }
-
-        /**
-         * Return the time.
-         * @return The time string as it appeared in the encoded object.
-         */
-        public string TimeString
-        {
-            get { return time; }
-        }
-
-        /**
-         * return the time - always in the form of
-         *  YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
-         * <p>
-         * Normally in a certificate we would expect "Z" rather than "GMT",
-         * however adding the "GMT" means we can just use:
-         * <pre>
-         *     dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
-         * </pre>
-         * To read in the time and Get a date which is compatible with our local
-         * time zone.</p>
-         */
-        public string GetTime()
-        {
-            //
-            // standardise the format.
-            //
-            if (time[time.Length - 1] == 'Z')
-            {
-                return time.Substring(0, time.Length - 1) + "GMT+00:00";
-            }
-            else
-            {
-                int signPos = time.Length - 5;
-                char sign = time[signPos];
-                if (sign == '-' || sign == '+')
-                {
-                    return time.Substring(0, signPos)
-                        + "GMT"
-                        + time.Substring(signPos, 3)
-                        + ":"
-                        + time.Substring(signPos + 3);
-                }
-                else
-                {
-                    signPos = time.Length - 3;
-                    sign = time[signPos];
-                    if (sign == '-' || sign == '+')
-                    {
-                        return time.Substring(0, signPos)
-                            + "GMT"
-                            + time.Substring(signPos)
-                            + ":00";
-                    }
-                }
-            }
-
-            return time + CalculateGmtOffset();
-        }
-
-        private string CalculateGmtOffset()
-        {
-            char sign = '+';
-            DateTime time = ToDateTime();
-
-            TimeSpan offset = TimeZoneInfo.Local.GetUtcOffset(time);
-            if (offset.CompareTo(TimeSpan.Zero) < 0)
-            {
-                sign = '-';
-                offset = offset.Duration();
-            }
-            int hours = offset.Hours;
-            int minutes = offset.Minutes;
-
-            return "GMT" + sign + Convert(hours) + ":" + Convert(minutes);
-        }
-
-        private static string Convert(
-            int time)
-        {
-            if (time < 10)
-            {
-                return "0" + time;
-            }
-
-            return time.ToString();
-        }
-
-        public DateTime ToDateTime()
-        {
-            string formatStr;
-            string d = time;
-            bool makeUniversal = false;
-
-            if (Platform.EndsWith(d, "Z"))
-            {
-                if (HasFractionalSeconds)
-                {
-                    int fCount = d.Length - d.IndexOf('.') - 2;
-                    formatStr = @"yyyyMMddHHmmss." + FString(fCount) + @"\Z";
-                }
-                else
-                {
-                    formatStr = @"yyyyMMddHHmmss\Z";
-                }
-            }
-            else if (time.IndexOf('-') > 0 || time.IndexOf('+') > 0)
-            {
-                d = GetTime();
-                makeUniversal = true;
-
-                if (HasFractionalSeconds)
-                {
-                    int fCount = Platform.IndexOf(d, "GMT") - 1 - d.IndexOf('.');
-                    formatStr = @"yyyyMMddHHmmss." + FString(fCount) + @"'GMT'zzz";
-                }
-                else
-                {
-                    formatStr = @"yyyyMMddHHmmss'GMT'zzz";
-                }
-            }
-            else
-            {
-                if (HasFractionalSeconds)
-                {
-                    int fCount = d.Length - 1 - d.IndexOf('.');
-                    formatStr = @"yyyyMMddHHmmss." + FString(fCount);
-                }
-                else
-                {
-                    formatStr = @"yyyyMMddHHmmss";
-                }
-
-                // TODO?
-//				dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID()));
-            }
-
-            return ParseDateString(d, formatStr, makeUniversal);
-        }
-
-        private string FString(
-            int count)
-        {
-            StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < count; ++i)
-            {
-                sb.Append('f');
-            }
-            return sb.ToString();
         }
 
-        private DateTime ParseDateString(string	s, string format, bool makeUniversal)
+        public DerGeneralizedTime(string time)
+            : base(time)
         {
-            /*
-             * NOTE: DateTime.Kind and DateTimeStyles.AssumeUniversal not available in .NET 1.1
-             */
-            DateTimeStyles style = DateTimeStyles.None;
-            if (Platform.EndsWith(format, "Z"))
-            {
-                try
-                {
-                    style = (DateTimeStyles)Enums.GetEnumValue(typeof(DateTimeStyles), "AssumeUniversal");
-                }
-                catch (Exception)
-                {
-                }
-
-                style |= DateTimeStyles.AdjustToUniversal;
-            }
-
-            DateTime dt = DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, style);
-
-            return makeUniversal ? dt.ToUniversalTime() : dt;
-        }
-
-        private bool HasFractionalSeconds
-        {
-            get { return time.IndexOf('.') == 14; }
-        }
-
-        private byte[] GetOctets()
-        {
-            return Strings.ToAsciiByteArray(time);
         }
 
         internal override IAsn1Encoding GetEncoding(int encoding)
         {
-            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, GetOctets());
+            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, GetDerTime());
         }
 
         internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
         {
-            return new PrimitiveEncoding(tagClass, tagNo, GetOctets());
-        }
-
-        protected override bool Asn1Equals(Asn1Object asn1Object)
-        {
-            DerGeneralizedTime that = asn1Object as DerGeneralizedTime;
-            return null != that
-                && this.time.Equals(that.time);
-        }
-
-        protected override int Asn1GetHashCode()
-        {
-            return time.GetHashCode();
-        }
-
-        internal static DerGeneralizedTime CreatePrimitive(byte[] contents)
-        {
-            return new DerGeneralizedTime(contents);
+            return new PrimitiveEncoding(tagClass, tagNo, GetDerTime());
         }
     }
 }
diff --git a/crypto/src/asn1/DerOctetString.cs b/crypto/src/asn1/DerOctetString.cs
index d9913f065..ea13765ec 100644
--- a/crypto/src/asn1/DerOctetString.cs
+++ b/crypto/src/asn1/DerOctetString.cs
@@ -37,5 +37,22 @@ namespace Org.BouncyCastle.Asn1
             asn1Out.WriteDL(len);
             asn1Out.Write(buf, off, len);
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void Encode(Asn1OutputStream asn1Out, ReadOnlySpan<byte> buf)
+        {
+            asn1Out.WriteIdentifier(Asn1Tags.Universal, Asn1Tags.OctetString);
+            asn1Out.WriteDL(buf.Length);
+            asn1Out.Write(buf);
+        }
+
+        internal static void Encode(Asn1OutputStream asn1Out, ReadOnlySpan<byte> buf1, ReadOnlySpan<byte> buf2)
+        {
+            asn1Out.WriteIdentifier(Asn1Tags.Universal, Asn1Tags.OctetString);
+            asn1Out.WriteDL(buf1.Length + buf2.Length);
+            asn1Out.Write(buf1);
+            asn1Out.Write(buf2);
+        }
+#endif
     }
 }
diff --git a/crypto/src/asn1/DerUTCTime.cs b/crypto/src/asn1/DerUTCTime.cs
index 7f7756d49..089285367 100644
--- a/crypto/src/asn1/DerUTCTime.cs
+++ b/crypto/src/asn1/DerUTCTime.cs
@@ -1,274 +1,25 @@
 using System;
-using System.Globalization;
-using System.IO;
-
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1
 {
-    /**
-     * UTC time object.
-     */
     public class DerUtcTime
-        : Asn1Object
+        : Asn1UtcTime
     {
-        internal class Meta : Asn1UniversalType
-        {
-            internal static readonly Asn1UniversalType Instance = new Meta();
-
-            private Meta() : base(typeof(DerUtcTime), Asn1Tags.UtcTime) {}
-
-            internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
-            {
-                return CreatePrimitive(octetString.GetOctets());
-            }
-        }
-
-		/**
-         * return a UTC Time from the passed in object.
-         *
-         * @exception ArgumentException if the object cannot be converted.
-         */
-        public static DerUtcTime GetInstance(object obj)
-        {
-            if (obj == null || obj is DerUtcTime)
-            {
-                return (DerUtcTime)obj;
-            }
-            else if (obj is IAsn1Convertible)
-            {
-                Asn1Object asn1Object = ((IAsn1Convertible)obj).ToAsn1Object();
-                if (asn1Object is DerUtcTime)
-                    return (DerUtcTime)asn1Object;
-            }
-            else if (obj is byte[])
-            {
-                try
-                {
-                    return (DerUtcTime)Meta.Instance.FromByteArray((byte[])obj);
-                }
-                catch (IOException e)
-                {
-                    throw new ArgumentException("failed to construct UTC time from byte[]: " + e.Message);
-                }
-            }
-
-            throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj));
-        }
-
-        /**
-         * return a UTC Time from a tagged object.
-         *
-         * @param taggedObject the tagged object holding the object we want
-         * @param declaredExplicit true if the object is meant to be explicitly tagged false otherwise.
-         * @exception ArgumentException if the tagged object cannot be converted.
-         */
-        public static DerUtcTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
-        {
-            return (DerUtcTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
-        }
-
-        private readonly string time;
-
-        /**
-         * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
-         * never encoded. When you're creating one of these objects from scratch, that's
-         * what you want to use, otherwise we'll try to deal with whatever Gets read from
-         * the input stream... (this is why the input format is different from the GetTime()
-         * method output).
-         * <p>
-         * @param time the time string.</p>
-         */
         public DerUtcTime(string time)
+			: base(time)
         {
-			if (time == null)
-				throw new ArgumentNullException("time");
-
-			this.time = time;
-
-			try
-			{
-				ToDateTime();
-			}
-			catch (FormatException e)
-			{
-				throw new ArgumentException("invalid date string: " + e.Message);
-			}
         }
 
-		/**
-         * base constructor from a DateTime object
-         */
         public DerUtcTime(DateTime time)
+			: base(time)
         {
-            this.time = time.ToUniversalTime().ToString("yyMMddHHmmss", CultureInfo.InvariantCulture) + "Z";
         }
 
 		internal DerUtcTime(byte[] contents)
+			: base(contents)
         {
-            //
-            // explicitly convert to characters
-            //
-            this.time = Strings.FromAsciiByteArray(contents);
-        }
-
-//		public DateTime ToDateTime()
-//		{
-//			string tm = this.AdjustedTimeString;
-//
-//			return new DateTime(
-//				Int16.Parse(tm.Substring(0, 4)),
-//				Int16.Parse(tm.Substring(4, 2)),
-//				Int16.Parse(tm.Substring(6, 2)),
-//				Int16.Parse(tm.Substring(8, 2)),
-//				Int16.Parse(tm.Substring(10, 2)),
-//				Int16.Parse(tm.Substring(12, 2)));
-//		}
-
-		/**
-		 * return the time as a date based on whatever a 2 digit year will return. For
-		 * standardised processing use ToAdjustedDateTime().
-		 *
-		 * @return the resulting date
-		 * @exception ParseException if the date string cannot be parsed.
-		 */
-		public DateTime ToDateTime()
-		{
-			return ParseDateString(TimeString, @"yyMMddHHmmss'GMT'zzz");
-		}
-
-		/**
-		* return the time as an adjusted date
-		* in the range of 1950 - 2049.
-		*
-		* @return a date in the range of 1950 to 2049.
-		* @exception ParseException if the date string cannot be parsed.
-		*/
-		public DateTime ToAdjustedDateTime()
-		{
-			return ParseDateString(AdjustedTimeString, @"yyyyMMddHHmmss'GMT'zzz");
-		}
-
-		private DateTime ParseDateString(string dateStr, string formatStr)
-		{
-			DateTime dt = DateTime.ParseExact(
-				dateStr,
-				formatStr,
-				DateTimeFormatInfo.InvariantInfo);
-
-			return dt.ToUniversalTime();
-		}
-
-		/**
-         * return the time - always in the form of
-         *  YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
-         * <p>
-         * Normally in a certificate we would expect "Z" rather than "GMT",
-         * however adding the "GMT" means we can just use:
-         * <pre>
-         *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
-         * </pre>
-         * To read in the time and Get a date which is compatible with our local
-         * time zone.</p>
-         * <p>
-         * <b>Note:</b> In some cases, due to the local date processing, this
-         * may lead to unexpected results. If you want to stick the normal
-         * convention of 1950 to 2049 use the GetAdjustedTime() method.</p>
-         */
-        public string TimeString
-        {
-			get
-			{
-				//
-				// standardise the format.
-				//
-				if (time.IndexOf('-') < 0 && time.IndexOf('+') < 0)
-				{
-					if (time.Length == 11)
-					{
-						return time.Substring(0, 10) + "00GMT+00:00";
-					}
-					else
-					{
-						return time.Substring(0, 12) + "GMT+00:00";
-					}
-				}
-				else
-				{
-					int index = time.IndexOf('-');
-					if (index < 0)
-					{
-						index = time.IndexOf('+');
-					}
-					string d = time;
-
-					if (index == time.Length - 3)
-					{
-						d += "00";
-					}
-
-					if (index == 10)
-					{
-						return d.Substring(0, 10) + "00GMT" + d.Substring(10, 3) + ":" + d.Substring(13, 2);
-					}
-					else
-					{
-						return d.Substring(0, 12) + "GMT" + d.Substring(12, 3) + ":" +  d.Substring(15, 2);
-					}
-				}
-			}
-        }
-
-		/// <summary>
-		/// Return a time string as an adjusted date with a 4 digit year.
-		/// This goes in the range of 1950 - 2049.
-		/// </summary>
-		public string AdjustedTimeString
-		{
-			get
-			{
-				string d = TimeString;
-				string c = d[0] < '5' ? "20" : "19";
-
-				return c + d;
-			}
-		}
-
-        private byte[] GetOctets()
-        {
-            return Strings.ToAsciiByteArray(time);
-        }
-
-        internal override IAsn1Encoding GetEncoding(int encoding)
-        {
-            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.UtcTime, GetOctets());
-        }
-
-        internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
-        {
-            return new PrimitiveEncoding(tagClass, tagNo, GetOctets());
-        }
-
-        protected override bool Asn1Equals(Asn1Object asn1Object)
-		{
-			DerUtcTime that = asn1Object as DerUtcTime;
-            return null != that
-                && this.time.Equals(that.time);
         }
 
-		protected override int Asn1GetHashCode()
-		{
-            return time.GetHashCode();
-        }
-
-		public override string ToString()
-		{
-			return time;
-		}
-
-        internal static DerUtcTime CreatePrimitive(byte[] contents)
-        {
-            return new DerUtcTime(contents);
-        }
+        // TODO: create proper DER encoding.
     }
 }
diff --git a/crypto/src/asn1/IndefiniteLengthInputStream.cs b/crypto/src/asn1/IndefiniteLengthInputStream.cs
index 1c8bd9a15..e192e9e8b 100644
--- a/crypto/src/asn1/IndefiniteLengthInputStream.cs
+++ b/crypto/src/asn1/IndefiniteLengthInputStream.cs
@@ -57,7 +57,28 @@ namespace Org.BouncyCastle.Asn1
 			return numRead + 1;
 		}
 
-		public override int ReadByte()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            // Only use this optimisation if we aren't checking for 00
+            if (_eofOn00 || buffer.Length <= 1)
+                return base.Read(buffer);
+
+            if (_lookAhead < 0)
+                return 0;
+
+            int numRead = _in.Read(buffer[1..]);
+            if (numRead <= 0)
+                throw new EndOfStreamException();
+
+            buffer[0] = (byte)_lookAhead;
+            _lookAhead = RequireByte();
+
+            return numRead + 1;
+        }
+#endif
+
+        public override int ReadByte()
 		{
             if (_eofOn00 && _lookAhead <= 0)
             {
diff --git a/crypto/src/asn1/bc/BCObjectIdentifiers.cs b/crypto/src/asn1/bc/BCObjectIdentifiers.cs
index a649e0c73..d526980e5 100644
--- a/crypto/src/asn1/bc/BCObjectIdentifiers.cs
+++ b/crypto/src/asn1/bc/BCObjectIdentifiers.cs
@@ -116,6 +116,27 @@ namespace Org.BouncyCastle.Asn1.BC
         public static readonly DerObjectIdentifier picnicl1full = picnic.Branch("10");
         public static readonly DerObjectIdentifier picnicl3full = picnic.Branch("11");
         public static readonly DerObjectIdentifier picnicl5full = picnic.Branch("12");
+        
+        /*
+         * Falcon
+         */
+        public static readonly DerObjectIdentifier falcon = bc_sig.Branch("7");
+
+        public static readonly DerObjectIdentifier falcon_512 = new DerObjectIdentifier("1.3.9999.3.1");  // falcon.branch("1");
+        public static readonly DerObjectIdentifier falcon_1024 =  new DerObjectIdentifier("1.3.9999.3.4"); // falcon.branch("2");
+
+        /*
+         * Dilithium
+         */
+        public static readonly DerObjectIdentifier dilithium = bc_sig.Branch("8");
+
+        // OpenSSL OIDs
+        public static readonly DerObjectIdentifier dilithium2 = new DerObjectIdentifier("1.3.6.1.4.1.2.267.7.4.4"); // dilithium.branch("1");
+        public static readonly DerObjectIdentifier dilithium3 = new DerObjectIdentifier("1.3.6.1.4.1.2.267.7.6.5"); // dilithium.branch("2");
+        public static readonly DerObjectIdentifier dilithium5 = new DerObjectIdentifier("1.3.6.1.4.1.2.267.7.8.7"); // dilithium.branch("3");
+        public static readonly DerObjectIdentifier dilithium2_aes = new DerObjectIdentifier("1.3.6.1.4.1.2.267.11.4.4"); // dilithium.branch("4");
+        public static readonly DerObjectIdentifier dilithium3_aes = new DerObjectIdentifier("1.3.6.1.4.1.2.267.11.6.5"); // dilithium.branch("5");
+        public static readonly DerObjectIdentifier dilithium5_aes = new DerObjectIdentifier("1.3.6.1.4.1.2.267.11.8.7"); // dilithium.branch("6");
 
 
         /**
@@ -171,7 +192,16 @@ namespace Org.BouncyCastle.Asn1.BC
         public static readonly DerObjectIdentifier lightsaberkem256r3 = pqc_kem_saber.Branch("7");
         public static readonly DerObjectIdentifier saberkem256r3 = pqc_kem_saber.Branch("8");
         public static readonly DerObjectIdentifier firesaberkem256r3 = pqc_kem_saber.Branch("9");
-        
+        public static readonly DerObjectIdentifier ulightsaberkemr3 = pqc_kem_saber.Branch("10");
+        public static readonly DerObjectIdentifier usaberkemr3 = pqc_kem_saber.Branch("11");
+        public static readonly DerObjectIdentifier ufiresaberkemr3 = pqc_kem_saber.Branch("12");
+        public static readonly DerObjectIdentifier lightsaberkem90sr3 = pqc_kem_saber.Branch("13");
+        public static readonly DerObjectIdentifier saberkem90sr3 = pqc_kem_saber.Branch("14");
+        public static readonly DerObjectIdentifier firesaberkem90sr3 = pqc_kem_saber.Branch("15");
+        public static readonly DerObjectIdentifier ulightsaberkem90sr3 = pqc_kem_saber.Branch("16");
+        public static readonly DerObjectIdentifier usaberkem90sr3 = pqc_kem_saber.Branch("17");
+        public static readonly DerObjectIdentifier ufiresaberkem90sr3 = pqc_kem_saber.Branch("18");
+
         /**
          * SIKE
          */
@@ -184,5 +214,35 @@ namespace Org.BouncyCastle.Asn1.BC
         public static readonly DerObjectIdentifier sikep503_compressed = pqc_kem_sike.Branch("6");
         public static readonly DerObjectIdentifier sikep610_compressed = pqc_kem_sike.Branch("7");
         public static readonly DerObjectIdentifier sikep751_compressed = pqc_kem_sike.Branch("8");
-	}
+        
+        /**
+         * Kyber
+         */
+        public static readonly DerObjectIdentifier pqc_kem_kyber = bc_kem.Branch("6");
+
+        public static readonly DerObjectIdentifier kyber512 = new DerObjectIdentifier("1.3.6.1.4.1.2.267.8.2.2"); // pqc_kem_kyber.Branch("1");
+        public static readonly DerObjectIdentifier kyber768 = new DerObjectIdentifier("1.3.6.1.4.1.2.267.8.3.3"); // pqc_kem_kyber.Branch("2");
+        public static readonly DerObjectIdentifier kyber1024 = new DerObjectIdentifier("1.3.6.1.4.1.2.267.8.4.4"); // pqc_kem_kyber.Branch("3");
+        public static readonly DerObjectIdentifier kyber512_aes = new DerObjectIdentifier("1.3.6.1.4.1.2.267.10.2.2"); // pqc_kem_kyber.Branch("4");
+        public static readonly DerObjectIdentifier kyber768_aes = new DerObjectIdentifier("1.3.6.1.4.1.2.267.10.3.3"); // pqc_kem_kyber.Branch("5");
+        public static readonly DerObjectIdentifier kyber1024_aes = new DerObjectIdentifier("1.3.6.1.4.1.2.267.10.4.4"); // pqc_kem_kyber.Branch("6");
+
+        /**
+         * BIKE
+         */
+        public static readonly DerObjectIdentifier pqc_kem_bike = bc_kem.Branch("8");
+
+        public static readonly DerObjectIdentifier bike128 = pqc_kem_bike.Branch("1");
+        public static readonly DerObjectIdentifier bike192 = pqc_kem_bike.Branch("2");
+        public static readonly DerObjectIdentifier bike256 = pqc_kem_bike.Branch("3");
+
+        /**
+         * HQC
+         */
+        public static readonly DerObjectIdentifier pqc_kem_hqc = bc_kem.Branch("9");
+
+        public static readonly DerObjectIdentifier hqc128 = pqc_kem_hqc.Branch("1");
+        public static readonly DerObjectIdentifier hqc192 = pqc_kem_hqc.Branch("2");
+        public static readonly DerObjectIdentifier hqc256 = pqc_kem_hqc.Branch("3");
+    }
 }
diff --git a/crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs b/crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs
index b74bac87a..a3ec5e4df 100644
--- a/crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs
+++ b/crypto/src/asn1/cmp/CAKeyUpdAnnContent.cs
@@ -7,42 +7,33 @@ namespace Org.BouncyCastle.Asn1.Cmp
 	public class CAKeyUpdAnnContent
 		: Asn1Encodable
 	{
-		private readonly CmpCertificate oldWithNew;
-		private readonly CmpCertificate newWithOld;
-		private readonly CmpCertificate newWithNew;
+        public static CAKeyUpdAnnContent GetInstance(object obj)
+        {
+            if (obj is CAKeyUpdAnnContent content)
+                return content;
 
-		private CAKeyUpdAnnContent(Asn1Sequence seq)
-		{
-			oldWithNew = CmpCertificate.GetInstance(seq[0]);
-			newWithOld = CmpCertificate.GetInstance(seq[1]);
-			newWithNew = CmpCertificate.GetInstance(seq[2]);
-		}
+            if (obj is Asn1Sequence seq)
+                return new CAKeyUpdAnnContent(seq);
 
-		public static CAKeyUpdAnnContent GetInstance(object obj)
-		{
-			if (obj is CAKeyUpdAnnContent)
-				return (CAKeyUpdAnnContent)obj;
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
+        }
 
-			if (obj is Asn1Sequence)
-				return new CAKeyUpdAnnContent((Asn1Sequence)obj);
+        private readonly CmpCertificate m_oldWithNew;
+		private readonly CmpCertificate m_newWithOld;
+		private readonly CmpCertificate m_newWithNew;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
-
-		public virtual CmpCertificate OldWithNew
-		{
-			get { return oldWithNew; }
-		}
-		
-		public virtual CmpCertificate NewWithOld
+		private CAKeyUpdAnnContent(Asn1Sequence seq)
 		{
-			get { return newWithOld; }
+			m_oldWithNew = CmpCertificate.GetInstance(seq[0]);
+			m_newWithOld = CmpCertificate.GetInstance(seq[1]);
+			m_newWithNew = CmpCertificate.GetInstance(seq[2]);
 		}
 
-		public virtual CmpCertificate NewWithNew
-		{
-			get { return newWithNew; }
-		}
+		public virtual CmpCertificate OldWithNew => m_oldWithNew;
+
+		public virtual CmpCertificate NewWithOld => m_newWithOld;
+
+		public virtual CmpCertificate NewWithNew => m_newWithNew;
 
 		/**
 		 * <pre>
@@ -56,7 +47,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return new DerSequence(oldWithNew, newWithOld, newWithNew);
+			return new DerSequence(m_oldWithNew, m_newWithOld, m_newWithNew);
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/CertAnnContent.cs b/crypto/src/asn1/cmp/CertAnnContent.cs
new file mode 100644
index 000000000..30b1fad2f
--- /dev/null
+++ b/crypto/src/asn1/cmp/CertAnnContent.cs
@@ -0,0 +1,72 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     *  CertAnnContent ::= CMPCertificate
+     */
+    public class CertAnnContent
+        : CmpCertificate
+    {
+        public static new CertAnnContent GetInstance(object obj)
+        {
+            // TODO[cmp]
+            if (obj == null)
+                return null;
+
+            if (obj is CertAnnContent content)
+                return content;
+
+            if (obj is CmpCertificate cmpCertificate)
+                return GetInstance(cmpCertificate.GetEncoded());
+
+            if (obj is byte[] bs)
+            {
+                try
+                {
+                    obj = Asn1Object.FromByteArray(bs);
+                }
+                catch (IOException)
+                {
+                    throw new ArgumentException("Invalid encoding in CertAnnContent");
+                }
+            }
+
+            if (obj is Asn1Sequence)
+                return new CertAnnContent(X509CertificateStructure.GetInstance(obj));
+
+            // TODO[cmp]
+            if (obj is Asn1TaggedObject taggedObject)
+                return new CertAnnContent(taggedObject.TagNo, taggedObject.GetObject());
+
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
+        }
+
+        public static new CertAnnContent GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+        {
+            // TODO[cmp]
+            if (taggedObject == null)
+                return null;
+
+            if (!declaredExplicit)
+                throw new ArgumentException("tag must be explicit");
+
+            // TODO[cmp]
+            return GetInstance(taggedObject.GetObject());
+        }
+
+        public CertAnnContent(int type, Asn1Object otherCert)
+            : base(type, otherCert)
+        {
+        }
+
+        public CertAnnContent(X509CertificateStructure x509v3PKCert)
+            : base(x509v3PKCert)
+        {
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/CertConfirmContent.cs b/crypto/src/asn1/cmp/CertConfirmContent.cs
index 370a9e7d6..8e75dfbd0 100644
--- a/crypto/src/asn1/cmp/CertConfirmContent.cs
+++ b/crypto/src/asn1/cmp/CertConfirmContent.cs
@@ -7,32 +7,27 @@ namespace Org.BouncyCastle.Asn1.Cmp
 	public class CertConfirmContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
-
-		private CertConfirmContent(Asn1Sequence seq)
-		{
-			content = seq;
-		}
-
 		public static CertConfirmContent GetInstance(object obj)
 		{
-			if (obj is CertConfirmContent)
-				return (CertConfirmContent)obj;
+			if (obj is CertConfirmContent content)
+				return content;
 
-			if (obj is Asn1Sequence)
-				return new CertConfirmContent((Asn1Sequence)obj);
+			if (obj is Asn1Sequence seq)
+				return new CertConfirmContent(seq);
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
 		}
 
-		public virtual CertStatus[] ToCertStatusArray()
+        private readonly Asn1Sequence m_content;
+
+        private CertConfirmContent(Asn1Sequence seq)
+        {
+            m_content = seq;
+        }
+
+        public virtual CertStatus[] ToCertStatusArray()
 		{
-			CertStatus[] result = new CertStatus[content.Count];
-			for (int i = 0; i != result.Length; i++)
-			{
-				result[i] = CertStatus.GetInstance(content[i]);
-			}
-			return result;
+			return m_content.MapElements(CertStatus.GetInstance);
 		}
 
 		/**
@@ -43,7 +38,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return content;
+			return m_content;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/CertOrEncCert.cs b/crypto/src/asn1/cmp/CertOrEncCert.cs
index eb200e1e8..e517b66ce 100644
--- a/crypto/src/asn1/cmp/CertOrEncCert.cs
+++ b/crypto/src/asn1/cmp/CertOrEncCert.cs
@@ -8,79 +8,79 @@ namespace Org.BouncyCastle.Asn1.Cmp
 	public class CertOrEncCert
 		: Asn1Encodable, IAsn1Choice
 	{
-		private readonly CmpCertificate certificate;
-		private readonly EncryptedValue encryptedCert;
+        public static CertOrEncCert GetInstance(object obj)
+        {
+            if (obj is CertOrEncCert certOrEncCert)
+                return certOrEncCert;
 
-		private CertOrEncCert(Asn1TaggedObject tagged)
+            if (obj is Asn1TaggedObject taggedObject)
+                return new CertOrEncCert(taggedObject);
+
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
+        }
+
+        private readonly CmpCertificate m_certificate;
+		private readonly EncryptedKey m_encryptedCert;
+
+		private CertOrEncCert(Asn1TaggedObject taggedObject)
 		{
-			if (tagged.TagNo == 0)
+			if (taggedObject.TagNo == 0)
 			{
-				certificate = CmpCertificate.GetInstance(tagged.GetObject());
+				m_certificate = CmpCertificate.GetInstance(taggedObject.GetObject());
 			}
-			else if (tagged.TagNo == 1)
+			else if (taggedObject.TagNo == 1)
 			{
-				encryptedCert = EncryptedValue.GetInstance(tagged.GetObject());
+                m_encryptedCert = EncryptedKey.GetInstance(taggedObject.GetObject());
 			}
 			else
 			{
-				throw new ArgumentException("unknown tag: " + tagged.TagNo, "tagged");
-			}
-		}
-		
-		public static CertOrEncCert GetInstance(object obj)
-		{
-			if (obj is CertOrEncCert)
-				return (CertOrEncCert)obj;
-
-			if (obj is Asn1TaggedObject)
-				return new CertOrEncCert((Asn1TaggedObject)obj);
-
-			throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
+				throw new ArgumentException("unknown tag: " + taggedObject.TagNo, nameof(taggedObject));
+            }
+        }
 
 		public CertOrEncCert(CmpCertificate certificate)
 		{
 			if (certificate == null)
-				throw new ArgumentNullException("certificate");
+				throw new ArgumentNullException(nameof(certificate));
 
-			this.certificate = certificate;
+			m_certificate = certificate;
 		}
 
-		public CertOrEncCert(EncryptedValue encryptedCert)
+		public CertOrEncCert(EncryptedValue encryptedValue)
 		{
-			if (encryptedCert == null)
-				throw new ArgumentNullException("encryptedCert");
+			if (encryptedValue == null)
+				throw new ArgumentNullException(nameof(encryptedValue));
 
-			this.encryptedCert = encryptedCert;
+			m_encryptedCert = new EncryptedKey(encryptedValue);
 		}
 
-		public virtual CmpCertificate Certificate
-		{
-			get { return certificate; }
-		}
+        public CertOrEncCert(EncryptedKey encryptedKey)
+        {
+            if (encryptedKey == null)
+                throw new ArgumentNullException(nameof(encryptedKey));
 
-		public virtual EncryptedValue EncryptedCert
-		{
-			get { return encryptedCert; }
-		}
+            m_encryptedCert = encryptedKey;
+        }
 
-		/**
+		public virtual CmpCertificate Certificate => m_certificate;
+
+		public virtual EncryptedKey EncryptedCert => m_encryptedCert;
+
+        /**
 		 * <pre>
 		 * CertOrEncCert ::= CHOICE {
 		 *                      certificate     [0] CMPCertificate,
-		 *                      encryptedCert   [1] EncryptedValue
+		 *                      encryptedCert   [1] EncryptedKey
 		 *           }
 		 * </pre>
 		 * @return a basic ASN.1 object representation.
 		 */
-		public override Asn1Object ToAsn1Object()
+        public override Asn1Object ToAsn1Object()
 		{
-			if (certificate != null)
-			{
-				return new DerTaggedObject(true, 0, certificate);
-			}
+			if (m_certificate != null)
+				return new DerTaggedObject(true, 0, m_certificate);
 
-			return new DerTaggedObject(true, 1, encryptedCert);
+			return new DerTaggedObject(true, 1, m_encryptedCert);
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/CertRepMessage.cs b/crypto/src/asn1/cmp/CertRepMessage.cs
index d24dd963b..696cfde47 100644
--- a/crypto/src/asn1/cmp/CertRepMessage.cs
+++ b/crypto/src/asn1/cmp/CertRepMessage.cs
@@ -7,8 +7,19 @@ namespace Org.BouncyCastle.Asn1.Cmp
 	public class CertRepMessage
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence caPubs;
-		private readonly Asn1Sequence response;
+        public static CertRepMessage GetInstance(object obj)
+        {
+            if (obj is CertRepMessage certRepMessage)
+                return certRepMessage;
+
+            if (obj != null)
+				return new CertRepMessage(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly Asn1Sequence m_caPubs;
+		private readonly Asn1Sequence m_response;
 		
 		private CertRepMessage(Asn1Sequence seq)
 		{
@@ -16,57 +27,33 @@ namespace Org.BouncyCastle.Asn1.Cmp
 
 			if (seq.Count > 1)
 			{
-				caPubs = Asn1Sequence.GetInstance((Asn1TaggedObject)seq[index++], true);
+				m_caPubs = Asn1Sequence.GetInstance((Asn1TaggedObject)seq[index++], true);
 			}
 
-			response = Asn1Sequence.GetInstance(seq[index]);
-		}
-
-		public static CertRepMessage GetInstance(object obj)
-		{
-			if (obj is CertRepMessage)
-				return (CertRepMessage)obj;
-
-			if (obj is Asn1Sequence)
-				return new CertRepMessage((Asn1Sequence)obj);
-
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+			m_response = Asn1Sequence.GetInstance(seq[index]);
 		}
 
 		public CertRepMessage(CmpCertificate[] caPubs, CertResponse[] response)
 		{
 			if (response == null)
-				throw new ArgumentNullException("response");
+				throw new ArgumentNullException(nameof(response));
 
 			if (caPubs != null)
 			{
-				this.caPubs = new DerSequence(caPubs);
+				m_caPubs = new DerSequence(caPubs);
 			}
 
-			this.response = new DerSequence(response);
+			m_response = new DerSequence(response);
 		}
 
 		public virtual CmpCertificate[] GetCAPubs()
 		{
-			if (caPubs == null)
-				return null;
-
-			CmpCertificate[] results = new CmpCertificate[caPubs.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = CmpCertificate.GetInstance(caPubs[i]);
-			}
-			return results;
+			return m_caPubs == null ? null : m_caPubs.MapElements(CmpCertificate.GetInstance);
 		}
 
 		public virtual CertResponse[] GetResponse()
 		{
-			CertResponse[] results = new CertResponse[response.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = CertResponse.GetInstance(response[i]);
-			}
-			return results;
+            return m_response.MapElements(CertResponse.GetInstance);
 		}
 
 		/**
@@ -81,9 +68,9 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector();
-            v.AddOptionalTagged(true, 1, caPubs);
-			v.Add(response);
+			Asn1EncodableVector v = new Asn1EncodableVector(2);
+            v.AddOptionalTagged(true, 1, m_caPubs);
+			v.Add(m_response);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/CertReqTemplateContent.cs b/crypto/src/asn1/cmp/CertReqTemplateContent.cs
new file mode 100644
index 000000000..c25c71ad1
--- /dev/null
+++ b/crypto/src/asn1/cmp/CertReqTemplateContent.cs
@@ -0,0 +1,67 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Crmf;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * GenMsg:    {id-it 19}, &lt; absent &gt;
+     * GenRep:    {id-it 19}, CertReqTemplateContent | &lt; absent &gt;
+     * <p>
+     * CertReqTemplateValue  ::= CertReqTemplateContent
+     * </p><p>
+     * CertReqTemplateContent ::= SEQUENCE {
+     * certTemplate           CertTemplate,
+     * keySpec                Controls OPTIONAL }
+     * </p><p>
+     * Controls  ::= SEQUENCE SIZE (1..MAX) OF AttributeTypeAndValue
+     * </p>
+     */
+    public class CertReqTemplateContent
+        : Asn1Encodable
+    {
+        public static CertReqTemplateContent GetInstance(object obj)
+        {
+            if (obj is CertReqTemplateContent certReqTemplateContent)
+                return certReqTemplateContent;
+
+            if (obj != null)
+                return new CertReqTemplateContent(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly CertTemplate m_certTemplate;
+        private readonly Asn1Sequence m_keySpec;
+
+        private CertReqTemplateContent(Asn1Sequence seq)
+        {
+            if (seq.Count != 1 && seq.Count != 2)
+                throw new ArgumentException("expected sequence size of 1 or 2");
+
+            m_certTemplate = CertTemplate.GetInstance(seq[0]);
+
+            if (seq.Count > 1)
+            {
+                m_keySpec = Asn1Sequence.GetInstance(seq[1]);
+            }
+        }
+
+        public CertReqTemplateContent(CertTemplate certTemplate, Asn1Sequence keySpec)
+        {
+            m_certTemplate = certTemplate;
+            m_keySpec = keySpec;
+        }
+
+        public virtual CertTemplate CertTemplate => m_certTemplate;
+
+        public virtual Asn1Sequence KeySpec => m_keySpec;
+
+        public override Asn1Object ToAsn1Object()
+        {
+            Asn1EncodableVector v = new Asn1EncodableVector(m_certTemplate);
+            v.AddOptional(m_keySpec);
+            return new DerSequence(v);
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/CertResponse.cs b/crypto/src/asn1/cmp/CertResponse.cs
index 843fd9299..72a44c93e 100644
--- a/crypto/src/asn1/cmp/CertResponse.cs
+++ b/crypto/src/asn1/cmp/CertResponse.cs
@@ -1,21 +1,30 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class CertResponse
 		: Asn1Encodable
 	{
-		private readonly DerInteger certReqId;
-		private readonly PkiStatusInfo status;
-		private readonly CertifiedKeyPair certifiedKeyPair;
-		private readonly Asn1OctetString rspInfo;
+        public static CertResponse GetInstance(object obj)
+        {
+			if (obj is CertResponse certResponse)
+				return certResponse;
+
+			if (obj != null)
+				return new CertResponse(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly DerInteger m_certReqId;
+		private readonly PkiStatusInfo m_status;
+		private readonly CertifiedKeyPair m_certifiedKeyPair;
+		private readonly Asn1OctetString m_rspInfo;
 
 		private CertResponse(Asn1Sequence seq)
 		{
-			certReqId = DerInteger.GetInstance(seq[0]);
-			status = PkiStatusInfo.GetInstance(seq[1]);
+			m_certReqId = DerInteger.GetInstance(seq[0]);
+			m_status = PkiStatusInfo.GetInstance(seq[1]);
 
 			if (seq.Count >= 3)
 			{
@@ -24,71 +33,46 @@ namespace Org.BouncyCastle.Asn1.Cmp
 					Asn1Encodable o = seq[2];
 					if (o is Asn1OctetString)
 					{
-						rspInfo = Asn1OctetString.GetInstance(o);
+						m_rspInfo = Asn1OctetString.GetInstance(o);
 					}
 					else
 					{
-						certifiedKeyPair = CertifiedKeyPair.GetInstance(o);
+						m_certifiedKeyPair = CertifiedKeyPair.GetInstance(o);
 					}
 				}
 				else
 				{
-					certifiedKeyPair = CertifiedKeyPair.GetInstance(seq[2]);
-					rspInfo = Asn1OctetString.GetInstance(seq[3]);
+					m_certifiedKeyPair = CertifiedKeyPair.GetInstance(seq[2]);
+					m_rspInfo = Asn1OctetString.GetInstance(seq[3]);
 				}
 			}
 		}
 
-		public static CertResponse GetInstance(object obj)
-		{
-			if (obj is CertResponse)
-				return (CertResponse)obj;
-
-			if (obj is Asn1Sequence)
-				return new CertResponse((Asn1Sequence)obj);
-
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
-
-		public CertResponse(
-			DerInteger certReqId,
-			PkiStatusInfo status)
+		public CertResponse(DerInteger certReqId, PkiStatusInfo status)
 			: this(certReqId, status, null, null)
 		{
 		}
 
-		public CertResponse(
-			DerInteger			certReqId,
-			PkiStatusInfo		status,
-			CertifiedKeyPair	certifiedKeyPair,
-			Asn1OctetString		rspInfo)
-		{
-			if (certReqId == null)
-				throw new ArgumentNullException("certReqId");
+        public CertResponse(DerInteger certReqId, PkiStatusInfo status, CertifiedKeyPair certifiedKeyPair,
+            Asn1OctetString rspInfo)
+        {
+            if (certReqId == null)
+				throw new ArgumentNullException(nameof(certReqId));
 
 			if (status == null)
-				throw new ArgumentNullException("status");
+				throw new ArgumentNullException(nameof(status));
 
-			this.certReqId = certReqId;
-			this.status = status;
-			this.certifiedKeyPair = certifiedKeyPair;
-			this.rspInfo = rspInfo;
+			m_certReqId = certReqId;
+			m_status = status;
+			m_certifiedKeyPair = certifiedKeyPair;
+			m_rspInfo = rspInfo;
 		}
 
-		public virtual DerInteger CertReqID
-		{
-			get { return certReqId; }
-		}
+		public virtual DerInteger CertReqID => m_certReqId;
 
-		public virtual PkiStatusInfo Status
-		{
-			get { return status; }
-		}
+		public virtual PkiStatusInfo Status => m_status;
 
-		public virtual CertifiedKeyPair CertifiedKeyPair
-		{
-			get { return certifiedKeyPair; }
-		}
+		public virtual CertifiedKeyPair CertifiedKeyPair => m_certifiedKeyPair;
 
 		/**
 		 * <pre>
@@ -108,8 +92,8 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(certReqId, status);
-			v.AddOptional(certifiedKeyPair, rspInfo);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_certReqId, m_status);
+			v.AddOptional(m_certifiedKeyPair, m_rspInfo);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/CertStatus.cs b/crypto/src/asn1/cmp/CertStatus.cs
index d437b57b2..6eb36c6fb 100644
--- a/crypto/src/asn1/cmp/CertStatus.cs
+++ b/crypto/src/asn1/cmp/CertStatus.cs
@@ -1,84 +1,102 @@
 using System;
 
+using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class CertStatus
 		: Asn1Encodable
 	{
-		private readonly Asn1OctetString certHash;
-		private readonly DerInteger certReqId;
-		private readonly PkiStatusInfo statusInfo;
+        public static CertStatus GetInstance(object obj)
+        {
+			if (obj is CertStatus certStatus)
+				return certStatus;
 
-		private CertStatus(Asn1Sequence seq)
+			if (obj != null)
+				return new CertStatus(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly Asn1OctetString m_certHash;
+		private readonly DerInteger m_certReqID;
+		private readonly PkiStatusInfo m_statusInfo;
+        private readonly AlgorithmIdentifier m_hashAlg;
+
+        private CertStatus(Asn1Sequence seq)
 		{
-			certHash = Asn1OctetString.GetInstance(seq[0]);
-			certReqId = DerInteger.GetInstance(seq[1]);
+			m_certHash = Asn1OctetString.GetInstance(seq[0]);
+			m_certReqID = DerInteger.GetInstance(seq[1]);
 
 			if (seq.Count > 2)
 			{
-				statusInfo = PkiStatusInfo.GetInstance(seq[2]);
+				for (int t = 2; t < seq.Count; t++)
+				{
+					Asn1Object p = seq[t].ToAsn1Object();
+					if (p is Asn1Sequence s)
+					{
+						m_statusInfo = PkiStatusInfo.GetInstance(s);
+					}
+					if (p is Asn1TaggedObject dto)
+					{
+						if (dto.TagNo != 0)
+							throw new ArgumentException("unknown tag " + dto.TagNo);
+
+						m_hashAlg = AlgorithmIdentifier.GetInstance(dto, true);
+					}
+				}
 			}
 		}
 
-		public CertStatus(byte[] certHash, BigInteger certReqId)
+		public CertStatus(byte[] certHash, BigInteger certReqID)
 		{
-			this.certHash = new DerOctetString(certHash);
-			this.certReqId = new DerInteger(certReqId);
+			m_certHash = new DerOctetString(certHash);
+			m_certReqID = new DerInteger(certReqID);
 		}
 
-		public CertStatus(byte[] certHash, BigInteger certReqId, PkiStatusInfo statusInfo)
+		public CertStatus(byte[] certHash, BigInteger certReqID, PkiStatusInfo statusInfo)
 		{
-			this.certHash = new DerOctetString(certHash);
-			this.certReqId = new DerInteger(certReqId);
-			this.statusInfo = statusInfo;
+            m_certHash = new DerOctetString(certHash);
+            m_certReqID = new DerInteger(certReqID);
+            m_statusInfo = statusInfo;
 		}
 
-		public static CertStatus GetInstance(object obj)
-		{
-			if (obj is CertStatus)
-				return (CertStatus)obj;
+        public CertStatus(byte[] certHash, BigInteger certReqID, PkiStatusInfo statusInfo, AlgorithmIdentifier hashAlg)
+        {
+            m_certHash = new DerOctetString(certHash);
+            m_certReqID = new DerInteger(certReqID);
+            m_statusInfo = statusInfo;
+            m_hashAlg = hashAlg;
+        }
 
-			if (obj is Asn1Sequence)
-				return new CertStatus((Asn1Sequence)obj);
+        public virtual Asn1OctetString CertHash => m_certHash;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
+		public virtual DerInteger CertReqID => m_certReqID;
 
-		public virtual Asn1OctetString CertHash
-		{
-			get { return certHash; }
-		}
-
-		public virtual DerInteger CertReqID
-		{
-			get { return certReqId; }
-		}
+		public virtual PkiStatusInfo StatusInfo => m_statusInfo;
 
-		public virtual PkiStatusInfo StatusInfo
-		{
-			get { return statusInfo; }
-		}
+		public virtual AlgorithmIdentifier HashAlg => m_hashAlg;
 
-		/**
-		 * <pre>
-		 * CertStatus ::= SEQUENCE {
-		 *                   certHash    OCTET STRING,
-		 *                   -- the hash of the certificate, using the same hash algorithm
-		 *                   -- as is used to create and verify the certificate signature
-		 *                   certReqId   INTEGER,
-		 *                   -- to match this confirmation with the corresponding req/rep
-		 *                   statusInfo  PKIStatusInfo OPTIONAL
-		 * }
-		 * </pre>
-		 * @return a basic ASN.1 object representation.
-		 */
-		public override Asn1Object ToAsn1Object()
+        /**
+         * <pre>
+         *
+         *  CertStatus ::= SEQUENCE {
+         *     certHash    OCTET STRING,
+         *     certReqId   INTEGER,
+         *     statusInfo  PKIStatusInfo OPTIONAL,
+         *     hashAlg [0] AlgorithmIdentifier{DIGEST-ALGORITHM, {...}} OPTIONAL
+         *   }
+         *
+         * </pre>
+         *
+         * @return a basic ASN.1 object representation.
+         */
+        public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(certHash, certReqId);
-			v.AddOptional(statusInfo);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_certHash, m_certReqID);
+			v.AddOptional(m_statusInfo);
+			v.AddOptionalTagged(true, 0, m_hashAlg);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/CertifiedKeyPair.cs b/crypto/src/asn1/cmp/CertifiedKeyPair.cs
index 0b1c5d44d..ec60ce965 100644
--- a/crypto/src/asn1/cmp/CertifiedKeyPair.cs
+++ b/crypto/src/asn1/cmp/CertifiedKeyPair.cs
@@ -1,20 +1,30 @@
 using System;
 
 using Org.BouncyCastle.Asn1.Crmf;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class CertifiedKeyPair
 		: Asn1Encodable
 	{
-		private readonly CertOrEncCert certOrEncCert;
-		private readonly EncryptedValue privateKey;
-		private readonly PkiPublicationInfo publicationInfo;
+        public static CertifiedKeyPair GetInstance(object obj)
+        {
+            if (obj is CertifiedKeyPair certifiedKeyPair)
+                return certifiedKeyPair;
 
-		private CertifiedKeyPair(Asn1Sequence seq)
+            if (obj != null)
+                return new CertifiedKeyPair(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly CertOrEncCert m_certOrEncCert;
+		private readonly EncryptedKey m_privateKey;
+		private readonly PkiPublicationInfo m_publicationInfo;
+
+        private CertifiedKeyPair(Asn1Sequence seq)
 		{
-			certOrEncCert = CertOrEncCert.GetInstance(seq[0]);
+			m_certOrEncCert = CertOrEncCert.GetInstance(seq[0]);
 
 			if (seq.Count >= 2)
 			{
@@ -23,66 +33,48 @@ namespace Org.BouncyCastle.Asn1.Cmp
 					Asn1TaggedObject tagged = Asn1TaggedObject.GetInstance(seq[1]);
 					if (tagged.TagNo == 0)
 					{
-						privateKey = EncryptedValue.GetInstance(tagged.GetObject());
+						m_privateKey = EncryptedKey.GetInstance(tagged.GetObject());
 					}
 					else
 					{
-						publicationInfo = PkiPublicationInfo.GetInstance(tagged.GetObject());
+						m_publicationInfo = PkiPublicationInfo.GetInstance(tagged.GetObject());
 					}
 				}
 				else
 				{
-					privateKey = EncryptedValue.GetInstance(Asn1TaggedObject.GetInstance(seq[1]));
-					publicationInfo = PkiPublicationInfo.GetInstance(Asn1TaggedObject.GetInstance(seq[2]));
+                    m_privateKey = EncryptedKey.GetInstance(Asn1TaggedObject.GetInstance(seq[1]).GetObject());
+                    m_publicationInfo = PkiPublicationInfo.GetInstance(Asn1TaggedObject.GetInstance(seq[2]).GetObject());
 				}
 			}
 		}
 
-		public static CertifiedKeyPair GetInstance(object obj)
+		public CertifiedKeyPair(CertOrEncCert certOrEncCert)
+			: this(certOrEncCert, (EncryptedKey)null, null)
 		{
-			if (obj is CertifiedKeyPair)
-				return (CertifiedKeyPair)obj;
-
-			if (obj is Asn1Sequence)
-				return new CertifiedKeyPair((Asn1Sequence)obj);
-
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
 		}
 
-		public CertifiedKeyPair(
-			CertOrEncCert certOrEncCert)
-			: this(certOrEncCert, null, null)
-		{
-		}
+        public CertifiedKeyPair(CertOrEncCert certOrEncCert, EncryptedValue privateKey,
+            PkiPublicationInfo publicationInfo)
+            : this(certOrEncCert, privateKey == null ? null : new EncryptedKey(privateKey), publicationInfo)
+        {
+        }
 
-		public CertifiedKeyPair(
-			CertOrEncCert		certOrEncCert,
-			EncryptedValue		privateKey,
-			PkiPublicationInfo	publicationInfo
-		)
-		{
+        public CertifiedKeyPair(CertOrEncCert certOrEncCert, EncryptedKey privateKey,
+			PkiPublicationInfo publicationInfo)
+        {
 			if (certOrEncCert == null)
-				throw new ArgumentNullException("certOrEncCert");
+				throw new ArgumentNullException(nameof(certOrEncCert));
 
-			this.certOrEncCert = certOrEncCert;
-			this.privateKey = privateKey;
-			this.publicationInfo = publicationInfo;
-		}
+            m_certOrEncCert = certOrEncCert;
+            m_privateKey = privateKey;
+            m_publicationInfo = publicationInfo;
+        }
 
-		public virtual CertOrEncCert CertOrEncCert
-		{
-			get { return certOrEncCert; }
-		}
+		public virtual CertOrEncCert CertOrEncCert => m_certOrEncCert;
 
-		public virtual EncryptedValue PrivateKey
-		{
-			get { return privateKey; }
-		}
+		public virtual EncryptedKey PrivateKey => m_privateKey;
 
-		public virtual PkiPublicationInfo PublicationInfo
-		{
-			get { return publicationInfo; }
-		}
+		public virtual PkiPublicationInfo PublicationInfo => m_publicationInfo;
 
 		/**
 		 * <pre>
@@ -97,9 +89,9 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(certOrEncCert);
-            v.AddOptionalTagged(true, 0, privateKey);
-            v.AddOptionalTagged(true, 1, publicationInfo);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_certOrEncCert);
+            v.AddOptionalTagged(true, 0, m_privateKey);
+            v.AddOptionalTagged(true, 1, m_publicationInfo);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/Challenge.cs b/crypto/src/asn1/cmp/Challenge.cs
index 016c082e2..ca3d06339 100644
--- a/crypto/src/asn1/cmp/Challenge.cs
+++ b/crypto/src/asn1/cmp/Challenge.cs
@@ -1,16 +1,52 @@
 using System;
 
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class Challenge
+    /**
+     * <pre>
+     * Challenge ::= SEQUENCE {
+     *          owf                 AlgorithmIdentifier  OPTIONAL,
+     *
+     *          -- MUST be present in the first Challenge; MAY be omitted in
+     *          -- any subsequent Challenge in POPODecKeyChallContent (if
+     *          -- omitted, then the owf used in the immediately preceding
+     *          -- Challenge is to be used).
+     *
+     *          witness             OCTET STRING,
+     *          -- the result of applying the one-way function (owf) to a
+     *          -- randomly-generated INTEGER, A.  [Note that a different
+     *          -- INTEGER MUST be used for each Challenge.]
+     *          challenge           OCTET STRING
+     *          -- the encryption (under the public key for which the cert.
+     *          -- request is being made) of Rand, where Rand is specified as
+     *          --   Rand ::= SEQUENCE {
+     *          --      int      INTEGER,
+     *          --       - the randomly-generated INTEGER A (above)
+     *          --      sender   GeneralName
+     *          --       - the sender's name (as included in PKIHeader)
+     *          --   }
+     *      }
+     *      </pre>
+     */
+    public class Challenge
 		: Asn1Encodable
 	{
-		private readonly AlgorithmIdentifier owf;
-		private readonly Asn1OctetString witness;
-		private readonly Asn1OctetString challenge;
+        public static Challenge GetInstance(object obj)
+        {
+            if (obj is Challenge challenge)
+                return challenge;
+
+            if (obj != null)
+                return new Challenge(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly AlgorithmIdentifier m_owf;
+		private readonly Asn1OctetString m_witness;
+		private readonly Asn1OctetString m_challenge;
 
 		private Challenge(Asn1Sequence seq)
 		{
@@ -18,30 +54,32 @@ namespace Org.BouncyCastle.Asn1.Cmp
 
 			if (seq.Count == 3)
 			{
-				owf = AlgorithmIdentifier.GetInstance(seq[index++]);
+				m_owf = AlgorithmIdentifier.GetInstance(seq[index++]);
 			}
 
-			witness = Asn1OctetString.GetInstance(seq[index++]);
-			challenge = Asn1OctetString.GetInstance(seq[index]);
+			m_witness = Asn1OctetString.GetInstance(seq[index++]);
+			m_challenge = Asn1OctetString.GetInstance(seq[index]);
 		}
 
-		public static Challenge GetInstance(object obj)
-		{
-			if (obj is Challenge)
-				return (Challenge)obj;
+        public Challenge(byte[] witness, byte[] challenge)
+            : this(null, witness, challenge)
+        {
+        }
 
-			if (obj is Asn1Sequence)
-				return new Challenge((Asn1Sequence)obj);
+        public Challenge(AlgorithmIdentifier owf, byte[] witness, byte[] challenge)
+        {
+            m_owf = owf;
+            m_witness = new DerOctetString(witness);
+            m_challenge = new DerOctetString(challenge);
+        }
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
+        public virtual AlgorithmIdentifier Owf => m_owf;
 
-		public virtual AlgorithmIdentifier Owf
-		{
-			get { return owf; }
-		}
+		public virtual Asn1OctetString Witness => m_witness;
+
+		public virtual Asn1OctetString ChallengeValue => m_challenge;
 
-		/**
+        /**
 		 * <pre>
 		 * Challenge ::= SEQUENCE {
 		 *                 owf                 AlgorithmIdentifier  OPTIONAL,
@@ -68,12 +106,57 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 * </pre>
 		 * @return a basic ASN.1 object representation.
 		 */
-		public override Asn1Object ToAsn1Object()
+        public override Asn1Object ToAsn1Object()
 		{
 			Asn1EncodableVector v = new Asn1EncodableVector();
-			v.AddOptional(owf);
-			v.Add(witness, challenge);
+			v.AddOptional(m_owf);
+			v.Add(m_witness, m_challenge);
 			return new DerSequence(v);
 		}
+
+        /**
+         * Rand is the inner type
+         */
+        public class Rand
+            : Asn1Encodable
+        {
+            public static Rand GetInstance(object obj)
+            {
+                if (obj is Rand rand)
+                    return rand;
+
+                if (obj != null)
+                    return new Rand(Asn1Sequence.GetInstance(obj));
+
+                return null;
+            }
+
+            private readonly DerInteger m_intVal;
+            private readonly GeneralName m_sender;
+
+            public Rand(DerInteger intVal, GeneralName sender)
+            {
+                m_intVal = intVal;
+                m_sender = sender;
+            }
+
+            public Rand(Asn1Sequence seq)
+            {
+                if (seq.Count != 2)
+                    throw new ArgumentException("expected sequence size of 2");
+
+                m_intVal = DerInteger.GetInstance(seq[0]);
+                m_sender = GeneralName.GetInstance(seq[1]);
+            }
+
+            public virtual DerInteger IntVal => m_intVal;
+
+			public virtual GeneralName Sender => m_sender;
+
+			public override Asn1Object ToAsn1Object()
+			{
+                return new DerSequence(m_intVal, m_sender);
+            }
+        }
 	}
 }
diff --git a/crypto/src/asn1/cmp/CmpCertificate.cs b/crypto/src/asn1/cmp/CmpCertificate.cs
index 33356b486..af433ec4d 100644
--- a/crypto/src/asn1/cmp/CmpCertificate.cs
+++ b/crypto/src/asn1/cmp/CmpCertificate.cs
@@ -1,4 +1,5 @@
 using System;
+using System.IO;
 
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Utilities;
@@ -8,54 +9,84 @@ namespace Org.BouncyCastle.Asn1.Cmp
     public class CmpCertificate
         : Asn1Encodable, IAsn1Choice
     {
-        private readonly X509CertificateStructure x509v3PKCert;
-        private readonly AttributeCertificate x509v2AttrCert;
-
-        /**
-         * Note: the addition of attribute certificates is a BC extension.
-         */
-        public CmpCertificate(AttributeCertificate x509v2AttrCert)
+        public static CmpCertificate GetInstance(object obj)
         {
-            this.x509v2AttrCert = x509v2AttrCert;
-        }
+            // TODO[cmp] Review this whole metho
 
-        public CmpCertificate(X509CertificateStructure x509v3PKCert)
-        {
-            if (x509v3PKCert.Version != 3)
-                throw new ArgumentException("only version 3 certificates allowed", "x509v3PKCert");
+            if (obj == null)
+                return null;
 
-            this.x509v3PKCert = x509v3PKCert;
-        }
+            if (obj is CmpCertificate cmpCertificate)
+                return cmpCertificate;
 
-        public static CmpCertificate GetInstance(object obj)
-        {
-            if (obj is CmpCertificate)
-                return (CmpCertificate)obj;
+            if (obj is byte[] bs)
+            {
+                try
+                {
+                    obj = Asn1Object.FromByteArray(bs);
+                }
+                catch (IOException)
+                {
+                    throw new ArgumentException("Invalid encoding in CmpCertificate");
+                }
+            }
 
             if (obj is Asn1Sequence)
                 return new CmpCertificate(X509CertificateStructure.GetInstance(obj));
 
-            if (obj is Asn1TaggedObject)
-                return new CmpCertificate(AttributeCertificate.GetInstance(((Asn1TaggedObject)obj).GetObject()));
+            if (obj is Asn1TaggedObject taggedObject)
+                return new CmpCertificate(taggedObject.TagNo, taggedObject.GetObject());
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
         }
 
-        public virtual bool IsX509v3PKCert
+        public static CmpCertificate GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
         {
-            get { return x509v3PKCert != null; }
+            // TODO[cmp]
+            if (taggedObject == null)
+                return null;
+
+            if (!declaredExplicit)
+                throw new ArgumentException("tag must be explicit");
+
+            // TODO[cmp]
+            return GetInstance(taggedObject.GetObject());
         }
 
-        public virtual X509CertificateStructure X509v3PKCert
+        private readonly X509CertificateStructure m_x509v3PKCert;
+
+        private readonly int m_otherTagValue;
+        private readonly Asn1Encodable m_otherCert;
+
+        /**
+         * Note: the addition of other certificates is a BC extension. If you use this constructor they
+         * will be added with an explicit tag value of type.
+         *
+         * @param type      the type of the certificate (used as a tag value).
+         * @param otherCert the object representing the certificate
+         */
+        public CmpCertificate(int type, Asn1Encodable otherCert)
         {
-            get { return x509v3PKCert; }
+            m_otherTagValue = type;
+            m_otherCert = otherCert;
         }
 
-        public virtual AttributeCertificate X509v2AttrCert
+        public CmpCertificate(X509CertificateStructure x509v3PKCert)
         {
-            get { return x509v2AttrCert; }
+            if (x509v3PKCert.Version != 3)
+                throw new ArgumentException("only version 3 certificates allowed", nameof(x509v3PKCert));
+
+            m_x509v3PKCert = x509v3PKCert;
         }
 
+        public virtual bool IsX509v3PKCert => m_x509v3PKCert != null;
+
+        public virtual X509CertificateStructure X509v3PKCert => m_x509v3PKCert;
+
+        public virtual int OtherCertTag => m_otherTagValue;
+
+        public virtual Asn1Encodable OtherCert => m_otherCert;
+
         /**
          * <pre>
          * CMPCertificate ::= CHOICE {
@@ -69,13 +100,13 @@ namespace Org.BouncyCastle.Asn1.Cmp
          */
         public override Asn1Object ToAsn1Object()
         {
-            if (x509v2AttrCert != null)
+            if (m_otherCert != null)
             {
                 // explicit following CMP conventions
-                return new DerTaggedObject(true, 1, x509v2AttrCert);
+                return new DerTaggedObject(true, m_otherTagValue, m_otherCert);
             }
 
-            return x509v3PKCert.ToAsn1Object();
+            return m_x509v3PKCert.ToAsn1Object();
         }
     }
 }
diff --git a/crypto/src/asn1/cmp/CmpObjectIdentifiers.cs b/crypto/src/asn1/cmp/CmpObjectIdentifiers.cs
index 7e8274175..1b3227c47 100644
--- a/crypto/src/asn1/cmp/CmpObjectIdentifiers.cs
+++ b/crypto/src/asn1/cmp/CmpObjectIdentifiers.cs
@@ -2,105 +2,257 @@ using System;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public abstract class CmpObjectIdentifiers
+	public static class CmpObjectIdentifiers
 	{
-		// RFC 4210
-
-		// id-PasswordBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 13}
-		public static readonly DerObjectIdentifier passwordBasedMac = new DerObjectIdentifier("1.2.840.113533.7.66.13");
-
-		// id-DHBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 30}
-		public static readonly DerObjectIdentifier dhBasedMac = new DerObjectIdentifier("1.2.840.113533.7.66.30");
-
-		// Example InfoTypeAndValue contents include, but are not limited
-		// to, the following (un-comment in this ASN.1 module and use as
-		// appropriate for a given environment):
-		//
-		// id-it-caProtEncCert OBJECT IDENTIFIER ::= {id-it 1}
-		// CAProtEncCertValue ::= CMPCertificate
-		// id-it-signKeyPairTypes OBJECT IDENTIFIER ::= {id-it 2}
-		// SignKeyPairTypesValue ::= SEQUENCE OF AlgorithmIdentifier
-		// id-it-encKeyPairTypes OBJECT IDENTIFIER ::= {id-it 3}
-		// EncKeyPairTypesValue ::= SEQUENCE OF AlgorithmIdentifier
-		// id-it-preferredSymmAlg OBJECT IDENTIFIER ::= {id-it 4}
-		// PreferredSymmAlgValue ::= AlgorithmIdentifier
-		// id-it-caKeyUpdateInfo OBJECT IDENTIFIER ::= {id-it 5}
-		// CAKeyUpdateInfoValue ::= CAKeyUpdAnnContent
-		// id-it-currentCRL OBJECT IDENTIFIER ::= {id-it 6}
-		// CurrentCRLValue ::= CertificateList
-		// id-it-unsupportedOIDs OBJECT IDENTIFIER ::= {id-it 7}
-		// UnsupportedOIDsValue ::= SEQUENCE OF OBJECT IDENTIFIER
-		// id-it-keyPairParamReq OBJECT IDENTIFIER ::= {id-it 10}
-		// KeyPairParamReqValue ::= OBJECT IDENTIFIER
-		// id-it-keyPairParamRep OBJECT IDENTIFIER ::= {id-it 11}
-		// KeyPairParamRepValue ::= AlgorithmIdentifer
-		// id-it-revPassphrase OBJECT IDENTIFIER ::= {id-it 12}
-		// RevPassphraseValue ::= EncryptedValue
-		// id-it-implicitConfirm OBJECT IDENTIFIER ::= {id-it 13}
-		// ImplicitConfirmValue ::= NULL
-		// id-it-confirmWaitTime OBJECT IDENTIFIER ::= {id-it 14}
-		// ConfirmWaitTimeValue ::= GeneralizedTime
-		// id-it-origPKIMessage OBJECT IDENTIFIER ::= {id-it 15}
-		// OrigPKIMessageValue ::= PKIMessages
-		// id-it-suppLangTags OBJECT IDENTIFIER ::= {id-it 16}
-		// SuppLangTagsValue ::= SEQUENCE OF UTF8String
-		//
-		// where
-		//
-		// id-pkix OBJECT IDENTIFIER ::= {
-		// iso(1) identified-organization(3)
-		// dod(6) internet(1) security(5) mechanisms(5) pkix(7)}
-		// and
-		// id-it OBJECT IDENTIFIER ::= {id-pkix 4}
-		public static readonly DerObjectIdentifier it_caProtEncCert = new DerObjectIdentifier("1.3.6.1.5.5.7.4.1");
-		public static readonly DerObjectIdentifier it_signKeyPairTypes = new DerObjectIdentifier("1.3.6.1.5.5.7.4.2");
-		public static readonly DerObjectIdentifier it_encKeyPairTypes = new DerObjectIdentifier("1.3.6.1.5.5.7.4.3");
-		public static readonly DerObjectIdentifier it_preferredSymAlg = new DerObjectIdentifier("1.3.6.1.5.5.7.4.4");
-		public static readonly DerObjectIdentifier it_caKeyUpdateInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.4.5");
-		public static readonly DerObjectIdentifier it_currentCRL = new DerObjectIdentifier("1.3.6.1.5.5.7.4.6");
-		public static readonly DerObjectIdentifier it_unsupportedOIDs = new DerObjectIdentifier("1.3.6.1.5.5.7.4.7");
-		public static readonly DerObjectIdentifier it_keyPairParamReq = new DerObjectIdentifier("1.3.6.1.5.5.7.4.10");
-		public static readonly DerObjectIdentifier it_keyPairParamRep = new DerObjectIdentifier("1.3.6.1.5.5.7.4.11");
-		public static readonly DerObjectIdentifier it_revPassphrase = new DerObjectIdentifier("1.3.6.1.5.5.7.4.12");
-		public static readonly DerObjectIdentifier it_implicitConfirm = new DerObjectIdentifier("1.3.6.1.5.5.7.4.13");
-		public static readonly DerObjectIdentifier it_confirmWaitTime = new DerObjectIdentifier("1.3.6.1.5.5.7.4.14");
-		public static readonly DerObjectIdentifier it_origPKIMessage = new DerObjectIdentifier("1.3.6.1.5.5.7.4.15");
-		public static readonly DerObjectIdentifier it_suppLangTags = new DerObjectIdentifier("1.3.6.1.5.5.7.4.16");
-
-		// RFC 4211
-
-		// id-pkix OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
-		// dod(6) internet(1) security(5) mechanisms(5) pkix(7) }
-		//
-		// arc for Internet X.509 PKI protocols and their components
-		// id-pkip OBJECT IDENTIFIER :: { id-pkix pkip(5) }
-		//
-		// arc for Registration Controls in CRMF
-		// id-regCtrl OBJECT IDENTIFIER ::= { id-pkip regCtrl(1) }
-		//
-		// arc for Registration Info in CRMF
-		// id-regInfo OBJECT IDENTIFIER ::= { id-pkip id-regInfo(2) }
-
-		public static readonly DerObjectIdentifier regCtrl_regToken = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.1");
-		public static readonly DerObjectIdentifier regCtrl_authenticator = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.2");
-		public static readonly DerObjectIdentifier regCtrl_pkiPublicationInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.3");
-		public static readonly DerObjectIdentifier regCtrl_pkiArchiveOptions = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.4");
-		public static readonly DerObjectIdentifier regCtrl_oldCertID = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.5");
-		public static readonly DerObjectIdentifier regCtrl_protocolEncrKey = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.6");
-
-		// From RFC4210:
-		// id-regCtrl-altCertTemplate OBJECT IDENTIFIER ::= {id-regCtrl 7}
-		public static readonly DerObjectIdentifier regCtrl_altCertTemplate = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.7");
-
-		public static readonly DerObjectIdentifier regInfo_utf8Pairs = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2.1");
-		public static readonly DerObjectIdentifier regInfo_certReq = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2.2");
-
-		// id-smime OBJECT IDENTIFIER ::= { iso(1) member-body(2)
-		// us(840) rsadsi(113549) pkcs(1) pkcs9(9) 16 }
-		//
-		// id-ct OBJECT IDENTIFIER ::= { id-smime 1 } -- content types
-		//
-		// id-ct-encKeyWithID OBJECT IDENTIFIER ::= {id-ct 21}
-		public static readonly DerObjectIdentifier ct_encKeyWithID = new DerObjectIdentifier("1.2.840.113549.1.9.16.1.21");
+        // RFC 4210
+
+        /**
+         * id-PasswordBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 13}
+         */
+        public static readonly DerObjectIdentifier passwordBasedMac = new DerObjectIdentifier("1.2.840.113533.7.66.13");
+
+        /**
+         * id-DHBasedMac OBJECT IDENTIFIER ::= {1 2 840 113533 7 66 30}
+         */
+        public static readonly DerObjectIdentifier dhBasedMac = new DerObjectIdentifier("1.2.840.113533.7.66.30");
+
+        // Example InfoTypeAndValue contents include, but are not limited
+        // to, the following (un-comment in this ASN.1 module and use as
+        // appropriate for a given environment):
+        //
+        //   id-it-caProtEncCert    OBJECT IDENTIFIER ::= {id-it 1}
+        //      CAProtEncCertValue      ::= CMPCertificate
+        //   id-it-signKeyPairTypes OBJECT IDENTIFIER ::= {id-it 2}
+        //      SignKeyPairTypesValue   ::= SEQUENCE OF AlgorithmIdentifier
+        //   id-it-encKeyPairTypes  OBJECT IDENTIFIER ::= {id-it 3}
+        //      EncKeyPairTypesValue    ::= SEQUENCE OF AlgorithmIdentifier
+        //   id-it-preferredSymmAlg OBJECT IDENTIFIER ::= {id-it 4}
+        //      PreferredSymmAlgValue   ::= AlgorithmIdentifier
+        //   id-it-caKeyUpdateInfo  OBJECT IDENTIFIER ::= {id-it 5}
+        //      CAKeyUpdateInfoValue    ::= CAKeyUpdAnnContent
+        //   id-it-currentCRL       OBJECT IDENTIFIER ::= {id-it 6}
+        //      CurrentCRLValue         ::= CertificateList
+        //   id-it-unsupportedOIDs  OBJECT IDENTIFIER ::= {id-it 7}
+        //      UnsupportedOIDsValue    ::= SEQUENCE OF OBJECT IDENTIFIER
+        //   id-it-keyPairParamReq  OBJECT IDENTIFIER ::= {id-it 10}
+        //      KeyPairParamReqValue    ::= OBJECT IDENTIFIER
+        //   id-it-keyPairParamRep  OBJECT IDENTIFIER ::= {id-it 11}
+        //      KeyPairParamRepValue    ::= AlgorithmIdentifer
+        //   id-it-revPassphrase    OBJECT IDENTIFIER ::= {id-it 12}
+        //      RevPassphraseValue      ::= EncryptedValue
+        //   id-it-implicitConfirm  OBJECT IDENTIFIER ::= {id-it 13}
+        //      ImplicitConfirmValue    ::= NULL
+        //   id-it-confirmWaitTime  OBJECT IDENTIFIER ::= {id-it 14}
+        //      ConfirmWaitTimeValue    ::= GeneralizedTime
+        //   id-it-origPKIMessage   OBJECT IDENTIFIER ::= {id-it 15}
+        //      OrigPKIMessageValue     ::= PKIMessages
+        //   id-it-suppLangTags     OBJECT IDENTIFIER ::= {id-it 16}
+        //      SuppLangTagsValue       ::= SEQUENCE OF UTF8String
+        //   id-it-certProfile  OBJECT IDENTIFIER ::= {id-it 21}
+        //      CertProfileValue ::= SEQUENCE SIZE (1..MAX) OF UTF8String
+        // where
+        //
+        //   id-pkix OBJECT IDENTIFIER ::= {
+        //      iso(1) identified-organization(3)
+        //      dod(6) internet(1) security(5) mechanisms(5) pkix(7)}
+        // and
+        //   id-it   OBJECT IDENTIFIER ::= {id-pkix 4}
+
+        /** RFC 4120: it-id: PKIX.4 = 1.3.6.1.5.5.7.4 */
+
+
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.1
+         */
+        public static readonly DerObjectIdentifier it_caProtEncCert = new DerObjectIdentifier("1.3.6.1.5.5.7.4.1");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.2
+         */
+        public static readonly DerObjectIdentifier it_signKeyPairTypes = new DerObjectIdentifier("1.3.6.1.5.5.7.4.2");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.3
+         */
+        public static readonly DerObjectIdentifier it_encKeyPairTypes = new DerObjectIdentifier("1.3.6.1.5.5.7.4.3");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.4
+         */
+        public static readonly DerObjectIdentifier it_preferredSymAlg = new DerObjectIdentifier("1.3.6.1.5.5.7.4.4");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.5
+         */
+        public static readonly DerObjectIdentifier it_caKeyUpdateInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.4.5");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.6
+         */
+        public static readonly DerObjectIdentifier it_currentCRL = new DerObjectIdentifier("1.3.6.1.5.5.7.4.6");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.7
+         */
+        public static readonly DerObjectIdentifier it_unsupportedOIDs = new DerObjectIdentifier("1.3.6.1.5.5.7.4.7");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.10
+         */
+        public static readonly DerObjectIdentifier it_keyPairParamReq = new DerObjectIdentifier("1.3.6.1.5.5.7.4.10");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.11
+         */
+        public static readonly DerObjectIdentifier it_keyPairParamRep = new DerObjectIdentifier("1.3.6.1.5.5.7.4.11");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.12
+         */
+        public static readonly DerObjectIdentifier it_revPassphrase = new DerObjectIdentifier("1.3.6.1.5.5.7.4.12");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.13
+         */
+        public static readonly DerObjectIdentifier it_implicitConfirm = new DerObjectIdentifier("1.3.6.1.5.5.7.4.13");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.14
+         */
+        public static readonly DerObjectIdentifier it_confirmWaitTime = new DerObjectIdentifier("1.3.6.1.5.5.7.4.14");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.15
+         */
+        public static readonly DerObjectIdentifier it_origPKIMessage = new DerObjectIdentifier("1.3.6.1.5.5.7.4.15");
+        /**
+         * RFC 4120: 1.3.6.1.5.5.7.4.16
+         */
+        public static readonly DerObjectIdentifier it_suppLangTags = new DerObjectIdentifier("1.3.6.1.5.5.7.4.16");
+
+        /**
+         * Update 16, RFC 4210
+         * {id-it 17}
+         */
+        public static readonly DerObjectIdentifier id_it_caCerts = new DerObjectIdentifier("1.3.6.1.5.5.7.4.17");
+
+
+        /**
+         * Update 16, RFC 4210
+         * GenRep:    {id-it 18}, RootCaKeyUpdateContent
+         */
+        public static readonly DerObjectIdentifier id_it_rootCaKeyUpdate = new DerObjectIdentifier("1.3.6.1.5.5.7.4.18");
+
+
+        /**
+         * Update 16, RFC 4210
+         * {id-it 19}
+         */
+        public static readonly DerObjectIdentifier id_it_certReqTemplate = new DerObjectIdentifier("1.3.6.1.5.5.7.4.19");
+
+
+        /**
+         * Update 16, RFC 4210
+         * GenMsg:    {id-it 20}, RootCaCertValue
+         */
+        public static readonly DerObjectIdentifier id_it_rootCaCert = new DerObjectIdentifier("1.3.6.1.5.5.7.4.20");
+
+        /**
+         * Update-16 to RFC 4210
+         * id-it-certProfile  OBJECT IDENTIFIER ::= {id-it 21}
+         */
+        public static readonly DerObjectIdentifier id_it_certProfile = new DerObjectIdentifier("1.3.6.1.5.5.7.4.21");
+
+        public static readonly DerObjectIdentifier id_it_crlStatusList = new DerObjectIdentifier("1.3.6.1.5.5.7.4.22");
+
+        public static readonly DerObjectIdentifier id_it_crls = new DerObjectIdentifier("1.3.6.1.5.5.7.4.23");
+
+        // Not yet formally defined.
+
+        //public static readonly DerObjectIdentifier id_it_crlStatusList = null;
+        //public static readonly DerObjectIdentifier id_it_crls = null;
+
+
+        // RFC 4211
+
+        // id-pkix  OBJECT IDENTIFIER  ::= { iso(1) identified-organization(3)
+        //     dod(6) internet(1) security(5) mechanisms(5) pkix(7) }
+        //
+        // arc for Internet X.509 PKI protocols and their components
+        // id-pkip  OBJECT IDENTIFIER :: { id-pkix pkip(5) }
+        //
+        // arc for Registration Controls in CRMF
+        // id-regCtrl  OBJECT IDENTIFIER ::= { id-pkip regCtrl(1) }
+        //
+        // arc for Registration Info in CRMF
+        // id-regInfo       OBJECT IDENTIFIER ::= { id-pkip id-regInfo(2) }
+
+        /**
+         * RFC 4211: it-pkip: PKIX.5 = 1.3.6.1.5.5.7.5
+         */
+        public static readonly DerObjectIdentifier id_pkip = new DerObjectIdentifier("1.3.6.1.5.5.7.5");
+
+        /**
+         * RFC 4211: it-regCtrl: 1.3.6.1.5.5.7.5.1
+         */
+        public static readonly DerObjectIdentifier id_regCtrl = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1");
+        /**
+         * RFC 4211: it-regInfo: 1.3.6.1.5.5.7.5.2
+         */
+        public static readonly DerObjectIdentifier id_regInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2");
+
+
+        /**
+         * 1.3.6.1.5.5.7.5.1.1
+         */
+        public static readonly DerObjectIdentifier regCtrl_regToken = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.1");
+        /**
+         * 1.3.6.1.5.5.7.5.1.2
+         */
+        public static readonly DerObjectIdentifier regCtrl_authenticator = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.2");
+        /**
+         * 1.3.6.1.5.5.7.5.1.3
+         */
+        public static readonly DerObjectIdentifier regCtrl_pkiPublicationInfo = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.3");
+        /**
+         * 1.3.6.1.5.5.7.5.1.4
+         */
+        public static readonly DerObjectIdentifier regCtrl_pkiArchiveOptions = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.4");
+        /**
+         * 1.3.6.1.5.5.7.5.1.5
+         */
+        public static readonly DerObjectIdentifier regCtrl_oldCertID = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.5");
+        /**
+         * 1.3.6.1.5.5.7.5.1.6
+         */
+        public static readonly DerObjectIdentifier regCtrl_protocolEncrKey = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.6");
+
+        /**
+         * From RFC4210:
+         * id-regCtrl-altCertTemplate OBJECT IDENTIFIER ::= {id-regCtrl 7}; 1.3.6.1.5.5.7.1.7
+         */
+        public static readonly DerObjectIdentifier regCtrl_altCertTemplate = new DerObjectIdentifier("1.3.6.1.5.5.7.5.1.7");
+
+        /**
+         * RFC 4211: it-regInfo-utf8Pairs: 1.3.6.1.5.5.7.5.2.1
+         */
+        public static readonly DerObjectIdentifier regInfo_utf8Pairs = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2.1");
+        /**
+         * RFC 4211: it-regInfo-certReq: 1.3.6.1.5.5.7.5.2.1
+         */
+        public static readonly DerObjectIdentifier regInfo_certReq = new DerObjectIdentifier("1.3.6.1.5.5.7.5.2.2");
+
+        /**
+         * 1.2.840.113549.1.9.16.1.21
+         * <p>
+         * id-ct   OBJECT IDENTIFIER ::= { id-smime  1 }  -- content types
+         * </p><p>
+         * id-ct-encKeyWithID OBJECT IDENTIFIER ::= {id-ct 21}
+         * </p>
+         */
+        public static readonly DerObjectIdentifier ct_encKeyWithID = new DerObjectIdentifier("1.2.840.113549.1.9.16.1.21");
+
+
+        /**
+         * id-regCtrl-algId OBJECT IDENTIFIER ::= { iso(1)
+         * identified-organization(3) dod(6) internet(1) security(5)
+         * mechanisms(5) pkix(7) pkip(5) regCtrl(1) 11 }
+         */
+        public static readonly DerObjectIdentifier id_regCtrl_algId = id_pkip.Branch("1.11");
+
+        /**
+         * id-regCtrl-rsaKeyLen OBJECT IDENTIFIER ::= { iso(1)
+         * identified-organization(3) dod(6) internet(1) security(5)
+         * mechanisms(5) pkix(7) pkip(5) regCtrl(1) 12 }
+         */
+        public static readonly DerObjectIdentifier id_regCtrl_rsaKeyLen = id_pkip.Branch("1.12");
 	}
 }
diff --git a/crypto/src/asn1/cmp/CrlAnnContent.cs b/crypto/src/asn1/cmp/CrlAnnContent.cs
index db8ecfa40..0da25cd0e 100644
--- a/crypto/src/asn1/cmp/CrlAnnContent.cs
+++ b/crypto/src/asn1/cmp/CrlAnnContent.cs
@@ -1,39 +1,36 @@
-using System;
-
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class CrlAnnContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
+        public static CrlAnnContent GetInstance(object obj)
+        {
+			if (obj is CrlAnnContent crlAnnContent)
+				return crlAnnContent;
 
-		private CrlAnnContent(Asn1Sequence seq)
-		{
-			content = seq;
-		}
+			if (obj != null)
+				return new CrlAnnContent(Asn1Sequence.GetInstance(obj));
 
-		public static CrlAnnContent GetInstance(object obj)
-		{
-			if (obj is CrlAnnContent)
-				return (CrlAnnContent)obj;
+			return null;
+        }
 
-			if (obj is Asn1Sequence)
-				return new CrlAnnContent((Asn1Sequence)obj);
+        private readonly Asn1Sequence m_content;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+		private CrlAnnContent(Asn1Sequence seq)
+		{
+			m_content = seq;
 		}
 
-		public virtual CertificateList[] ToCertificateListArray()
+        public CrlAnnContent(CertificateList crl)
+        {
+            m_content = new DerSequence(crl);
+        }
+
+        public virtual CertificateList[] ToCertificateListArray()
 		{
-			CertificateList[] result = new CertificateList[content.Count];
-			for (int i = 0; i != result.Length; ++ i)
-			{
-				result[i] = CertificateList.GetInstance(content[i]);
-			}
-			return result;
+			return m_content.MapElements(CertificateList.GetInstance);
 		}
 
 		/**
@@ -44,7 +41,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return content;
+			return m_content;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/CrlSource.cs b/crypto/src/asn1/cmp/CrlSource.cs
new file mode 100644
index 000000000..9e2526ec2
--- /dev/null
+++ b/crypto/src/asn1/cmp/CrlSource.cs
@@ -0,0 +1,72 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * GenMsg:    {id-it TBD1}, SEQUENCE SIZE (1..MAX) OF CRLStatus
+     * GenRep:    {id-it TBD2}, SEQUENCE SIZE (1..MAX) OF
+     * CertificateList  |  &lt; absent &gt;
+     * <p>
+     * CRLSource ::= CHOICE {
+     * dpn          [0] DistributionPointName,
+     * issuer       [1] GeneralNames }
+     * </p>
+     */
+    public class CrlSource
+        : Asn1Encodable, IAsn1Choice
+    {
+        public static CrlSource GetInstance(object obj)
+        {
+            if (obj is CrlSource crlSource)
+                return crlSource;
+
+            if (obj != null)
+                return new CrlSource(Asn1TaggedObject.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly DistributionPointName m_dpn;
+        private readonly GeneralNames m_issuer;
+
+        private CrlSource(Asn1TaggedObject taggedObject)
+        {
+            switch (taggedObject.TagNo)
+            {
+            case 0:
+                m_dpn = DistributionPointName.GetInstance(taggedObject, true);
+                m_issuer = null;
+                break;
+            case 1:
+                m_dpn = null;
+                m_issuer = GeneralNames.GetInstance(taggedObject, true);
+                break;
+            default:
+                throw new ArgumentException("unknown tag: " + Asn1Utilities.GetTagText(taggedObject));
+            }
+        }
+
+        public CrlSource(DistributionPointName dpn, GeneralNames issuer)
+        {
+            if ((dpn == null) == (issuer == null))
+                throw new ArgumentException("either dpn or issuer must be set");
+
+            m_dpn = dpn;
+            m_issuer = issuer;
+        }
+
+        public virtual DistributionPointName Dpn => m_dpn;
+
+        public virtual GeneralNames Issuer => m_issuer;
+
+        public override Asn1Object ToAsn1Object()
+        {
+            if (m_dpn != null)
+                return new DerTaggedObject(true, 0, m_dpn);
+
+            return new DerTaggedObject(true, 1, m_issuer);
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/CrlStatus.cs b/crypto/src/asn1/cmp/CrlStatus.cs
new file mode 100644
index 000000000..5bacbbbcc
--- /dev/null
+++ b/crypto/src/asn1/cmp/CrlStatus.cs
@@ -0,0 +1,61 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * CRLStatus ::= SEQUENCE {
+     * source       CRLSource,
+     * thisUpdate   Time OPTIONAL }
+     */
+    public class CrlStatus
+        : Asn1Encodable
+    {
+        public static CrlStatus GetInstance(object obj)
+        {
+            if (obj is CrlStatus crlStatus)
+                return crlStatus;
+
+            if (obj != null)
+                return new CrlStatus(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly CrlSource m_source;
+        private readonly Time m_thisUpdate;
+
+        private CrlStatus(Asn1Sequence sequence)
+        {
+            int count = sequence.Count;
+            if (count < 1 || count > 2)
+                throw new ArgumentException("expected sequence size of 1 or 2, got " + count);
+
+            m_source = CrlSource.GetInstance(sequence[0]);
+
+            if (sequence.Count == 2)
+            {
+                m_thisUpdate = Time.GetInstance(sequence[1]);
+            }
+        }
+
+        public CrlStatus(CrlSource source, Time thisUpdate)
+        {
+            m_source = source;
+            m_thisUpdate = thisUpdate;
+        }
+
+        public virtual CrlSource Source => m_source;
+
+        public virtual Time ThisUpdate => m_thisUpdate;
+
+        public override Asn1Object ToAsn1Object()
+        {
+            if (m_thisUpdate == null)
+                return new DerSequence(m_source);
+
+            return new DerSequence(m_source, m_thisUpdate);
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/DhbmParameter.cs b/crypto/src/asn1/cmp/DhbmParameter.cs
new file mode 100644
index 000000000..aaf71f70e
--- /dev/null
+++ b/crypto/src/asn1/cmp/DhbmParameter.cs
@@ -0,0 +1,56 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * DHBMParameter ::= SEQUENCE {
+     * owf                 AlgorithmIdentifier,
+     * -- AlgId for a One-Way Function (SHA-1 recommended)
+     * mac                 AlgorithmIdentifier
+     * -- the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11],
+     * }   -- or HMAC [RFC2104, RFC2202])
+     */
+    public class DhbmParameter
+        : Asn1Encodable
+    {
+        public static DhbmParameter GetInstance(object obj)
+        {
+            if (obj is DhbmParameter dhbmParameter)
+                return dhbmParameter;
+
+            if (obj != null)
+                return new DhbmParameter(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly AlgorithmIdentifier m_owf;
+        private readonly AlgorithmIdentifier m_mac;
+
+        private DhbmParameter(Asn1Sequence sequence)
+        {
+            if (sequence.Count != 2)
+                throw new ArgumentException("expecting sequence size of 2");
+
+            m_owf = AlgorithmIdentifier.GetInstance(sequence[0]);
+            m_mac = AlgorithmIdentifier.GetInstance(sequence[1]);
+        }
+
+        public DhbmParameter(AlgorithmIdentifier owf, AlgorithmIdentifier mac)
+        {
+            m_owf = owf;
+            m_mac = mac;
+        }
+
+        public virtual AlgorithmIdentifier Owf => m_owf;
+
+        public virtual AlgorithmIdentifier Mac => m_mac;
+
+        public override Asn1Object ToAsn1Object()
+        {
+            return new DerSequence(m_owf, m_mac);
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/ErrorMsgContent.cs b/crypto/src/asn1/cmp/ErrorMsgContent.cs
index 5d2132bb8..fe8318aab 100644
--- a/crypto/src/asn1/cmp/ErrorMsgContent.cs
+++ b/crypto/src/asn1/cmp/ErrorMsgContent.cs
@@ -1,45 +1,54 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class ErrorMsgContent
+    /**
+     * <pre>
+     *      ErrorMsgContent ::= SEQUENCE {
+     *          pKIStatusInfo          PKIStatusInfo,
+     *          errorCode              INTEGER           OPTIONAL,
+     *          -- implementation-specific error codes
+     *          errorDetails           PKIFreeText       OPTIONAL
+     *          -- implementation-specific error details
+     *      }
+     * </pre>
+     */
+    public class ErrorMsgContent
 		: Asn1Encodable
 	{
-		private readonly PkiStatusInfo pkiStatusInfo;
-		private readonly DerInteger errorCode;
-		private readonly PkiFreeText errorDetails;
+        public static ErrorMsgContent GetInstance(object obj)
+        {
+            if (obj is ErrorMsgContent errorMsgContent)
+                return errorMsgContent;
+
+            if (obj != null)
+                return new ErrorMsgContent(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly PkiStatusInfo m_pkiStatusInfo;
+		private readonly DerInteger m_errorCode;
+		private readonly PkiFreeText m_errorDetails;
 
 		private ErrorMsgContent(Asn1Sequence seq)
 		{
-			pkiStatusInfo = PkiStatusInfo.GetInstance(seq[0]);
+			m_pkiStatusInfo = PkiStatusInfo.GetInstance(seq[0]);
 
 			for (int pos = 1; pos < seq.Count; ++pos)
 			{
 				Asn1Encodable ae = seq[pos];
 				if (ae is DerInteger)
 				{
-					errorCode = DerInteger.GetInstance(ae);
+					m_errorCode = DerInteger.GetInstance(ae);
 				}
 				else
 				{
-					errorDetails = PkiFreeText.GetInstance(ae);
+					m_errorDetails = PkiFreeText.GetInstance(ae);
 				}
 			}
 		}
 
-		public static ErrorMsgContent GetInstance(object obj)
-		{
-			if (obj is ErrorMsgContent)
-				return (ErrorMsgContent)obj;
-
-			if (obj is Asn1Sequence)
-				return new ErrorMsgContent((Asn1Sequence)obj);
-
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
-
 		public ErrorMsgContent(PkiStatusInfo pkiStatusInfo)
 			: this(pkiStatusInfo, null, null)
 		{
@@ -51,27 +60,18 @@ namespace Org.BouncyCastle.Asn1.Cmp
 			PkiFreeText		errorDetails)
 		{
 			if (pkiStatusInfo == null)
-				throw new ArgumentNullException("pkiStatusInfo");
+				throw new ArgumentNullException(nameof(pkiStatusInfo));
 
-			this.pkiStatusInfo = pkiStatusInfo;
-			this.errorCode = errorCode;
-			this.errorDetails = errorDetails;
-		}
-		
-		public virtual PkiStatusInfo PkiStatusInfo
-		{
-			get { return pkiStatusInfo; }
+			m_pkiStatusInfo = pkiStatusInfo;
+			m_errorCode = errorCode;
+			m_errorDetails = errorDetails;
 		}
 
-		public virtual DerInteger ErrorCode
-		{
-			get { return errorCode; }
-		}
+		public virtual PkiStatusInfo PkiStatusInfo => m_pkiStatusInfo;
 
-		public virtual PkiFreeText ErrorDetails
-		{
-			get { return errorDetails; }
-		}
+		public virtual DerInteger ErrorCode => m_errorCode;
+
+		public virtual PkiFreeText ErrorDetails => m_errorDetails;
 
 		/**
 		 * <pre>
@@ -87,8 +87,8 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(pkiStatusInfo);
-			v.AddOptional(errorCode, errorDetails);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_pkiStatusInfo);
+			v.AddOptional(m_errorCode, m_errorDetails);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/GenMsgContent.cs b/crypto/src/asn1/cmp/GenMsgContent.cs
index f3142b5c6..b4673b76a 100644
--- a/crypto/src/asn1/cmp/GenMsgContent.cs
+++ b/crypto/src/asn1/cmp/GenMsgContent.cs
@@ -1,43 +1,42 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class GenMsgContent
+    /**
+     * <pre>GenMsgContent ::= SEQUENCE OF InfoTypeAndValue</pre>
+     */
+    public class GenMsgContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
+        public static GenMsgContent GetInstance(object obj)
+        {
+			if (obj is GenMsgContent genMsgContent)
+				return genMsgContent;
 
-		private GenMsgContent(Asn1Sequence seq)
-		{
-			content = seq;
-		}
+			if (obj != null)
+				return new GenMsgContent(Asn1Sequence.GetInstance(obj));
 
-		public static GenMsgContent GetInstance(object obj)
-		{
-			if (obj is GenMsgContent)
-				return (GenMsgContent)obj;
+			return null;
+        }
 
-			if (obj is Asn1Sequence)
-				return new GenMsgContent((Asn1Sequence)obj);
+        private readonly Asn1Sequence m_content;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+		private GenMsgContent(Asn1Sequence seq)
+		{
+			m_content = seq;
 		}
 
-		public GenMsgContent(params InfoTypeAndValue[] itv)
+        public GenMsgContent(InfoTypeAndValue itv)
+        {
+            m_content = new DerSequence(itv);
+        }
+
+        public GenMsgContent(params InfoTypeAndValue[] itvs)
 		{
-			content = new DerSequence(itv);
+			m_content = new DerSequence(itvs);
 		}
 
 		public virtual InfoTypeAndValue[] ToInfoTypeAndValueArray()
 		{
-			InfoTypeAndValue[] result = new InfoTypeAndValue[content.Count];
-			for (int i = 0; i != result.Length; ++i)
-			{
-				result[i] = InfoTypeAndValue.GetInstance(content[i]);
-			}
-			return result;
+			return m_content.MapElements(InfoTypeAndValue.GetInstance);
 		}
 
 		/**
@@ -48,7 +47,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return content;
+			return m_content;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/GenRepContent.cs b/crypto/src/asn1/cmp/GenRepContent.cs
index 3c3573e37..38f91061c 100644
--- a/crypto/src/asn1/cmp/GenRepContent.cs
+++ b/crypto/src/asn1/cmp/GenRepContent.cs
@@ -1,43 +1,39 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class GenRepContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
+        public static GenRepContent GetInstance(object obj)
+        {
+            if (obj is GenRepContent genRepContent)
+                return genRepContent;
 
-		private GenRepContent(Asn1Sequence seq)
-		{
-			content = seq;
-		}
+            if (obj != null)
+                return new GenRepContent(Asn1Sequence.GetInstance(obj));
 
-		public static GenRepContent GetInstance(object obj)
-		{
-			if (obj is GenRepContent)
-				return (GenRepContent)obj;
+            return null;
+        }
 
-			if (obj is Asn1Sequence)
-				return new GenRepContent((Asn1Sequence)obj);
+        private readonly Asn1Sequence m_content;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+		private GenRepContent(Asn1Sequence seq)
+		{
+			m_content = seq;
 		}
 
-		public GenRepContent(params InfoTypeAndValue[] itv)
+        public GenRepContent(InfoTypeAndValue itv)
+        {
+            m_content = new DerSequence(itv);
+        }
+
+        public GenRepContent(params InfoTypeAndValue[] itvs)
 		{
-			content = new DerSequence(itv);
+			m_content = new DerSequence(itvs);
 		}
 
 		public virtual InfoTypeAndValue[] ToInfoTypeAndValueArray()
 		{
-			InfoTypeAndValue[] result = new InfoTypeAndValue[content.Count];
-			for (int i = 0; i != result.Length; ++i)
-			{
-				result[i] = InfoTypeAndValue.GetInstance(content[i]);
-			}
-			return result;
+            return m_content.MapElements(InfoTypeAndValue.GetInstance);
 		}
 
 		/**
@@ -48,7 +44,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return content;
+			return m_content;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/InfoTypeAndValue.cs b/crypto/src/asn1/cmp/InfoTypeAndValue.cs
index 305d6e5e7..08ad68a42 100644
--- a/crypto/src/asn1/cmp/InfoTypeAndValue.cs
+++ b/crypto/src/asn1/cmp/InfoTypeAndValue.cs
@@ -50,54 +50,47 @@ namespace Org.BouncyCastle.Asn1.Cmp
     public class InfoTypeAndValue
         : Asn1Encodable
     {
-        private readonly DerObjectIdentifier infoType;
-        private readonly Asn1Encodable infoValue;
+        private readonly DerObjectIdentifier m_infoType;
+        private readonly Asn1Encodable m_infoValue;
 
         private InfoTypeAndValue(Asn1Sequence seq)
         {
-            infoType = DerObjectIdentifier.GetInstance(seq[0]);
+            m_infoType = DerObjectIdentifier.GetInstance(seq[0]);
 
             if (seq.Count > 1)
             {
-                infoValue = (Asn1Encodable)seq[1];
+                m_infoValue = seq[1];
             }
         }
 
         public static InfoTypeAndValue GetInstance(object obj)
         {
-            if (obj is InfoTypeAndValue)
-                return (InfoTypeAndValue)obj;
+            if (obj is InfoTypeAndValue infoTypeAndValue)
+                return infoTypeAndValue;
 
-            if (obj is Asn1Sequence)
-                return new InfoTypeAndValue((Asn1Sequence)obj);
+            if (obj != null)
+                return new InfoTypeAndValue(Asn1Sequence.GetInstance(obj));
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+            return null;
         }
 
-        public InfoTypeAndValue(
-            DerObjectIdentifier infoType)
+        public InfoTypeAndValue(DerObjectIdentifier infoType)
+            : this(infoType, null)
         {
-            this.infoType = infoType;
-            this.infoValue = null;
         }
 
-        public InfoTypeAndValue(
-            DerObjectIdentifier infoType,
-            Asn1Encodable       optionalValue)
+        public InfoTypeAndValue(DerObjectIdentifier infoType, Asn1Encodable infoValue)
         {
-            this.infoType = infoType;
-            this.infoValue = optionalValue;
-        }
+            if (infoType == null)
+                throw new ArgumentNullException(nameof(infoType));
 
-        public virtual DerObjectIdentifier InfoType
-        {
-            get { return infoType; }
+            m_infoType = infoType;
+            m_infoValue = infoValue;
         }
 
-        public virtual Asn1Encodable InfoValue
-        {
-            get { return infoValue; }
-        }
+        public virtual DerObjectIdentifier InfoType => m_infoType;
+
+        public virtual Asn1Encodable InfoValue => m_infoValue;
 
         /**
          * <pre>
@@ -110,9 +103,10 @@ namespace Org.BouncyCastle.Asn1.Cmp
          */
         public override Asn1Object ToAsn1Object()
         {
-            Asn1EncodableVector v = new Asn1EncodableVector(infoType);
-            v.AddOptional(infoValue);
-            return new DerSequence(v);
+            if (m_infoValue == null)
+                return new DerSequence(m_infoType);
+
+            return new DerSequence(m_infoType, m_infoValue);
         }
     }
 }
diff --git a/crypto/src/asn1/cmp/KeyRecRepContent.cs b/crypto/src/asn1/cmp/KeyRecRepContent.cs
index e35c0e351..6c5ef62f2 100644
--- a/crypto/src/asn1/cmp/KeyRecRepContent.cs
+++ b/crypto/src/asn1/cmp/KeyRecRepContent.cs
@@ -1,20 +1,29 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class KeyRecRepContent
 		: Asn1Encodable
 	{
-		private readonly PkiStatusInfo status;
-		private readonly CmpCertificate newSigCert;
-		private readonly Asn1Sequence caCerts;
-		private readonly Asn1Sequence keyPairHist;
+        public static KeyRecRepContent GetInstance(object obj)
+        {
+			if (obj is KeyRecRepContent keyRecRepContent)
+				return keyRecRepContent;
+
+			if (obj != null)
+				return new KeyRecRepContent(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly PkiStatusInfo m_status;
+		private readonly CmpCertificate m_newSigCert;
+		private readonly Asn1Sequence m_caCerts;
+		private readonly Asn1Sequence m_keyPairHist;
 
 		private KeyRecRepContent(Asn1Sequence seq)
 		{
-			status = PkiStatusInfo.GetInstance(seq[0]);
+			m_status = PkiStatusInfo.GetInstance(seq[0]);
 
 			for (int pos = 1; pos < seq.Count; ++pos)
 			{
@@ -22,66 +31,39 @@ namespace Org.BouncyCastle.Asn1.Cmp
 
 				switch (tObj.TagNo)
 				{
-					case 0:
-						newSigCert = CmpCertificate.GetInstance(tObj.GetObject());
-						break;
-					case 1:
-						caCerts = Asn1Sequence.GetInstance(tObj.GetObject());
-						break;
-					case 2:
-						keyPairHist = Asn1Sequence.GetInstance(tObj.GetObject());
-						break;
-					default:
-						throw new ArgumentException("unknown tag number: " + tObj.TagNo, "seq");
+				case 0:
+					m_newSigCert = CmpCertificate.GetInstance(tObj.GetObject());
+					break;
+				case 1:
+					m_caCerts = Asn1Sequence.GetInstance(tObj.GetObject());
+					break;
+				case 2:
+					m_keyPairHist = Asn1Sequence.GetInstance(tObj.GetObject());
+					break;
+				default:
+					throw new ArgumentException("unknown tag number: " + tObj.TagNo, "seq");
 				}
 			}
 		}
 
-		public static KeyRecRepContent GetInstance(object obj)
-		{
-			if (obj is KeyRecRepContent)
-				return (KeyRecRepContent)obj;
+		public virtual PkiStatusInfo Status => m_status;
 
-			if (obj is Asn1Sequence)
-				return new KeyRecRepContent((Asn1Sequence)obj);
-
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
-
-		public virtual PkiStatusInfo Status
-		{
-			get { return status; }
-		}
-
-		public virtual CmpCertificate NewSigCert
-		{
-			get { return newSigCert; }
-		}
+		public virtual CmpCertificate NewSigCert => m_newSigCert;
 
 		public virtual CmpCertificate[] GetCACerts()
 		{
-			if (caCerts == null)
+			if (m_caCerts == null)
 				return null;
 
-			CmpCertificate[] results = new CmpCertificate[caCerts.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = CmpCertificate.GetInstance(caCerts[i]);
-			}
-			return results;
+			return m_caCerts.MapElements(CmpCertificate.GetInstance);
 		}
 
 		public virtual CertifiedKeyPair[] GetKeyPairHist()
 		{
-			if (keyPairHist == null)
+			if (m_keyPairHist == null)
 				return null;
 
-			CertifiedKeyPair[] results = new CertifiedKeyPair[keyPairHist.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = CertifiedKeyPair.GetInstance(keyPairHist[i]);
-			}
-			return results;
+			return m_keyPairHist.MapElements(CertifiedKeyPair.GetInstance);
 		}
 
 		/**
@@ -99,10 +81,10 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(status);
-            v.AddOptionalTagged(true, 0, newSigCert);
-            v.AddOptionalTagged(true, 1, caCerts);
-            v.AddOptionalTagged(true, 2, keyPairHist);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_status);
+            v.AddOptionalTagged(true, 0, m_newSigCert);
+            v.AddOptionalTagged(true, 1, m_caCerts);
+            v.AddOptionalTagged(true, 2, m_keyPairHist);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/NestedMessageContent.cs b/crypto/src/asn1/cmp/NestedMessageContent.cs
new file mode 100644
index 000000000..0cb2c080b
--- /dev/null
+++ b/crypto/src/asn1/cmp/NestedMessageContent.cs
@@ -0,0 +1,35 @@
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * NestedMessageContent ::= PKIMessages
+     */
+    public class NestedMessageContent
+        : PkiMessages
+    {
+        public static new NestedMessageContent GetInstance(object obj)
+        {
+            if (obj is NestedMessageContent nestedMessageContent)
+                return nestedMessageContent;
+
+            if (obj != null)
+                return new NestedMessageContent(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        public NestedMessageContent(PkiMessage msg)
+            : base(msg)
+        {
+        }
+
+        public NestedMessageContent(PkiMessage[] msgs)
+            : base(msgs)
+        {
+        }
+
+        public NestedMessageContent(Asn1Sequence seq)
+            : base(seq)
+        {
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/OobCert.cs b/crypto/src/asn1/cmp/OobCert.cs
new file mode 100644
index 000000000..82d5afe55
--- /dev/null
+++ b/crypto/src/asn1/cmp/OobCert.cs
@@ -0,0 +1,68 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * OOBCert ::= CMPCertificate
+     */
+    public class OobCert
+        : CmpCertificate
+    {
+        public static new OobCert GetInstance(object obj)
+        {
+            if (obj == null)
+                return null;
+
+            if (obj is OobCert oobCert)
+                return oobCert;
+
+            if (obj is CmpCertificate cmpCertificate)
+                return GetInstance(cmpCertificate.GetEncoded());
+
+            if (obj is byte[] bs)
+            {
+                try
+                {
+                    obj = Asn1Object.FromByteArray(bs);
+                }
+                catch (IOException)
+                {
+                    throw new ArgumentException("Invalid encoding in OobCert");
+                }
+            }
+
+            if (obj is Asn1Sequence seq)
+                return new OobCert(X509CertificateStructure.GetInstance(obj));
+
+            if (obj is Asn1TaggedObject taggedObject)
+                return new OobCert(taggedObject.TagNo, taggedObject.GetObject());
+
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
+        }
+
+        public static new OobCert GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+        {
+            if (taggedObject == null)
+                return null;
+
+            if (!declaredExplicit)
+                throw new ArgumentException("tag must be explicit");
+
+            return GetInstance(taggedObject.GetObject());
+        }
+
+        public OobCert(int type, Asn1Encodable otherCert)
+            : base(type, otherCert)
+        {
+        }
+
+        public OobCert(X509CertificateStructure x509v3PKCert)
+            : base(x509v3PKCert)
+        {
+        }
+    }
+}
diff --git a/crypto/src/asn1/cmp/OobCertHash.cs b/crypto/src/asn1/cmp/OobCertHash.cs
index 434939c0e..a18ff300d 100644
--- a/crypto/src/asn1/cmp/OobCertHash.cs
+++ b/crypto/src/asn1/cmp/OobCertHash.cs
@@ -6,18 +6,40 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class OobCertHash
+    /**
+     * <pre>
+     * OOBCertHash ::= SEQUENCE {
+     * hashAlg     [0] AlgorithmIdentifier     OPTIONAL,
+     * certId      [1] CertId                  OPTIONAL,
+     * hashVal         BIT STRING
+     * -- hashVal is calculated over the DER encoding of the
+     * -- self-signed certificate with the identifier certID.
+     * }
+     * </pre>
+     */
+    public class OobCertHash
 		: Asn1Encodable
 	{
-		private readonly AlgorithmIdentifier hashAlg;
-		private readonly CertId certId;
-		private readonly DerBitString  hashVal;
+        public static OobCertHash GetInstance(object obj)
+        {
+			if (obj is OobCertHash oobCertHash)
+				return oobCertHash;
+
+			if (obj != null)
+				return new OobCertHash(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly AlgorithmIdentifier m_hashAlg;
+		private readonly CertId m_certId;
+		private readonly DerBitString m_hashVal;
 
 		private OobCertHash(Asn1Sequence seq)
 		{
 			int index = seq.Count - 1;
 
-			hashVal = DerBitString.GetInstance(seq[index--]);
+			m_hashVal = DerBitString.GetInstance(seq[index--]);
 
 			for (int i = index; i >= 0; i--)
 			{
@@ -25,36 +47,21 @@ namespace Org.BouncyCastle.Asn1.Cmp
 
 				if (tObj.TagNo == 0)
 				{
-					hashAlg = AlgorithmIdentifier.GetInstance(tObj, true);
+					m_hashAlg = AlgorithmIdentifier.GetInstance(tObj, true);
 				}
 				else
 				{
-					certId = CertId.GetInstance(tObj, true);
+					m_certId = CertId.GetInstance(tObj, true);
 				}
 			}
 		}
 
-		public static OobCertHash GetInstance(object obj)
-		{
-			if (obj is OobCertHash)
-				return (OobCertHash)obj;
+		public virtual CertId CertID => m_certId;
 
-			if (obj is Asn1Sequence)
-				return new OobCertHash((Asn1Sequence)obj);
+        public virtual AlgorithmIdentifier HashAlg => m_hashAlg;
+
+		public virtual DerBitString HashVal => m_hashVal;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
-		
-		public virtual AlgorithmIdentifier HashAlg
-		{
-			get { return hashAlg; }
-		}
-		
-		public virtual CertId CertID
-		{
-			get { return certId; }
-		}
-		
 		/**
 		 * <pre>
 		 * OobCertHash ::= SEQUENCE {
@@ -70,9 +77,9 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		public override Asn1Object ToAsn1Object()
 		{
 			Asn1EncodableVector v = new Asn1EncodableVector();
-            v.AddOptionalTagged(true, 0, hashAlg);
-            v.AddOptionalTagged(true, 1, certId);
-			v.Add(hashVal);
+            v.AddOptionalTagged(true, 0, m_hashAlg);
+            v.AddOptionalTagged(true, 1, m_certId);
+			v.Add(m_hashVal);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/PKIBody.cs b/crypto/src/asn1/cmp/PKIBody.cs
index f17eed64d..68f63ab0b 100644
--- a/crypto/src/asn1/cmp/PKIBody.cs
+++ b/crypto/src/asn1/cmp/PKIBody.cs
@@ -6,6 +6,37 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
+    /**
+     * PKIBody ::= CHOICE {       -- message-specific body elements
+     *          ir       [0]  CertReqMessages,        --Initialization Request
+     *          ip       [1]  CertRepMessage,         --Initialization Response
+     *          cr       [2]  CertReqMessages,        --Certification Request
+     *          cp       [3]  CertRepMessage,         --Certification Response
+     *          p10cr    [4]  CertificationRequest,   --imported from [PKCS10]
+     *          popdecc  [5]  POPODecKeyChallContent, --pop Challenge
+     *          popdecr  [6]  POPODecKeyRespContent,  --pop Response
+     *          kur      [7]  CertReqMessages,        --Key Update Request
+     *          kup      [8]  CertRepMessage,         --Key Update Response
+     *          krr      [9]  CertReqMessages,        --Key Recovery Request
+     *          krp      [10] KeyRecRepContent,       --Key Recovery Response
+     *          rr       [11] RevReqContent,          --Revocation Request
+     *          rp       [12] RevRepContent,          --Revocation Response
+     *          ccr      [13] CertReqMessages,        --Cross-Cert. Request
+     *          ccp      [14] CertRepMessage,         --Cross-Cert. Response
+     *          ckuann   [15] CAKeyUpdAnnContent,     --CA Key Update Ann.
+     *          cann     [16] CertAnnContent,         --Certificate Ann.
+     *          rann     [17] RevAnnContent,          --Revocation Ann.
+     *          crlann   [18] CRLAnnContent,          --CRL Announcement
+     *          pkiconf  [19] PKIConfirmContent,      --Confirmation
+     *          nested   [20] NestedMessageContent,   --Nested Message
+     *          genm     [21] GenMsgContent,          --General Message
+     *          genp     [22] GenRepContent,          --General Response
+     *          error    [23] ErrorMsgContent,        --Error Message
+     *          certConf [24] CertConfirmContent,     --Certificate confirm
+     *          pollReq  [25] PollReqContent,         --Polling request
+     *          pollRep  [26] PollRepContent          --Polling response
+     *      }
+     */
     public class PkiBody
         : Asn1Encodable, IAsn1Choice
     {
@@ -37,24 +68,27 @@ namespace Org.BouncyCastle.Asn1.Cmp
         public const int TYPE_POLL_REQ = 25;
         public const int TYPE_POLL_REP = 26;
 
-        private int tagNo;
-        private Asn1Encodable body;
-
         public static PkiBody GetInstance(object obj)
         {
-            if (obj is PkiBody)
-                return (PkiBody)obj;
+            if (obj == null)
+                return null;
+
+            if (obj is PkiBody pkiBody)
+                return pkiBody;
 
-            if (obj is Asn1TaggedObject)
-                return new PkiBody((Asn1TaggedObject)obj);
+            if (obj is Asn1TaggedObject taggedObject)
+                return new PkiBody(taggedObject);
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
         }
 
-        private PkiBody(Asn1TaggedObject tagged)
+        private readonly int m_tagNo;
+        private readonly Asn1Encodable m_body;
+
+        private PkiBody(Asn1TaggedObject taggedObject)
         {
-            tagNo = tagged.TagNo;
-            body = GetBodyForType(tagNo, tagged.GetObject());
+            m_tagNo = taggedObject.TagNo;
+            m_body = GetBodyForType(m_tagNo, taggedObject.GetObject());
         }
 
         /**
@@ -62,88 +96,78 @@ namespace Org.BouncyCastle.Asn1.Cmp
          * @param type one of the TYPE_* constants
          * @param content message content
          */
-        public PkiBody(
-            int type,
-            Asn1Encodable content)
+        public PkiBody(int type, Asn1Encodable content)
         {
-            tagNo = type;
-            body = GetBodyForType(type, content);
+            m_tagNo = type;
+            m_body = GetBodyForType(type, content);
         }
 
-        private static Asn1Encodable GetBodyForType(
-            int type,
-            Asn1Encodable o)
+        private static Asn1Encodable GetBodyForType(int type, Asn1Encodable o)
         {
             switch (type)
             {
-                case TYPE_INIT_REQ:
-                    return CertReqMessages.GetInstance(o);
-	            case TYPE_INIT_REP:
-	                return CertRepMessage.GetInstance(o);
-                case TYPE_CERT_REQ:
-                    return CertReqMessages.GetInstance(o);
-	            case TYPE_CERT_REP:
-	                return CertRepMessage.GetInstance(o);
-	            case TYPE_P10_CERT_REQ:
-	                return CertificationRequest.GetInstance(o);
-	            case TYPE_POPO_CHALL:
-	                return PopoDecKeyChallContent.GetInstance(o);
-	            case TYPE_POPO_REP:
-	                return PopoDecKeyRespContent.GetInstance(o);
-                case TYPE_KEY_UPDATE_REQ:
-                    return CertReqMessages.GetInstance(o);
-	            case TYPE_KEY_UPDATE_REP:
-	                return CertRepMessage.GetInstance(o);
-                case TYPE_KEY_RECOVERY_REQ:
-                    return CertReqMessages.GetInstance(o);
-	            case TYPE_KEY_RECOVERY_REP:
-	                return KeyRecRepContent.GetInstance(o);
-	            case TYPE_REVOCATION_REQ:
-	                return RevReqContent.GetInstance(o);
-	            case TYPE_REVOCATION_REP:
-	                return RevRepContent.GetInstance(o);
-                case TYPE_CROSS_CERT_REQ:
-                    return CertReqMessages.GetInstance(o);
-	            case TYPE_CROSS_CERT_REP:
-	                return CertRepMessage.GetInstance(o);
-	            case TYPE_CA_KEY_UPDATE_ANN:
-	                return CAKeyUpdAnnContent.GetInstance(o);
-	            case TYPE_CERT_ANN:
-	                return CmpCertificate.GetInstance(o);
-	            case TYPE_REVOCATION_ANN:
-	                return RevAnnContent.GetInstance(o);
-	            case TYPE_CRL_ANN:
-	                return CrlAnnContent.GetInstance(o);
-	            case TYPE_CONFIRM:
-	                return PkiConfirmContent.GetInstance(o);
-                case TYPE_NESTED:
-                    return PkiMessages.GetInstance(o);
-	            case TYPE_GEN_MSG:
-	                return GenMsgContent.GetInstance(o);
-	            case TYPE_GEN_REP:
-	                return GenRepContent.GetInstance(o);
-	            case TYPE_ERROR:
-	                return ErrorMsgContent.GetInstance(o);
-	            case TYPE_CERT_CONFIRM:
-	                return CertConfirmContent.GetInstance(o);
-	            case TYPE_POLL_REQ:
-	                return PollReqContent.GetInstance(o);
-	            case TYPE_POLL_REP:
-	                return PollRepContent.GetInstance(o);
-	            default:
-	                throw new ArgumentException("unknown tag number: " + type, "type");
+            case TYPE_INIT_REQ:
+                return CertReqMessages.GetInstance(o);
+            case TYPE_INIT_REP:
+                return CertRepMessage.GetInstance(o);
+            case TYPE_CERT_REQ:
+                return CertReqMessages.GetInstance(o);
+            case TYPE_CERT_REP:
+                return CertRepMessage.GetInstance(o);
+            case TYPE_P10_CERT_REQ:
+                return CertificationRequest.GetInstance(o);
+            case TYPE_POPO_CHALL:
+                return PopoDecKeyChallContent.GetInstance(o);
+            case TYPE_POPO_REP:
+                return PopoDecKeyRespContent.GetInstance(o);
+            case TYPE_KEY_UPDATE_REQ:
+                return CertReqMessages.GetInstance(o);
+            case TYPE_KEY_UPDATE_REP:
+                return CertRepMessage.GetInstance(o);
+            case TYPE_KEY_RECOVERY_REQ:
+                return CertReqMessages.GetInstance(o);
+            case TYPE_KEY_RECOVERY_REP:
+                return KeyRecRepContent.GetInstance(o);
+            case TYPE_REVOCATION_REQ:
+                return RevReqContent.GetInstance(o);
+            case TYPE_REVOCATION_REP:
+                return RevRepContent.GetInstance(o);
+            case TYPE_CROSS_CERT_REQ:
+                return CertReqMessages.GetInstance(o);
+            case TYPE_CROSS_CERT_REP:
+                return CertRepMessage.GetInstance(o);
+            case TYPE_CA_KEY_UPDATE_ANN:
+                return CAKeyUpdAnnContent.GetInstance(o);
+            case TYPE_CERT_ANN:
+                return CmpCertificate.GetInstance(o);
+            case TYPE_REVOCATION_ANN:
+                return RevAnnContent.GetInstance(o);
+            case TYPE_CRL_ANN:
+                return CrlAnnContent.GetInstance(o);
+            case TYPE_CONFIRM:
+                return PkiConfirmContent.GetInstance(o);
+            case TYPE_NESTED:
+                return PkiMessages.GetInstance(o);
+            case TYPE_GEN_MSG:
+                return GenMsgContent.GetInstance(o);
+            case TYPE_GEN_REP:
+                return GenRepContent.GetInstance(o);
+            case TYPE_ERROR:
+                return ErrorMsgContent.GetInstance(o);
+            case TYPE_CERT_CONFIRM:
+                return CertConfirmContent.GetInstance(o);
+            case TYPE_POLL_REQ:
+                return PollReqContent.GetInstance(o);
+            case TYPE_POLL_REP:
+                return PollRepContent.GetInstance(o);
+            default:
+	            throw new ArgumentException("unknown tag number: " + type, nameof(type));
             }
         }
 
-        public virtual int Type
-        {
-            get { return tagNo; }
-        }
+        public virtual Asn1Encodable Content => m_body;
 
-        public virtual Asn1Encodable Content
-        {
-            get { return body; }
-        }
+        public virtual int Type => m_tagNo;
 
         /**
          * <pre>
@@ -181,7 +205,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
          */
         public override Asn1Object ToAsn1Object()
         {
-            return new DerTaggedObject(true, tagNo, body);
+            return new DerTaggedObject(true, m_tagNo, m_body);
         }
     }
 }
diff --git a/crypto/src/asn1/cmp/PKIConfirmContent.cs b/crypto/src/asn1/cmp/PKIConfirmContent.cs
index d154427a4..ecebb22a8 100644
--- a/crypto/src/asn1/cmp/PKIConfirmContent.cs
+++ b/crypto/src/asn1/cmp/PKIConfirmContent.cs
@@ -4,24 +4,38 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class PkiConfirmContent
+    /**
+     *  PKIConfirmContent ::= NULL
+     */
+    public class PkiConfirmContent
 		: Asn1Encodable
 	{
 		public static PkiConfirmContent GetInstance(object obj)
 		{
-			if (obj is PkiConfirmContent)
-				return (PkiConfirmContent)obj;
+			if (obj == null)
+				return null;
 
-			if (obj is Asn1Null)
-				return new PkiConfirmContent();
+			if (obj is PkiConfirmContent pkiConfirmContent)
+				return pkiConfirmContent;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
+			if (obj is Asn1Null asn1Null)
+				return new PkiConfirmContent(asn1Null);
 
-		public PkiConfirmContent()
-		{
+            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), nameof(obj));
 		}
 
+        private readonly Asn1Null m_val;
+
+        public PkiConfirmContent()
+            : this(DerNull.Instance)
+        {
+        }
+
+        private PkiConfirmContent(Asn1Null val)
+        {
+            m_val = val;
+        }
+
 		/**
 		 * <pre>
 		 * PkiConfirmContent ::= NULL
@@ -30,7 +44,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return DerNull.Instance;
+			return m_val;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/PKIFailureInfo.cs b/crypto/src/asn1/cmp/PKIFailureInfo.cs
index 75a3ff0d7..fd37665b9 100644
--- a/crypto/src/asn1/cmp/PKIFailureInfo.cs
+++ b/crypto/src/asn1/cmp/PKIFailureInfo.cs
@@ -21,7 +21,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
      * certRevoked         (10),
      * certConfirmed       (11),
      * wrongIntegrity      (12),
-     * badRecipientNonce   (13), 
+     * badRecipientNonce   (13),
      * timeNotAvailable    (14),
      *   -- the TSA's time source is not available
      * unacceptedPolicy    (15),
@@ -37,13 +37,13 @@ namespace Org.BouncyCastle.Asn1.Cmp
      * transactionIdInUse  (21),
      * unsupportedVersion  (22),
      * notAuthorized       (23),
-     * systemUnavail       (24),    
+     * systemUnavail       (24),
      * systemFailure       (25),
      *   -- the request cannot be handled due to system failure
-     * duplicateCertReq    (26) 
+     * duplicateCertReq    (26)
      * </pre>
      */
-	public class PkiFailureInfo
+    public class PkiFailureInfo
 		: DerBitString
 	{
         public const int BadAlg               = (1 << 7); // unrecognized or unsupported Algorithm Identifier
diff --git a/crypto/src/asn1/cmp/PKIFreeText.cs b/crypto/src/asn1/cmp/PKIFreeText.cs
index 006930320..f3a4b8a81 100644
--- a/crypto/src/asn1/cmp/PKIFreeText.cs
+++ b/crypto/src/asn1/cmp/PKIFreeText.cs
@@ -1,61 +1,66 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class PkiFreeText
 		: Asn1Encodable
 	{
-		internal Asn1Sequence strings;
-
-		public static PkiFreeText GetInstance(
-			Asn1TaggedObject	obj,
-			bool				isExplicit)
+		public static PkiFreeText GetInstance(object obj)
 		{
-			return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit));
+			if (obj is PkiFreeText pkiFreeText)
+				return pkiFreeText;
+
+			if (obj != null)
+                return new PkiFreeText(Asn1Sequence.GetInstance(obj));
+
+            return null;
 		}
 
-		public static PkiFreeText GetInstance(
-			object obj)
+        public static PkiFreeText GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+        {
+            return GetInstance(Asn1Sequence.GetInstance(taggedObject, declaredExplicit));
+        }
+
+        internal Asn1Sequence m_strings;
+
+        internal PkiFreeText(Asn1Sequence seq)
 		{
-			if (obj is PkiFreeText)
-			{
-				return (PkiFreeText)obj;
-			}
-			else if (obj is Asn1Sequence)
+			foreach (var element in seq)
 			{
-				return new PkiFreeText((Asn1Sequence)obj);
+				if (!(element is DerUtf8String))
+					throw new ArgumentException("attempt to insert non UTF8 STRING into PkiFreeText");
 			}
 
-            throw new ArgumentException("Unknown object in factory: " + Platform.GetTypeName(obj), "obj");
+			m_strings = seq;
 		}
 
-		public PkiFreeText(
-			Asn1Sequence seq)
+		public PkiFreeText(DerUtf8String p)
 		{
-			foreach (object o in seq)
-			{
-				if (!(o is DerUtf8String))
-				{
-					throw new ArgumentException("attempt to insert non UTF8 STRING into PkiFreeText");
-				}
-			}
+			m_strings = new DerSequence(p);
+		}
 
-			this.strings = seq;
+		public PkiFreeText(string p)
+			: this(new DerUtf8String(p))
+		{
 		}
 
-		public PkiFreeText(
-			DerUtf8String p)
+		public PkiFreeText(DerUtf8String[] strs)
 		{
-			strings = new DerSequence(p);
+			m_strings = new DerSequence(strs);
 		}
 
-		public int Count
+		public PkiFreeText(string[] strs)
 		{
-			get { return strings.Count; }
+			Asn1EncodableVector v = new Asn1EncodableVector(strs.Length);
+			for (int i = 0; i < strs.Length; i++)
+			{
+				v.Add(new DerUtf8String(strs[i]));
+			}
+			m_strings = new DerSequence(v);
 		}
 
+		public virtual int Count => m_strings.Count;
+
 		/**
 		 * Return the UTF8STRING at index.
 		 *
@@ -64,7 +69,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public DerUtf8String this[int index]
 		{
-			get { return (DerUtf8String) strings[index]; }
+			get { return (DerUtf8String)m_strings[index]; }
 		}
 
 		/**
@@ -74,7 +79,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return strings;
+			return m_strings;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/PKIHeader.cs b/crypto/src/asn1/cmp/PKIHeader.cs
index 7b6296279..7ed914e6a 100644
--- a/crypto/src/asn1/cmp/PKIHeader.cs
+++ b/crypto/src/asn1/cmp/PKIHeader.cs
@@ -19,7 +19,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
         private readonly DerInteger pvno;
         private readonly GeneralName sender;
         private readonly GeneralName recipient;
-        private readonly DerGeneralizedTime messageTime;
+        private readonly Asn1GeneralizedTime messageTime;
         private readonly AlgorithmIdentifier protectionAlg;
         private readonly Asn1OctetString senderKID;       // KeyIdentifier
         private readonly Asn1OctetString recipKID;        // KeyIdentifier
@@ -41,35 +41,35 @@ namespace Org.BouncyCastle.Asn1.Cmp
 
                 switch (tObj.TagNo)
                 {
-                    case 0:
-                        messageTime = DerGeneralizedTime.GetInstance(tObj, true);
-                        break;
-                    case 1:
-                        protectionAlg = AlgorithmIdentifier.GetInstance(tObj, true);
-                        break;
-                    case 2:
-                        senderKID = Asn1OctetString.GetInstance(tObj, true);
-                        break;
-                    case 3:
-                        recipKID = Asn1OctetString.GetInstance(tObj, true);
-                        break;
-                    case 4:
-                        transactionID = Asn1OctetString.GetInstance(tObj, true);
-                        break;
-                    case 5:
-                        senderNonce = Asn1OctetString.GetInstance(tObj, true);
-                        break;
-                    case 6:
-                        recipNonce = Asn1OctetString.GetInstance(tObj, true);
-                        break;
-                    case 7:
-                        freeText = PkiFreeText.GetInstance(tObj, true);
-                        break;
-                    case 8:
-                        generalInfo = Asn1Sequence.GetInstance(tObj, true);
-                        break;
-                    default:
-                        throw new ArgumentException("unknown tag number: " + tObj.TagNo, "seq");
+                case 0:
+                    messageTime = Asn1GeneralizedTime.GetInstance(tObj, true);
+                    break;
+                case 1:
+                    protectionAlg = AlgorithmIdentifier.GetInstance(tObj, true);
+                    break;
+                case 2:
+                    senderKID = Asn1OctetString.GetInstance(tObj, true);
+                    break;
+                case 3:
+                    recipKID = Asn1OctetString.GetInstance(tObj, true);
+                    break;
+                case 4:
+                    transactionID = Asn1OctetString.GetInstance(tObj, true);
+                    break;
+                case 5:
+                    senderNonce = Asn1OctetString.GetInstance(tObj, true);
+                    break;
+                case 6:
+                    recipNonce = Asn1OctetString.GetInstance(tObj, true);
+                    break;
+                case 7:
+                    freeText = PkiFreeText.GetInstance(tObj, true);
+                    break;
+                case 8:
+                    generalInfo = Asn1Sequence.GetInstance(tObj, true);
+                    break;
+                default:
+                    throw new ArgumentException("unknown tag number: " + tObj.TagNo, nameof(seq));
                 }
             }
         }
@@ -118,7 +118,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
             get { return recipient; }
         }
 
-        public virtual DerGeneralizedTime MessageTime
+        public virtual Asn1GeneralizedTime MessageTime
         {
             get { return messageTime; }
         }
diff --git a/crypto/src/asn1/cmp/PKIHeaderBuilder.cs b/crypto/src/asn1/cmp/PKIHeaderBuilder.cs
index d771dda4c..cbefc73b8 100644
--- a/crypto/src/asn1/cmp/PKIHeaderBuilder.cs
+++ b/crypto/src/asn1/cmp/PKIHeaderBuilder.cs
@@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		private DerInteger pvno;
 		private GeneralName sender;
 		private GeneralName recipient;
-		private DerGeneralizedTime messageTime;
+		private Asn1GeneralizedTime messageTime;
 		private AlgorithmIdentifier protectionAlg;
 		private Asn1OctetString senderKID;       // KeyIdentifier
 		private Asn1OctetString recipKID;        // KeyIdentifier
@@ -37,7 +37,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 			this.recipient = recipient;
 		}
 
-		public virtual PkiHeaderBuilder SetMessageTime(DerGeneralizedTime time)
+		public virtual PkiHeaderBuilder SetMessageTime(Asn1GeneralizedTime time)
 		{
 			messageTime = time;
 			return this;
diff --git a/crypto/src/asn1/cmp/PKIMessages.cs b/crypto/src/asn1/cmp/PKIMessages.cs
index eb01e544a..0008f476a 100644
--- a/crypto/src/asn1/cmp/PKIMessages.cs
+++ b/crypto/src/asn1/cmp/PKIMessages.cs
@@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
     {
         private Asn1Sequence content;
 
-        private PkiMessages(Asn1Sequence seq)
+        internal PkiMessages(Asn1Sequence seq)
         {
             content = seq;
         }
diff --git a/crypto/src/asn1/cmp/PbmParameter.cs b/crypto/src/asn1/cmp/PbmParameter.cs
index 206b89ba1..f4b702ed5 100644
--- a/crypto/src/asn1/cmp/PbmParameter.cs
+++ b/crypto/src/asn1/cmp/PbmParameter.cs
@@ -1,77 +1,74 @@
 using System;
 
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
+    /**
+     *  PBMParameter ::= SEQUENCE {
+     *          salt                OCTET STRING,
+     *          -- note:  implementations MAY wish to limit acceptable sizes
+     *          -- of this string to values appropriate for their environment
+     *          -- in order to reduce the risk of denial-of-service attacks
+     *          owf                 AlgorithmIdentifier,
+     *          -- AlgId for a One-Way Function (SHA-1 recommended)
+     *          iterationCount      INTEGER,
+     *          -- number of times the OWF is applied
+     *          -- note:  implementations MAY wish to limit acceptable sizes
+     *          -- of this integer to values appropriate for their environment
+     *          -- in order to reduce the risk of denial-of-service attacks
+     *          mac                 AlgorithmIdentifier
+     *          -- the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11],
+     *      }   -- or HMAC [RFC2104, RFC2202])
+     */
     public class PbmParameter
         : Asn1Encodable
     {
-        private Asn1OctetString salt;
-        private AlgorithmIdentifier owf;
-        private DerInteger iterationCount;
-        private AlgorithmIdentifier mac;
-
-        private PbmParameter(Asn1Sequence seq)
-        {
-            salt = Asn1OctetString.GetInstance(seq[0]);
-            owf = AlgorithmIdentifier.GetInstance(seq[1]);
-            iterationCount = DerInteger.GetInstance(seq[2]);
-            mac = AlgorithmIdentifier.GetInstance(seq[3]);
-        }
-
         public static PbmParameter GetInstance(object obj)
         {
-            if (obj is PbmParameter)
-                return (PbmParameter)obj;
+            if (obj is PbmParameter pbmParameter)
+                return pbmParameter;
 
-            if (obj is Asn1Sequence)
-                return new PbmParameter((Asn1Sequence)obj);
+            if (obj != null)
+                return new PbmParameter(Asn1Sequence.GetInstance(obj));
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+            return null;
         }
 
-        public PbmParameter(
-            byte[] salt,
-            AlgorithmIdentifier owf,
-            int iterationCount,
-            AlgorithmIdentifier mac)
-            : this(new DerOctetString(salt), owf, new DerInteger(iterationCount), mac)
-        {
-        }
+        private readonly Asn1OctetString m_salt;
+        private readonly AlgorithmIdentifier m_owf;
+        private readonly DerInteger m_iterationCount;
+        private readonly AlgorithmIdentifier m_mac;
 
-        public PbmParameter(
-            Asn1OctetString salt,
-            AlgorithmIdentifier owf,
-            DerInteger iterationCount,
-            AlgorithmIdentifier mac)
+        private PbmParameter(Asn1Sequence seq)
         {
-            this.salt = salt;
-            this.owf = owf;
-            this.iterationCount = iterationCount;
-            this.mac = mac;
+            m_salt = Asn1OctetString.GetInstance(seq[0]);
+            m_owf = AlgorithmIdentifier.GetInstance(seq[1]);
+            m_iterationCount = DerInteger.GetInstance(seq[2]);
+            m_mac = AlgorithmIdentifier.GetInstance(seq[3]);
         }
 
-        public virtual Asn1OctetString Salt
+        public PbmParameter(byte[] salt, AlgorithmIdentifier owf, int iterationCount, AlgorithmIdentifier mac)
+            : this(new DerOctetString(salt), owf, new DerInteger(iterationCount), mac)
         {
-            get { return salt; }
         }
 
-        public virtual AlgorithmIdentifier Owf
+        public PbmParameter(Asn1OctetString salt, AlgorithmIdentifier owf, DerInteger iterationCount,
+            AlgorithmIdentifier mac)
         {
-            get { return owf; }
+            m_salt = salt;
+            m_owf = owf;
+            m_iterationCount = iterationCount;
+            m_mac = mac;
         }
 
-        public virtual DerInteger IterationCount
-        {
-            get { return iterationCount; }
-        }
+        public virtual DerInteger IterationCount => m_iterationCount;
 
-        public virtual AlgorithmIdentifier Mac
-        {
-            get { return mac; }
-        }
+        public virtual AlgorithmIdentifier Mac => m_mac;
+
+        public virtual AlgorithmIdentifier Owf => m_owf;
+
+        public virtual Asn1OctetString Salt => m_salt;
 
         /**
          * <pre>
@@ -95,7 +92,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
          */
         public override Asn1Object ToAsn1Object()
         {
-            return new DerSequence(salt, owf, iterationCount, mac);
+            return new DerSequence(m_salt, m_owf, m_iterationCount, m_mac);
         }
     }
 }
diff --git a/crypto/src/asn1/cmp/PollRepContent.cs b/crypto/src/asn1/cmp/PollRepContent.cs
index ff75d7d6d..15f153a5d 100644
--- a/crypto/src/asn1/cmp/PollRepContent.cs
+++ b/crypto/src/asn1/cmp/PollRepContent.cs
@@ -1,71 +1,69 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class PollRepContent
+    /**
+     * PollRepContent ::= SEQUENCE OF SEQUENCE {
+     * certReqId    INTEGER,
+     * checkAfter   INTEGER,  -- time in seconds
+     * reason       PKIFreeText OPTIONAL }
+     */
+    public class PollRepContent
 		: Asn1Encodable
 	{
-		private readonly DerInteger certReqId;
-		private readonly DerInteger checkAfter;
-		private readonly PkiFreeText reason;
+        public static PollRepContent GetInstance(object obj)
+        {
+			if (obj is PollRepContent pollRepContent)
+				return pollRepContent;
+
+			if (obj != null)
+				return new PollRepContent(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly DerInteger[] m_certReqID;
+		private readonly DerInteger[] m_checkAfter;
+		private readonly PkiFreeText[] m_reason;
 
 		private PollRepContent(Asn1Sequence seq)
 		{
-			certReqId = DerInteger.GetInstance(seq[0]);
-			checkAfter = DerInteger.GetInstance(seq[1]);
+			int count = seq.Count;
+			m_certReqID = new DerInteger[count];
+			m_checkAfter = new DerInteger[count];
+			m_reason = new PkiFreeText[count];
 
-			if (seq.Count > 2)
+			for (int i = 0; i != count; i++)
 			{
-				reason = PkiFreeText.GetInstance(seq[2]);
-			}
-		}
-
-		public static PollRepContent GetInstance(object obj)
-		{
-			if (obj is PollRepContent)
-				return (PollRepContent)obj;
+				Asn1Sequence s = Asn1Sequence.GetInstance(seq[i]);
 
-			if (obj is Asn1Sequence)
-				return new PollRepContent((Asn1Sequence)obj);
+				m_certReqID[i] = DerInteger.GetInstance(s[0]);
+				m_checkAfter[i] = DerInteger.GetInstance(s[1]);
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+				if (s.Count > 2)
+				{
+					m_reason[i] = PkiFreeText.GetInstance(s[2]);
+				}
+			}
 		}
 
-	    public PollRepContent(
-	        DerInteger certReqId,
-	        DerInteger checkAfter)
+	    public PollRepContent(DerInteger certReqID, DerInteger checkAfter)
+			: this(certReqID, checkAfter, null)
 	    {
-	        this.certReqId = certReqId;
-	        this.checkAfter = checkAfter;
-	        this.reason = null;
 	    }
 
-        public PollRepContent(
-	        DerInteger certReqId,
-	        DerInteger checkAfter,
-	        PkiFreeText reason)
+        public PollRepContent(DerInteger certReqID, DerInteger checkAfter, PkiFreeText reason)
 	    {
-	        this.certReqId = certReqId;
-	        this.checkAfter = checkAfter;
-	        this.reason = reason;
-	    }
+            m_certReqID = new DerInteger[1]{ certReqID };
+            m_checkAfter = new DerInteger[1]{ checkAfter };
+            m_reason = new PkiFreeText[1]{ reason };
+        }
 
-		public virtual DerInteger CertReqID
-		{
-			get { return certReqId; }
-		}
+        public virtual int Count => m_certReqID.Length;
 
-		public virtual DerInteger CheckAfter
-		{
-			get { return checkAfter; }
-		}
+        public virtual DerInteger GetCertReqID(int index) => m_certReqID[index];
 
-		public virtual PkiFreeText Reason
-		{
-			get { return reason; }
-		}
+		public virtual DerInteger GetCheckAfter(int index) => m_checkAfter[index];
+
+		public virtual PkiFreeText GetReason(int index) => m_reason[index];
 
 		/**
 		 * <pre>
@@ -79,9 +77,20 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(certReqId, checkAfter);
-			v.AddOptional(reason);
-			return new DerSequence(v);
+			Asn1EncodableVector outer = new Asn1EncodableVector(m_certReqID.Length);
+
+			for (int i = 0; i != m_certReqID.Length; i++)
+			{
+				Asn1EncodableVector v = new Asn1EncodableVector(3);
+
+				v.Add(m_certReqID[i]);
+				v.Add(m_checkAfter[i]);
+				v.AddOptional(m_reason[i]);
+
+				outer.Add(new DerSequence(v));
+			}
+
+			return new DerSequence(outer);
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/PollReqContent.cs b/crypto/src/asn1/cmp/PollReqContent.cs
index dd9b0c352..80a39348a 100644
--- a/crypto/src/asn1/cmp/PollReqContent.cs
+++ b/crypto/src/asn1/cmp/PollReqContent.cs
@@ -1,51 +1,91 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class PollReqContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
+        public static PollReqContent GetInstance(object obj)
+        {
+			if (obj is PollReqContent pollReqContent)
+				return pollReqContent;
+
+			if (obj != null)
+				return new PollReqContent(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly Asn1Sequence m_content;
 
 		private PollReqContent(Asn1Sequence seq)
 		{
-			content = seq;
+			m_content = seq;
 		}
 
-		public static PollReqContent GetInstance(object obj)
+		/**
+		 * Create a pollReqContent for a single certReqId.
+		 *
+		 * @param certReqId the certificate request ID.
+		 */
+		public PollReqContent(DerInteger certReqId)
+			: this(new DerSequence(new DerSequence(certReqId)))
 		{
-			if (obj is PollReqContent)
-				return (PollReqContent)obj;
+		}
 
-			if (obj is Asn1Sequence)
-				return new PollReqContent((Asn1Sequence)obj);
+		/**
+		 * Create a pollReqContent for a multiple certReqIds.
+		 *
+		 * @param certReqIds the certificate request IDs.
+		 */
+		public PollReqContent(DerInteger[] certReqIds)
+			: this(new DerSequence(IntsToSequence(certReqIds)))
+		{
+		}
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+		/**
+		 * Create a pollReqContent for a single certReqId.
+		 *
+		 * @param certReqId the certificate request ID.
+		 */
+		public PollReqContent(BigInteger certReqId)
+			: this(new DerInteger(certReqId))
+		{
 		}
 
-		public virtual DerInteger[][] GetCertReqIDs()
+		/**
+		 * Create a pollReqContent for a multiple certReqIds.
+		 *
+		 * @param certReqIds the certificate request IDs.
+		 */
+		public PollReqContent(BigInteger[] certReqIds)
+			: this(IntsToAsn1(certReqIds))
 		{
-			DerInteger[][] result = new DerInteger[content.Count][];
-			for (int i = 0; i != result.Length; ++i)
-			{
-				result[i] = SequenceToDerIntegerArray((Asn1Sequence)content[i]);
-			}
-			return result;
 		}
 
-		private static DerInteger[] SequenceToDerIntegerArray(Asn1Sequence seq)
+		public virtual DerInteger[][] GetCertReqIDs()
 		{
-			DerInteger[] result = new DerInteger[seq.Count];
+			DerInteger[][] result = new DerInteger[m_content.Count][];
 			for (int i = 0; i != result.Length; ++i)
 			{
-				result[i] = DerInteger.GetInstance(seq[i]);
+				result[i] = SequenceToDerIntegerArray((Asn1Sequence)m_content[i]);
 			}
 			return result;
 		}
 
-		/**
+        public virtual BigInteger[] GetCertReqIDValues()
+        {
+            BigInteger[] result = new BigInteger[m_content.Count];
+
+            for (int i = 0; i != result.Length; i++)
+            {
+                result[i] = DerInteger.GetInstance(Asn1Sequence.GetInstance(m_content[i])[0]).Value;
+            }
+
+            return result;
+        }
+
+        /**
 		 * <pre>
 		 * PollReqContent ::= SEQUENCE OF SEQUENCE {
 		 *                        certReqId              INTEGER
@@ -53,9 +93,38 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 * </pre>
 		 * @return a basic ASN.1 object representation.
 		 */
-		public override Asn1Object ToAsn1Object()
+        public override Asn1Object ToAsn1Object()
+		{
+			return m_content;
+		}
+
+		private static DerInteger[] SequenceToDerIntegerArray(Asn1Sequence seq)
+		{
+			return seq.MapElements(DerInteger.GetInstance);
+		}
+
+		private static DerSequence[] IntsToSequence(DerInteger[] ids)
 		{
-			return content;
+			DerSequence[] result = new DerSequence[ids.Length];
+
+			for (int i = 0; i != result.Length; i++)
+			{
+				result[i] = new DerSequence(ids[i]);
+			}
+
+			return result;
+		}
+
+		private static DerInteger[] IntsToAsn1(BigInteger[] ids)
+		{
+			DerInteger[] result = new DerInteger[ids.Length];
+
+			for (int i = 0; i != result.Length; i++)
+			{
+				result[i] = new DerInteger(ids[i]);
+			}
+
+			return result;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/PopoDecKeyChallContent.cs b/crypto/src/asn1/cmp/PopoDecKeyChallContent.cs
index 03a13a5d5..0bd1597c8 100644
--- a/crypto/src/asn1/cmp/PopoDecKeyChallContent.cs
+++ b/crypto/src/asn1/cmp/PopoDecKeyChallContent.cs
@@ -1,38 +1,31 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class PopoDecKeyChallContent
 	    : Asn1Encodable
 	{
-	    private readonly Asn1Sequence content;
+        public static PopoDecKeyChallContent GetInstance(object obj)
+        {
+			if (obj is PopoDecKeyChallContent popoDecKeyChallContent)
+				return popoDecKeyChallContent;
 
-	    private PopoDecKeyChallContent(Asn1Sequence seq)
-	    {
-	        content = seq;
-	    }
+            if (obj != null)
+                return new PopoDecKeyChallContent(Asn1Sequence.GetInstance(obj));
 
-	    public static PopoDecKeyChallContent GetInstance(object obj)
-	    {
-	        if (obj is PopoDecKeyChallContent)
-	            return (PopoDecKeyChallContent)obj;
+            return null;
+        }
 
-			if (obj is Asn1Sequence)
-	            return new PopoDecKeyChallContent((Asn1Sequence)obj);
+        private readonly Asn1Sequence m_content;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+	    private PopoDecKeyChallContent(Asn1Sequence seq)
+	    {
+	        m_content = seq;
 	    }
 
 	    public virtual Challenge[] ToChallengeArray()
 	    {
-	        Challenge[] result = new Challenge[content.Count];
-	        for (int i = 0; i != result.Length; ++i)
-	        {
-	            result[i] = Challenge.GetInstance(content[i]);
-	        }
-	        return result;
+			return m_content.MapElements(Challenge.GetInstance);
 	    }
 
 	    /**
@@ -43,7 +36,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 	     */
 	    public override Asn1Object ToAsn1Object()
 	    {
-	        return content;
+	        return m_content;
 	    }
 	}
 }
diff --git a/crypto/src/asn1/cmp/PopoDecKeyRespContent.cs b/crypto/src/asn1/cmp/PopoDecKeyRespContent.cs
index 73f59b7c1..77d720271 100644
--- a/crypto/src/asn1/cmp/PopoDecKeyRespContent.cs
+++ b/crypto/src/asn1/cmp/PopoDecKeyRespContent.cs
@@ -1,38 +1,29 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class PopoDecKeyRespContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
+        public static PopoDecKeyRespContent GetInstance(object obj)
+        {
+			if (obj is PopoDecKeyRespContent popoDecKeyRespContent)
+				return popoDecKeyRespContent;
 
-		private PopoDecKeyRespContent(Asn1Sequence seq)
-		{
-			content = seq;
-		}
+			if (obj != null)
+				return new PopoDecKeyRespContent(Asn1Sequence.GetInstance(obj));
 
-		public static PopoDecKeyRespContent GetInstance(object obj)
-		{
-			if (obj is PopoDecKeyRespContent)
-				return (PopoDecKeyRespContent)obj;
+			return null;
+        }
 
-			if (obj is Asn1Sequence)
-				return new PopoDecKeyRespContent((Asn1Sequence)obj);
+        private readonly Asn1Sequence m_content;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+		private PopoDecKeyRespContent(Asn1Sequence seq)
+		{
+			m_content = seq;
 		}
 
-		public virtual DerInteger[] ToDerIntegerArray()
+		public virtual DerInteger[] ToIntegerArray()
 		{
-			DerInteger[] result = new DerInteger[content.Count];
-			for (int i = 0; i != result.Length; ++i)
-			{
-				result[i] = DerInteger.GetInstance(content[i]);
-			}
-			return result;
+			return m_content.MapElements(DerInteger.GetInstance);
 		}
 
 		/**
@@ -43,7 +34,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return content;
+			return m_content;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/ProtectedPart.cs b/crypto/src/asn1/cmp/ProtectedPart.cs
index ed90708f9..fc83ac6c6 100644
--- a/crypto/src/asn1/cmp/ProtectedPart.cs
+++ b/crypto/src/asn1/cmp/ProtectedPart.cs
@@ -1,47 +1,37 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class ProtectedPart
 		: Asn1Encodable
 	{
-		private readonly PkiHeader header;
-		private readonly PkiBody body;
-		
-		private ProtectedPart(Asn1Sequence seq)
-		{
-			header = PkiHeader.GetInstance(seq[0]);
-			body = PkiBody.GetInstance(seq[1]);
-		}
+        public static ProtectedPart GetInstance(object obj)
+        {
+			if (obj is ProtectedPart protectedPart)
+				return protectedPart;
 
-		public static ProtectedPart GetInstance(object obj)
-		{
-			if (obj is ProtectedPart)
-				return (ProtectedPart)obj;
+			if (obj != null)
+				return new ProtectedPart(Asn1Sequence.GetInstance(obj));
 
-			if (obj is Asn1Sequence)
-				return new ProtectedPart((Asn1Sequence)obj);
+			return null;
+        }
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
+        private readonly PkiHeader m_header;
+		private readonly PkiBody m_body;
 
-		public ProtectedPart(PkiHeader header, PkiBody body)
+		private ProtectedPart(Asn1Sequence seq)
 		{
-			this.header = header;
-			this.body = body;
+			m_header = PkiHeader.GetInstance(seq[0]);
+			m_body = PkiBody.GetInstance(seq[1]);
 		}
 
-		public virtual PkiHeader Header
+		public ProtectedPart(PkiHeader header, PkiBody body)
 		{
-			get { return header; }
+			m_header = header;
+			m_body = body;
 		}
 
-		public virtual PkiBody Body
-		{
-			get { return body; }
-		}
+		public virtual PkiHeader Header => m_header;
+
+		public virtual PkiBody Body => m_body;
 
 		/**
 		 * <pre>
@@ -54,7 +44,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return new DerSequence(header, body);
+			return new DerSequence(m_header, m_body);
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/RevAnnContent.cs b/crypto/src/asn1/cmp/RevAnnContent.cs
index d5d42625c..cdd26c39f 100644
--- a/crypto/src/asn1/cmp/RevAnnContent.cs
+++ b/crypto/src/asn1/cmp/RevAnnContent.cs
@@ -1,68 +1,66 @@
-using System;
-
 using Org.BouncyCastle.Asn1.Crmf;
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class RevAnnContent
 		: Asn1Encodable
 	{
-		private readonly PkiStatusEncodable status;
-		private readonly CertId certId;
-		private readonly DerGeneralizedTime willBeRevokedAt;
-		private readonly DerGeneralizedTime badSinceDate;
-		private readonly X509Extensions crlDetails;
+        public static RevAnnContent GetInstance(object obj)
+        {
+			if (obj is RevAnnContent revAnnContent)
+				return revAnnContent;
 
-		private RevAnnContent(Asn1Sequence seq)
-		{
-			status = PkiStatusEncodable.GetInstance(seq[0]);
-			certId = CertId.GetInstance(seq[1]);
-			willBeRevokedAt = DerGeneralizedTime.GetInstance(seq[2]);
-			badSinceDate = DerGeneralizedTime.GetInstance(seq[3]);
+			if (obj != null)
+				return new RevAnnContent(Asn1Sequence.GetInstance(obj));
 
-			if (seq.Count > 4)
-			{
-				crlDetails = X509Extensions.GetInstance(seq[4]);
-			}
-		}
+			return null;
+        }
 
-		public static RevAnnContent GetInstance(object obj)
-		{
-			if (obj is RevAnnContent)
-				return (RevAnnContent)obj;
-
-			if (obj is Asn1Sequence)
-				return new RevAnnContent((Asn1Sequence)obj);
+        private readonly PkiStatusEncodable m_status;
+		private readonly CertId m_certID;
+		private readonly Asn1GeneralizedTime m_willBeRevokedAt;
+		private readonly Asn1GeneralizedTime m_badSinceDate;
+		private readonly X509Extensions m_crlDetails;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+        public RevAnnContent(PkiStatusEncodable status, CertId certID, Asn1GeneralizedTime willBeRevokedAt,
+            Asn1GeneralizedTime badSinceDate)
+            : this(status, certID, willBeRevokedAt, badSinceDate, null)
+        {
 		}
 
-		public virtual PkiStatusEncodable Status
-		{
-			get { return status; }
-		}
+        public RevAnnContent(PkiStatusEncodable status, CertId certID, Asn1GeneralizedTime willBeRevokedAt,
+            Asn1GeneralizedTime badSinceDate, X509Extensions crlDetails)
+        {
+            m_status = status;
+            m_certID = certID;
+            m_willBeRevokedAt = willBeRevokedAt;
+            m_badSinceDate = badSinceDate;
+            m_crlDetails = crlDetails;
+        }
 
-		public virtual CertId CertID
+        private RevAnnContent(Asn1Sequence seq)
 		{
-			get { return certId; }
-		}
+			m_status = PkiStatusEncodable.GetInstance(seq[0]);
+			m_certID = CertId.GetInstance(seq[1]);
+			m_willBeRevokedAt = Asn1GeneralizedTime.GetInstance(seq[2]);
+			m_badSinceDate = Asn1GeneralizedTime.GetInstance(seq[3]);
 
-		public virtual DerGeneralizedTime WillBeRevokedAt
-		{
-			get { return willBeRevokedAt; }
+			if (seq.Count > 4)
+			{
+				m_crlDetails = X509Extensions.GetInstance(seq[4]);
+			}
 		}
 
-		public virtual DerGeneralizedTime BadSinceDate
-		{
-			get { return badSinceDate; }
-		}
+		public virtual PkiStatusEncodable Status => m_status;
 
-		public virtual X509Extensions CrlDetails
-		{
-			get { return crlDetails; }
-		}
+		public virtual CertId CertID => m_certID;
+
+		public virtual Asn1GeneralizedTime WillBeRevokedAt => m_willBeRevokedAt;
+
+		public virtual Asn1GeneralizedTime BadSinceDate => m_badSinceDate;
+
+		public virtual X509Extensions CrlDetails => m_crlDetails;
 
 		/**
 		 * <pre>
@@ -79,8 +77,8 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(status, certId, willBeRevokedAt, badSinceDate);
-			v.AddOptional(crlDetails);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_status, m_certID, m_willBeRevokedAt, m_badSinceDate);
+			v.AddOptional(m_crlDetails);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/RevDetails.cs b/crypto/src/asn1/cmp/RevDetails.cs
index 7d2a65ab9..9472d7775 100644
--- a/crypto/src/asn1/cmp/RevDetails.cs
+++ b/crypto/src/asn1/cmp/RevDetails.cs
@@ -1,56 +1,61 @@
-using System;
-
 using Org.BouncyCastle.Asn1.Crmf;
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class RevDetails
+    /**
+     * <pre>
+     * RevDetails ::= SEQUENCE {
+     *          certDetails         CertTemplate,
+     *          -- allows requester to specify as much as they can about
+     *          -- the cert. for which revocation is requested
+     *          -- (e.g., for cases in which serialNumber is not available)
+     *          crlEntryDetails     Extensions       OPTIONAL
+     *          -- requested crlEntryExtensions
+     *      }
+     * </pre>
+     */
+    public class RevDetails
 		: Asn1Encodable
 	{
-		private readonly CertTemplate certDetails;
-		private readonly X509Extensions crlEntryDetails;
+        public static RevDetails GetInstance(object obj)
+        {
+			if (obj is RevDetails revDetails)
+				return revDetails;
 
-        private RevDetails(Asn1Sequence seq)
-		{
-			certDetails = CertTemplate.GetInstance(seq[0]);
-            crlEntryDetails = seq.Count <= 1
-                ?   null
-                :   X509Extensions.GetInstance(seq[1]);
-		}
+			if (obj != null)
+				return new RevDetails(Asn1Sequence.GetInstance(obj));
 
-        public static RevDetails GetInstance(object obj)
-		{
-			if (obj is RevDetails)
-				return (RevDetails)obj;
+			return null;
+        }
 
-			if (obj is Asn1Sequence)
-				return new RevDetails((Asn1Sequence)obj);
+        private readonly CertTemplate m_certDetails;
+		private readonly X509Extensions m_crlEntryDetails;
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+        private RevDetails(Asn1Sequence seq)
+		{
+			m_certDetails = CertTemplate.GetInstance(seq[0]);
+
+            if (seq.Count > 1)
+            {
+                m_crlEntryDetails = X509Extensions.GetInstance(seq[1]);
+            }
 		}
 
 		public RevDetails(CertTemplate certDetails)
-            :   this(certDetails, null)
+            : this(certDetails, null)
 		{
 		}
 
         public RevDetails(CertTemplate certDetails, X509Extensions crlEntryDetails)
 		{
-            this.certDetails = certDetails;
-            this.crlEntryDetails = crlEntryDetails;
+            m_certDetails = certDetails;
+            m_crlEntryDetails = crlEntryDetails;
 		}
 
-        public virtual CertTemplate CertDetails
-		{
-			get { return certDetails; }
-		}
+		public virtual CertTemplate CertDetails => m_certDetails;
 
-        public virtual X509Extensions CrlEntryDetails
-		{
-			get { return crlEntryDetails; }
-		}
+        public virtual X509Extensions CrlEntryDetails => m_crlEntryDetails;
 
 		/**
 		* <pre>
@@ -67,8 +72,8 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		*/
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(certDetails);
-			v.AddOptional(crlEntryDetails);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_certDetails);
+			v.AddOptional(m_crlEntryDetails);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/RevRepContent.cs b/crypto/src/asn1/cmp/RevRepContent.cs
index 4b3f82b96..841b3cf94 100644
--- a/crypto/src/asn1/cmp/RevRepContent.cs
+++ b/crypto/src/asn1/cmp/RevRepContent.cs
@@ -1,21 +1,43 @@
-using System;
-
 using Org.BouncyCastle.Asn1.Crmf;
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Asn1.Cmp
 {
-	public class RevRepContent
+    /**
+     * <pre>
+     * RevRepContent ::= SEQUENCE {
+     *          status       SEQUENCE SIZE (1..MAX) OF PKIStatusInfo,
+     *          -- in same order as was sent in RevReqContent
+     *          revCerts [0] SEQUENCE SIZE (1..MAX) OF CertId
+     *                                              OPTIONAL,
+     *          -- IDs for which revocation was requested
+     *          -- (same order as status)
+     *          crls     [1] SEQUENCE SIZE (1..MAX) OF CertificateList OPTIONAL
+     *          -- the resulting CRLs (there may be more than one)
+     *      }
+     *</pre>
+     */
+    public class RevRepContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence status;
-		private readonly Asn1Sequence revCerts;
-		private readonly Asn1Sequence crls;
+        public static RevRepContent GetInstance(object obj)
+        {
+			if (obj is RevRepContent revRepContent)
+				return revRepContent;
+
+			if (obj != null)
+				return new RevRepContent(Asn1Sequence.GetInstance(obj));
+
+			return null;
+        }
+
+        private readonly Asn1Sequence m_status;
+		private readonly Asn1Sequence m_revCerts;
+		private readonly Asn1Sequence m_crls;
 
 		private RevRepContent(Asn1Sequence seq)
 		{
-			status = Asn1Sequence.GetInstance(seq[0]);
+			m_status = Asn1Sequence.GetInstance(seq[0]);
 
 			for (int pos = 1; pos < seq.Count; ++pos)
 			{
@@ -23,60 +45,34 @@ namespace Org.BouncyCastle.Asn1.Cmp
 
 				if (tObj.TagNo == 0)
 				{
-					revCerts = Asn1Sequence.GetInstance(tObj, true);
+					m_revCerts = Asn1Sequence.GetInstance(tObj, true);
 				}
 				else
 				{
-					crls = Asn1Sequence.GetInstance(tObj, true);
+					m_crls = Asn1Sequence.GetInstance(tObj, true);
 				}
 			}
 		}
 
-		public static RevRepContent GetInstance(object obj)
-		{
-			if (obj is RevRepContent)
-				return (RevRepContent)obj;
-
-			if (obj is Asn1Sequence)
-				return new RevRepContent((Asn1Sequence)obj);
-
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
-		}
-		
 		public virtual PkiStatusInfo[] GetStatus()
 		{
-			PkiStatusInfo[] results = new PkiStatusInfo[status.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = PkiStatusInfo.GetInstance(status[i]);
-			}
-			return results;
+			return m_status.MapElements(PkiStatusInfo.GetInstance);
 		}
 
 		public virtual CertId[] GetRevCerts()
 		{
-			if (revCerts == null)
+			if (m_revCerts == null)
 				return null;
 
-			CertId[] results = new CertId[revCerts.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = CertId.GetInstance(revCerts[i]);
-			}
-			return results;
+			return m_revCerts.MapElements(CertId.GetInstance);
 		}
 
 		public virtual CertificateList[] GetCrls()
 		{
-			if (crls == null)
+			if (m_crls == null)
 				return null;
 
-			CertificateList[] results = new CertificateList[crls.Count];
-			for (int i = 0; i != results.Length; ++i)
-			{
-				results[i] = CertificateList.GetInstance(crls[i]);
-			}
-			return results;
+			return m_crls.MapElements(CertificateList.GetInstance);
 		}
 
 		/**
@@ -95,9 +91,9 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			Asn1EncodableVector v = new Asn1EncodableVector(status);
-            v.AddOptionalTagged(true, 0, revCerts);
-            v.AddOptionalTagged(true, 1, crls);
+			Asn1EncodableVector v = new Asn1EncodableVector(m_status);
+            v.AddOptionalTagged(true, 0, m_revCerts);
+            v.AddOptionalTagged(true, 1, m_crls);
 			return new DerSequence(v);
 		}
 	}
diff --git a/crypto/src/asn1/cmp/RevRepContentBuilder.cs b/crypto/src/asn1/cmp/RevRepContentBuilder.cs
index cc17d1d4c..f23bed8b5 100644
--- a/crypto/src/asn1/cmp/RevRepContentBuilder.cs
+++ b/crypto/src/asn1/cmp/RevRepContentBuilder.cs
@@ -7,29 +7,29 @@ namespace Org.BouncyCastle.Asn1.Cmp
 {
 	public class RevRepContentBuilder
 	{
-		private readonly Asn1EncodableVector status = new Asn1EncodableVector();
-		private readonly Asn1EncodableVector revCerts = new Asn1EncodableVector();
-		private readonly Asn1EncodableVector crls = new Asn1EncodableVector();
+		private readonly Asn1EncodableVector m_status = new Asn1EncodableVector();
+		private readonly Asn1EncodableVector m_revCerts = new Asn1EncodableVector();
+		private readonly Asn1EncodableVector m_crls = new Asn1EncodableVector();
 
 		public virtual RevRepContentBuilder Add(PkiStatusInfo status)
 		{
-			this.status.Add(status);
+			m_status.Add(status);
 			return this;
 		}
 
 		public virtual RevRepContentBuilder Add(PkiStatusInfo status, CertId certId)
 		{
-			if (this.status.Count != this.revCerts.Count)
+			if (m_status.Count != m_revCerts.Count)
 				throw new InvalidOperationException("status and revCerts sequence must be in common order");
 
-			this.status.Add(status);
-			this.revCerts.Add(certId);
+			m_status.Add(status);
+			m_revCerts.Add(certId);
 			return this;
 		}
 
 		public virtual RevRepContentBuilder AddCrl(CertificateList crl)
 		{
-			this.crls.Add(crl);
+			m_crls.Add(crl);
 			return this;
 		}
 
@@ -37,16 +37,16 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		{
 			Asn1EncodableVector v = new Asn1EncodableVector();
 
-			v.Add(new DerSequence(status));
+			v.Add(new DerSequence(m_status));
 
-			if (revCerts.Count != 0)
+			if (m_revCerts.Count != 0)
 			{
-				v.Add(new DerTaggedObject(true, 0, new DerSequence(revCerts)));
+				v.Add(new DerTaggedObject(true, 0, new DerSequence(m_revCerts)));
 			}
 
-			if (crls.Count != 0)
+			if (m_crls.Count != 0)
 			{
-				v.Add(new DerTaggedObject(true, 1, new DerSequence(crls)));
+				v.Add(new DerTaggedObject(true, 1, new DerSequence(m_crls)));
 			}
 
 			return RevRepContent.GetInstance(new DerSequence(v));
diff --git a/crypto/src/asn1/cmp/RevReqContent.cs b/crypto/src/asn1/cmp/RevReqContent.cs
index 1522d3789..c390530a8 100644
--- a/crypto/src/asn1/cmp/RevReqContent.cs
+++ b/crypto/src/asn1/cmp/RevReqContent.cs
@@ -7,37 +7,37 @@ namespace Org.BouncyCastle.Asn1.Cmp
 	public class RevReqContent
 		: Asn1Encodable
 	{
-		private readonly Asn1Sequence content;
-		
-		private RevReqContent(Asn1Sequence seq)
-		{
-			content = seq;
-		}
+        public static RevReqContent GetInstance(object obj)
+        {
+			if (obj is RevReqContent revReqContent)
+				return revReqContent;
 
-		public static RevReqContent GetInstance(object obj)
-		{
-			if (obj is RevReqContent)
-				return (RevReqContent)obj;
+			if (obj != null)
+				return new RevReqContent(Asn1Sequence.GetInstance(obj));
 
-			if (obj is Asn1Sequence)
-				return new RevReqContent((Asn1Sequence)obj);
+			return null;
+        }
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+        private readonly Asn1Sequence m_content;
+
+		private RevReqContent(Asn1Sequence seq)
+		{
+			m_content = seq;
 		}
 
-		public RevReqContent(params RevDetails[] revDetails)
+        public RevReqContent(RevDetails revDetails)
+        {
+            m_content = new DerSequence(revDetails);
+        }
+
+        public RevReqContent(params RevDetails[] revDetailsArray)
 		{
-			this.content = new DerSequence(revDetails);
+			m_content = new DerSequence(revDetailsArray);
 		}
 
 		public virtual RevDetails[] ToRevDetailsArray()
 		{
-			RevDetails[] result = new RevDetails[content.Count];
-			for (int i = 0; i != result.Length; ++i)
-			{
-				result[i] = RevDetails.GetInstance(content[i]);
-			}
-			return result;
+			return m_content.MapElements(RevDetails.GetInstance);
 		}
 
 		/**
@@ -48,7 +48,7 @@ namespace Org.BouncyCastle.Asn1.Cmp
 		 */
 		public override Asn1Object ToAsn1Object()
 		{
-			return content;
+			return m_content;
 		}
 	}
 }
diff --git a/crypto/src/asn1/cmp/RootCaKeyUpdateContent.cs b/crypto/src/asn1/cmp/RootCaKeyUpdateContent.cs
new file mode 100644
index 000000000..696b08b94
--- /dev/null
+++ b/crypto/src/asn1/cmp/RootCaKeyUpdateContent.cs
@@ -0,0 +1,91 @@
+using System;
+
+namespace Org.BouncyCastle.Asn1.Cmp
+{
+    /**
+     * GenMsg:    {id-it 20}, RootCaCertValue | &lt; absent &gt;
+     * GenRep:    {id-it 18}, RootCaKeyUpdateContent | &lt; absent &gt;
+     * <p>
+     * RootCaCertValue ::= CMPCertificate
+     * </p><p>
+     * RootCaKeyUpdateValue ::= RootCaKeyUpdateContent
+     * </p><p>
+     * RootCaKeyUpdateContent ::= SEQUENCE {
+     * newWithNew       CMPCertificate,
+     * newWithOld   [0] CMPCertificate OPTIONAL,
+     * oldWithNew   [1] CMPCertificate OPTIONAL
+     * }
+     * </p>
+     */
+    public class RootCaKeyUpdateContent
+        : Asn1Encodable
+    {
+        public static RootCaKeyUpdateContent GetInstance(object obj)
+        {
+            if (obj is RootCaKeyUpdateContent rootCaKeyUpdateContent)
+                return rootCaKeyUpdateContent;
+
+            if (obj != null)
+                return new RootCaKeyUpdateContent(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly CmpCertificate m_newWithNew;
+        private readonly CmpCertificate m_newWithOld;
+        private readonly CmpCertificate m_oldWithNew;
+
+        public RootCaKeyUpdateContent(CmpCertificate newWithNew, CmpCertificate newWithOld, CmpCertificate oldWithNew)
+        {
+            if (newWithNew == null)
+                throw new ArgumentNullException(nameof(newWithNew));
+
+            m_newWithNew = newWithNew;
+            m_newWithOld = newWithOld;
+            m_oldWithNew = oldWithNew;
+        }
+
+        private RootCaKeyUpdateContent(Asn1Sequence seq)
+        {
+            if (seq.Count < 1 || seq.Count > 3)
+                throw new ArgumentException("expected sequence of 1 to 3 elements only");
+
+            CmpCertificate newWithNew;
+            CmpCertificate newWithOld = null;
+            CmpCertificate oldWithNew = null;
+
+            newWithNew = CmpCertificate.GetInstance(seq[0]);
+
+            for (int pos = 1; pos < seq.Count; ++pos)
+            {
+                Asn1TaggedObject ato = Asn1TaggedObject.GetInstance(seq[pos]);
+                if (ato.TagNo == 0)
+                {
+                    newWithOld = CmpCertificate.GetInstance(ato, true);
+                }
+                else if (ato.TagNo == 1)
+                {
+                    oldWithNew = CmpCertificate.GetInstance(ato, true);
+                }
+            }
+
+            m_newWithNew = newWithNew;
+            m_newWithOld = newWithOld;
+            m_oldWithNew = oldWithNew;
+        }
+
+        public virtual CmpCertificate NewWithNew => m_newWithNew;
+
+        public virtual CmpCertificate NewWithOld => m_newWithOld;
+
+        public virtual CmpCertificate OldWithNew => m_oldWithNew;
+
+        public override Asn1Object ToAsn1Object()
+        {
+            Asn1EncodableVector v = new Asn1EncodableVector(m_newWithNew);
+            v.AddOptionalTagged(true, 0, m_newWithOld);
+            v.AddOptionalTagged(true, 1, m_oldWithNew);
+            return new DerSequence(v);
+        }
+    }
+}
diff --git a/crypto/src/asn1/cms/CMSObjectIdentifiers.cs b/crypto/src/asn1/cms/CMSObjectIdentifiers.cs
index 2ad0a3c7c..6f4f8fb5e 100644
--- a/crypto/src/asn1/cms/CMSObjectIdentifiers.cs
+++ b/crypto/src/asn1/cms/CMSObjectIdentifiers.cs
@@ -13,7 +13,8 @@ namespace Org.BouncyCastle.Asn1.Cms
         public static readonly DerObjectIdentifier AuthenticatedData = PkcsObjectIdentifiers.IdCTAuthData;
         public static readonly DerObjectIdentifier CompressedData = PkcsObjectIdentifiers.IdCTCompressedData;
         public static readonly DerObjectIdentifier AuthEnvelopedData = PkcsObjectIdentifiers.IdCTAuthEnvelopedData;
-        public static readonly DerObjectIdentifier timestampedData = PkcsObjectIdentifiers.IdCTTimestampedData;
+        public static readonly DerObjectIdentifier TimestampedData = PkcsObjectIdentifiers.IdCTTimestampedData;
+        public static readonly DerObjectIdentifier ZlibCompress = PkcsObjectIdentifiers.IdAlgZlibCompress;
 
         /**
          * The other Revocation Info arc
diff --git a/crypto/src/asn1/cms/KEKIdentifier.cs b/crypto/src/asn1/cms/KEKIdentifier.cs
index a42217440..36ab94f52 100644
--- a/crypto/src/asn1/cms/KEKIdentifier.cs
+++ b/crypto/src/asn1/cms/KEKIdentifier.cs
@@ -8,12 +8,12 @@ namespace Org.BouncyCastle.Asn1.Cms
         : Asn1Encodable
     {
         private Asn1OctetString		keyIdentifier;
-        private DerGeneralizedTime	date;
+        private Asn1GeneralizedTime date;
         private OtherKeyAttribute	other;
 
 		public KekIdentifier(
             byte[]              keyIdentifier,
-            DerGeneralizedTime  date,
+            Asn1GeneralizedTime date,
             OtherKeyAttribute   other)
         {
             this.keyIdentifier = new DerOctetString(keyIdentifier);
@@ -31,9 +31,9 @@ namespace Org.BouncyCastle.Asn1.Cms
             case 1:
 				break;
             case 2:
-				if (seq[1] is DerGeneralizedTime)
+				if (seq[1] is Asn1GeneralizedTime)
 				{
-					date = (DerGeneralizedTime) seq[1];
+					date = (Asn1GeneralizedTime) seq[1];
 				}
 				else
 				{
@@ -41,7 +41,7 @@ namespace Org.BouncyCastle.Asn1.Cms
 				}
 				break;
             case 3:
-				date  = (DerGeneralizedTime) seq[1];
+				date  = (Asn1GeneralizedTime) seq[1];
 				other = OtherKeyAttribute.GetInstance(seq[2]);
 				break;
             default:
@@ -88,7 +88,7 @@ namespace Org.BouncyCastle.Asn1.Cms
 			get { return keyIdentifier; }
 		}
 
-		public DerGeneralizedTime Date
+		public Asn1GeneralizedTime Date
 		{
 			get { return date; }
 		}
diff --git a/crypto/src/asn1/cms/RecipientKeyIdentifier.cs b/crypto/src/asn1/cms/RecipientKeyIdentifier.cs
index 995ddab51..dea9ce09d 100644
--- a/crypto/src/asn1/cms/RecipientKeyIdentifier.cs
+++ b/crypto/src/asn1/cms/RecipientKeyIdentifier.cs
@@ -8,12 +8,12 @@ namespace Org.BouncyCastle.Asn1.Cms
         : Asn1Encodable
     {
         private Asn1OctetString      subjectKeyIdentifier;
-        private DerGeneralizedTime   date;
+        private Asn1GeneralizedTime  date;
         private OtherKeyAttribute    other;
 
 		public RecipientKeyIdentifier(
             Asn1OctetString         subjectKeyIdentifier,
-            DerGeneralizedTime      date,
+            Asn1GeneralizedTime     date,
             OtherKeyAttribute       other)
         {
             this.subjectKeyIdentifier = subjectKeyIdentifier;
@@ -29,7 +29,7 @@ namespace Org.BouncyCastle.Asn1.Cms
 
 		public RecipientKeyIdentifier(
 			byte[]				subjectKeyIdentifier,
-			DerGeneralizedTime	date,
+            Asn1GeneralizedTime date,
 			OtherKeyAttribute	other)
 		{
 			this.subjectKeyIdentifier = new DerOctetString(subjectKeyIdentifier);
@@ -48,9 +48,9 @@ namespace Org.BouncyCastle.Asn1.Cms
 				case 1:
 					break;
 				case 2:
-					if (seq[1] is DerGeneralizedTime)
+					if (seq[1] is Asn1GeneralizedTime)
 					{
-						date = (DerGeneralizedTime) seq[1];
+						date = (Asn1GeneralizedTime)seq[1];
 					}
 					else
 					{
@@ -58,7 +58,7 @@ namespace Org.BouncyCastle.Asn1.Cms
 					}
 					break;
 				case 3:
-					date  = (DerGeneralizedTime) seq[1];
+					date  = (Asn1GeneralizedTime)seq[1];
 					other = OtherKeyAttribute.GetInstance(seq[2]);
 					break;
 				default:
@@ -105,7 +105,7 @@ namespace Org.BouncyCastle.Asn1.Cms
 			get { return subjectKeyIdentifier; }
 		}
 
-		public DerGeneralizedTime Date
+		public Asn1GeneralizedTime Date
 		{
 			get { return date; }
 		}
diff --git a/crypto/src/asn1/cms/Time.cs b/crypto/src/asn1/cms/Time.cs
index 47e284e69..67c73285b 100644
--- a/crypto/src/asn1/cms/Time.cs
+++ b/crypto/src/asn1/cms/Time.cs
@@ -22,7 +22,7 @@ namespace Org.BouncyCastle.Asn1.Cms
         {
             if (time == null)
                 throw new ArgumentNullException("time");
-            if (!(time is DerUtcTime) && !(time is DerGeneralizedTime))
+            if (!(time is Asn1UtcTime) && !(time is Asn1GeneralizedTime))
                 throw new ArgumentException("unknown object passed to Time");
 
             this.time = time;
@@ -33,8 +33,7 @@ namespace Org.BouncyCastle.Asn1.Cms
          * and 2049 a UTCTime object is Generated, otherwise a GeneralizedTime
          * is used.
          */
-        public Time(
-            DateTime date)
+        public Time(DateTime date)
         {
             DateTime d = date.ToUniversalTime();
 
@@ -48,15 +47,16 @@ namespace Org.BouncyCastle.Asn1.Cms
             }
         }
 
-		public static Time GetInstance(
-            object obj)
+		public static Time GetInstance(object obj)
         {
-            if (obj == null || obj is Time)
-                return (Time)obj;
-			if (obj is DerUtcTime)
-                return new Time((DerUtcTime)obj);
-			if (obj is DerGeneralizedTime)
-                return new Time((DerGeneralizedTime)obj);
+            if (obj == null)
+                return null;
+            if (obj is Time time)
+                return time;
+			if (obj is Asn1UtcTime utcTime)
+                return new Time(utcTime);
+			if (obj is Asn1GeneralizedTime generalizedTime)
+                return new Time(generalizedTime);
 
             throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj");
         }
@@ -65,14 +65,10 @@ namespace Org.BouncyCastle.Asn1.Cms
         {
 			get
 			{
-				if (time is DerUtcTime)
-				{
-					return ((DerUtcTime)time).AdjustedTimeString;
-				}
-				else
-				{
-					return ((DerGeneralizedTime)time).GetTime();
-				}
+				if (time is Asn1UtcTime utcTime)
+					return utcTime.AdjustedTimeString;
+
+                return ((Asn1GeneralizedTime)time).GetTime();
 			}
         }
 
@@ -82,12 +78,10 @@ namespace Org.BouncyCastle.Asn1.Cms
 			{
 				try
 				{
-					if (time is DerUtcTime)
-					{
-						return ((DerUtcTime)time).ToAdjustedDateTime();
-					}
+					if (time is Asn1UtcTime utcTime)
+						return utcTime.ToAdjustedDateTime();
 
-					return ((DerGeneralizedTime)time).ToDateTime();
+					return ((Asn1GeneralizedTime)time).ToDateTime();
 				}
 				catch (FormatException e)
 				{
diff --git a/crypto/src/asn1/crmf/EncryptedKey.cs b/crypto/src/asn1/crmf/EncryptedKey.cs
index 850fbd219..d4ff250c5 100644
--- a/crypto/src/asn1/crmf/EncryptedKey.cs
+++ b/crypto/src/asn1/crmf/EncryptedKey.cs
@@ -1,58 +1,44 @@
-using System;
-
-using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Cms;
 
 namespace Org.BouncyCastle.Asn1.Crmf
 {
     public class EncryptedKey
         : Asn1Encodable, IAsn1Choice
     {
-        private readonly EnvelopedData envelopedData;
-        private readonly EncryptedValue encryptedValue;
-
-        public static EncryptedKey GetInstance(object o)
+        public static EncryptedKey GetInstance(object obj)
         {
-            if (o is EncryptedKey)
-            {
-                return (EncryptedKey)o;
-            }
-            else if (o is Asn1TaggedObject)
-            {
-                return new EncryptedKey(EnvelopedData.GetInstance((Asn1TaggedObject)o, false));
-            }
-            else if (o is EncryptedValue)
-            {
-                return new EncryptedKey((EncryptedValue)o);
-            }
-            else
-            {
-                return new EncryptedKey(EncryptedValue.GetInstance(o));
-            }
+            if (obj is EncryptedKey encryptedKey)
+                return encryptedKey;
+
+            if (obj is Asn1TaggedObject taggedObject)
+                return new EncryptedKey(EnvelopedData.GetInstance(taggedObject, false));
+
+            return new EncryptedKey(EncryptedValue.GetInstance(obj));
         }
 
+        private readonly EnvelopedData m_envelopedData;
+        private readonly EncryptedValue m_encryptedValue;
+
         public EncryptedKey(EnvelopedData envelopedData)
         {
-            this.envelopedData = envelopedData;
+            m_envelopedData = envelopedData;
         }
 
         public EncryptedKey(EncryptedValue encryptedValue)
         {
-            this.encryptedValue = encryptedValue;
+            m_encryptedValue = encryptedValue;
         }
 
-        public virtual bool IsEncryptedValue
-        {
-            get { return encryptedValue != null; }
-        }
+        public virtual bool IsEncryptedValue => m_encryptedValue != null;
 
         public virtual Asn1Encodable Value
         {
             get
             {
-                if (encryptedValue != null)
-                    return encryptedValue;
+                if (m_encryptedValue != null)
+                    return m_encryptedValue;
 
-                return envelopedData;
+                return m_envelopedData;
             }
         }
 
@@ -67,12 +53,10 @@ namespace Org.BouncyCastle.Asn1.Crmf
          */
         public override Asn1Object ToAsn1Object()
         {
-            if (encryptedValue != null)
-            {
-                return encryptedValue.ToAsn1Object();
-            }
+            if (m_encryptedValue != null)
+                return m_encryptedValue.ToAsn1Object();
 
-            return new DerTaggedObject(false, 0, envelopedData);
+            return new DerTaggedObject(false, 0, m_envelopedData);
         }
     }
 }
diff --git a/crypto/src/asn1/crmf/EncryptedValue.cs b/crypto/src/asn1/crmf/EncryptedValue.cs
index 7c5cf18b4..950298504 100644
--- a/crypto/src/asn1/crmf/EncryptedValue.cs
+++ b/crypto/src/asn1/crmf/EncryptedValue.cs
@@ -7,48 +7,10 @@ namespace Org.BouncyCastle.Asn1.Crmf
     public class EncryptedValue
         : Asn1Encodable
     {
-        private readonly AlgorithmIdentifier intendedAlg;
-        private readonly AlgorithmIdentifier symmAlg;
-        private readonly DerBitString encSymmKey;
-        private readonly AlgorithmIdentifier keyAlg;
-        private readonly Asn1OctetString valueHint;
-        private readonly DerBitString encValue;
-
-        private EncryptedValue(Asn1Sequence seq)
-        {
-            int index = 0;
-            while (seq[index] is Asn1TaggedObject)
-            {
-                Asn1TaggedObject tObj = (Asn1TaggedObject)seq[index];
-
-                switch (tObj.TagNo)
-                {
-                    case 0:
-                        intendedAlg = AlgorithmIdentifier.GetInstance(tObj, false);
-                        break;
-                    case 1:
-                        symmAlg = AlgorithmIdentifier.GetInstance(tObj, false);
-                        break;
-                    case 2:
-                        encSymmKey = DerBitString.GetInstance(tObj, false);
-                        break;
-                    case 3:
-                        keyAlg = AlgorithmIdentifier.GetInstance(tObj, false);
-                        break;
-                    case 4:
-                        valueHint = Asn1OctetString.GetInstance(tObj, false);
-                        break;
-                }
-                ++index;
-            }
-
-            encValue = DerBitString.GetInstance(seq[index]);
-        }
-
         public static EncryptedValue GetInstance(object obj)
         {
-            if (obj is EncryptedValue)
-                return (EncryptedValue)obj;
+            if (obj is EncryptedValue encryptedValue)
+                return encryptedValue;
 
             if (obj != null)
                 return new EncryptedValue(Asn1Sequence.GetInstance(obj));
@@ -56,59 +18,71 @@ namespace Org.BouncyCastle.Asn1.Crmf
             return null;
         }
 
-        public EncryptedValue(
-            AlgorithmIdentifier intendedAlg,
-            AlgorithmIdentifier symmAlg,
-            DerBitString encSymmKey,
-            AlgorithmIdentifier keyAlg,
-            Asn1OctetString valueHint,
-            DerBitString encValue)
+        private readonly AlgorithmIdentifier m_intendedAlg;
+        private readonly AlgorithmIdentifier m_symmAlg;
+        private readonly DerBitString m_encSymmKey;
+        private readonly AlgorithmIdentifier m_keyAlg;
+        private readonly Asn1OctetString m_valueHint;
+        private readonly DerBitString m_encValue;
+
+        private EncryptedValue(Asn1Sequence seq)
         {
-            if (encValue == null)
+            int index = 0;
+            while (seq[index] is Asn1TaggedObject tObj)
             {
-                throw new ArgumentNullException("encValue");
+                switch (tObj.TagNo)
+                {
+                case 0:
+                    m_intendedAlg = AlgorithmIdentifier.GetInstance(tObj, false);
+                    break;
+                case 1:
+                    m_symmAlg = AlgorithmIdentifier.GetInstance(tObj, false);
+                    break;
+                case 2:
+                    m_encSymmKey = DerBitString.GetInstance(tObj, false);
+                    break;
+                case 3:
+                    m_keyAlg = AlgorithmIdentifier.GetInstance(tObj, false);
+                    break;
+                case 4:
+                    m_valueHint = Asn1OctetString.GetInstance(tObj, false);
+                    break;
+                }
+                ++index;
             }
 
-            this.intendedAlg = intendedAlg;
-            this.symmAlg = symmAlg;
-            this.encSymmKey = encSymmKey;
-            this.keyAlg = keyAlg;
-            this.valueHint = valueHint;
-            this.encValue = encValue;
+            m_encValue = DerBitString.GetInstance(seq[index]);
         }
 
-        public virtual AlgorithmIdentifier IntendedAlg
+        public EncryptedValue(AlgorithmIdentifier intendedAlg, AlgorithmIdentifier symmAlg, DerBitString encSymmKey,
+            AlgorithmIdentifier keyAlg, Asn1OctetString valueHint, DerBitString encValue)
         {
-            get { return intendedAlg; }
+            if (encValue == null)
+                throw new ArgumentNullException(nameof(encValue));
+
+            m_intendedAlg = intendedAlg;
+            m_symmAlg = symmAlg;
+            m_encSymmKey = encSymmKey;
+            m_keyAlg = keyAlg;
+            m_valueHint = valueHint;
+            m_encValue = encValue;
         }
 
-        public virtual AlgorithmIdentifier SymmAlg
-        {
-            get { return symmAlg; }
-        }
+        public virtual AlgorithmIdentifier IntendedAlg => m_intendedAlg;
 
-        public virtual DerBitString EncSymmKey
-        {
-            get { return encSymmKey; }
-        }
+        public virtual AlgorithmIdentifier SymmAlg => m_symmAlg;
 
-        public virtual AlgorithmIdentifier KeyAlg
-        {
-            get { return keyAlg; }
-        }
+        public virtual DerBitString EncSymmKey => m_encSymmKey;
 
-        public virtual Asn1OctetString ValueHint
-        {
-            get { return valueHint; }
-        }
+        public virtual AlgorithmIdentifier KeyAlg => m_keyAlg;
 
-        public virtual DerBitString EncValue
-        {
-            get { return encValue; }
-        }
+        public virtual Asn1OctetString ValueHint => m_valueHint;
+
+        public virtual DerBitString EncValue => m_encValue;
 
         /**
          * <pre>
+         * (IMPLICIT TAGS)
          * EncryptedValue ::= SEQUENCE {
          *                     intendedAlg   [0] AlgorithmIdentifier  OPTIONAL,
          *                     -- the intended algorithm for which the value will be used
@@ -131,12 +105,12 @@ namespace Org.BouncyCastle.Asn1.Crmf
         public override Asn1Object ToAsn1Object()
         {
             Asn1EncodableVector v = new Asn1EncodableVector();
-            v.AddOptionalTagged(false, 0, intendedAlg);
-            v.AddOptionalTagged(false, 1, symmAlg);
-            v.AddOptionalTagged(false, 2, encSymmKey);
-            v.AddOptionalTagged(false, 3, keyAlg);
-            v.AddOptionalTagged(false, 4, valueHint);
-            v.Add(encValue);
+            v.AddOptionalTagged(false, 0, m_intendedAlg);
+            v.AddOptionalTagged(false, 1, m_symmAlg);
+            v.AddOptionalTagged(false, 2, m_encSymmKey);
+            v.AddOptionalTagged(false, 3, m_keyAlg);
+            v.AddOptionalTagged(false, 4, m_valueHint);
+            v.Add(m_encValue);
             return new DerSequence(v);
         }
     }
diff --git a/crypto/src/asn1/crmf/PKIPublicationInfo.cs b/crypto/src/asn1/crmf/PKIPublicationInfo.cs
index a7d2bc603..c855a7d28 100644
--- a/crypto/src/asn1/crmf/PKIPublicationInfo.cs
+++ b/crypto/src/asn1/crmf/PKIPublicationInfo.cs
@@ -1,48 +1,91 @@
-using System;
-
-using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Asn1.Crmf
 {
+    /**
+     * <pre>
+     * PKIPublicationInfo ::= SEQUENCE {
+     *                  action     INTEGER {
+     *                                 dontPublish (0),
+     *                                 pleasePublish (1) },
+     *                  pubInfos  SEQUENCE SIZE (1..MAX) OF SinglePubInfo OPTIONAL }
+     * -- pubInfos MUST NOT be present if action is "dontPublish"
+     * -- (if action is "pleasePublish" and pubInfos is omitted,
+     * -- "dontCare" is assumed)
+     * </pre>
+     */
     public class PkiPublicationInfo
         : Asn1Encodable
     {
-        private readonly DerInteger action;
-        private readonly Asn1Sequence pubInfos;
+        public static readonly DerInteger DontPublish = new DerInteger(0);
+        public static readonly DerInteger PleasePublish = new DerInteger(1);
+
+        public static PkiPublicationInfo GetInstance(object obj)
+        {
+            if (obj is PkiPublicationInfo pkiPublicationInfo)
+                return pkiPublicationInfo;
+
+            if (obj != null)
+                return new PkiPublicationInfo(Asn1Sequence.GetInstance(obj));
+
+            return null;
+        }
+
+        private readonly DerInteger m_action;
+        private readonly Asn1Sequence m_pubInfos;
 
         private PkiPublicationInfo(Asn1Sequence seq)
         {
-            action = DerInteger.GetInstance(seq[0]);
-            pubInfos = Asn1Sequence.GetInstance(seq[1]);
+            m_action = DerInteger.GetInstance(seq[0]);
+            if (seq.Count > 1)
+            {
+                m_pubInfos = Asn1Sequence.GetInstance(seq[1]);
+            }
         }
 
-        public static PkiPublicationInfo GetInstance(object obj)
+        public PkiPublicationInfo(BigInteger action)
+            : this(new DerInteger(action))
         {
-            if (obj is PkiPublicationInfo)
-                return (PkiPublicationInfo)obj;
+        }
 
-            if (obj is Asn1Sequence)
-                return new PkiPublicationInfo((Asn1Sequence)obj);
+        public PkiPublicationInfo(DerInteger action)
+        {
+            m_action = action;
+        }
 
-            throw new ArgumentException("Invalid object: " + Platform.GetTypeName(obj), "obj");
+        /**
+         * Constructor with a single pubInfo, assumes pleasePublish as the action.
+         *
+         * @param pubInfo the pubInfo to be published (can be null if don't care is required).
+         */
+        public PkiPublicationInfo(SinglePubInfo pubInfo)
+            : this(pubInfo != null ? new SinglePubInfo[1]{ pubInfo } : null)
+        {
         }
 
-        public virtual DerInteger Action
+        /**
+         * Constructor with multiple pubInfo, assumes pleasePublish as the action.
+         *
+         * @param pubInfos the pubInfos to be published (can be null if don't care is required).
+         */
+        public PkiPublicationInfo(SinglePubInfo[] pubInfos)
         {
-            get { return action; }
+            m_action = PleasePublish;
+
+            if (pubInfos != null)
+            {
+                m_pubInfos = new DerSequence(pubInfos);
+            }
         }
 
+        public virtual DerInteger Action => m_action;
+
         public virtual SinglePubInfo[] GetPubInfos()
         {
-            if (pubInfos == null)
+            if (m_pubInfos == null)
                 return null;
 
-            SinglePubInfo[] results = new SinglePubInfo[pubInfos.Count];
-            for (int i = 0; i != results.Length; ++i)
-            {
-                results[i] = SinglePubInfo.GetInstance(pubInfos[i]);
-            }
-            return results;
+            return m_pubInfos.MapElements(SinglePubInfo.GetInstance);
         }
 
         /**
@@ -60,7 +103,10 @@ namespace Org.BouncyCastle.Asn1.Crmf
          */
         public override Asn1Object ToAsn1Object()
         {
-            return new DerSequence(action, pubInfos);
+            if (m_pubInfos == null)
+                return new DerSequence(m_action);
+
+            return new DerSequence(m_action, m_pubInfos);
         }
     }
 }
diff --git a/crypto/src/asn1/cryptlib/CryptlibObjectIdentifiers.cs b/crypto/src/asn1/cryptlib/CryptlibObjectIdentifiers.cs
new file mode 100644
index 000000000..e7208bab2
--- /dev/null
+++ b/crypto/src/asn1/cryptlib/CryptlibObjectIdentifiers.cs
@@ -0,0 +1,11 @@
+namespace Org.BouncyCastle.Asn1.Cryptlib
+{
+    internal class CryptlibObjectIdentifiers
+    {
+        internal static readonly DerObjectIdentifier cryptlib = new DerObjectIdentifier("1.3.6.1.4.1.3029");
+
+        internal static readonly DerObjectIdentifier ecc = cryptlib.Branch("1.5");
+
+        internal static readonly DerObjectIdentifier curvey25519 = ecc.Branch("1");
+    }
+}
diff --git a/crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs b/crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs
index e2f2c1848..5e3cb4781 100644
--- a/crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs
+++ b/crypto/src/asn1/cryptopro/CryptoProObjectIdentifiers.cs
@@ -13,7 +13,9 @@ namespace Org.BouncyCastle.Asn1.CryptoPro
 		public static readonly DerObjectIdentifier GostR3411 = new DerObjectIdentifier(GostID + ".9");
         public static readonly DerObjectIdentifier GostR3411Hmac = new DerObjectIdentifier(GostID + ".10");
 
-        public static readonly DerObjectIdentifier GostR28147Cbc = new DerObjectIdentifier(GostID + ".21");
+        public static readonly DerObjectIdentifier GostR28147Gcfb = new DerObjectIdentifier(GostID + ".21");
+        [Obsolete("Use 'GostR28147Gcfb' instead")]
+        public static readonly DerObjectIdentifier GostR28147Cbc = GostR28147Gcfb;
 
         public static readonly DerObjectIdentifier ID_Gost28147_89_CryptoPro_A_ParamSet = new DerObjectIdentifier(GostID + ".31.1");
 
@@ -47,5 +49,9 @@ namespace Org.BouncyCastle.Asn1.CryptoPro
 
 		public static readonly DerObjectIdentifier GostElSgDH3410Default = new DerObjectIdentifier(GostID + ".36.0");
         public static readonly DerObjectIdentifier GostElSgDH3410x1 = new DerObjectIdentifier(GostID + ".36.1");
+
+        public static readonly DerObjectIdentifier GostR3410x2001CryptoProESDH = new DerObjectIdentifier(GostID + ".96");
+
+        public static readonly DerObjectIdentifier GostR3410x2001DH = new DerObjectIdentifier(GostID + ".98");
     }
 }
diff --git a/crypto/src/asn1/esf/CrlIdentifier.cs b/crypto/src/asn1/esf/CrlIdentifier.cs
index a8e40c870..44c99170c 100644
--- a/crypto/src/asn1/esf/CrlIdentifier.cs
+++ b/crypto/src/asn1/esf/CrlIdentifier.cs
@@ -1,5 +1,5 @@
 using System;
-
+using System.Reflection;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities;
@@ -21,7 +21,7 @@ namespace Org.BouncyCastle.Asn1.Esf
 		: Asn1Encodable
 	{
 		private readonly X509Name	crlIssuer;
-		private readonly DerUtcTime	crlIssuedTime;
+		private readonly Asn1UtcTime crlIssuedTime;
 		private readonly DerInteger	crlNumber;
 
 		public static CrlIdentifier GetInstance(
@@ -48,7 +48,7 @@ namespace Org.BouncyCastle.Asn1.Esf
 				throw new ArgumentException("Bad sequence size: " + seq.Count, "seq");
 
 			this.crlIssuer = X509Name.GetInstance(seq[0]);
-			this.crlIssuedTime = DerUtcTime.GetInstance(seq[1]);
+			this.crlIssuedTime = Asn1UtcTime.GetInstance(seq[1]);
 
 			if (seq.Count > 2)
 			{
@@ -56,31 +56,36 @@ namespace Org.BouncyCastle.Asn1.Esf
 			}
 		}
 
-		public CrlIdentifier(
-			X509Name	crlIssuer,
-			DateTime	crlIssuedTime)
-			: this(crlIssuer, crlIssuedTime, null)
+        public CrlIdentifier(X509Name crlIssuer, DateTime crlIssuedTime)
+            : this(crlIssuer, crlIssuedTime, null)
 		{
 		}
 
-		public CrlIdentifier(
-			X509Name	crlIssuer,
-			DateTime	crlIssuedTime,
-			BigInteger	crlNumber)
+		public CrlIdentifier(X509Name crlIssuer, DateTime crlIssuedTime, BigInteger crlNumber)
+			: this(crlIssuer, new Asn1UtcTime(crlIssuedTime), crlNumber)
 		{
-			if (crlIssuer == null)
-				throw new ArgumentNullException("crlIssuer");
+		}
 
-			this.crlIssuer = crlIssuer;
-			this.crlIssuedTime = new DerUtcTime(crlIssuedTime);
+        public CrlIdentifier(X509Name crlIssuer, Asn1UtcTime crlIssuedTime)
+            : this(crlIssuer, crlIssuedTime, null)
+        {
+        }
 
-			if (crlNumber != null)
-			{
-				this.crlNumber = new DerInteger(crlNumber);
-			}
-		}
+        public CrlIdentifier(X509Name crlIssuer, Asn1UtcTime crlIssuedTime, BigInteger crlNumber)
+        {
+            if (crlIssuer == null)
+                throw new ArgumentNullException(nameof(crlIssuer));
+
+            this.crlIssuer = crlIssuer;
+            this.crlIssuedTime = crlIssuedTime;
+
+            if (null != crlNumber)
+            {
+                this.crlNumber = new DerInteger(crlNumber);
+            }
+        }
 
-		public X509Name CrlIssuer
+        public X509Name CrlIssuer
 		{
 			get { return crlIssuer; }
 		}
diff --git a/crypto/src/asn1/esf/OcspIdentifier.cs b/crypto/src/asn1/esf/OcspIdentifier.cs
index e65f1cfe7..fa7069aed 100644
--- a/crypto/src/asn1/esf/OcspIdentifier.cs
+++ b/crypto/src/asn1/esf/OcspIdentifier.cs
@@ -20,7 +20,7 @@ namespace Org.BouncyCastle.Asn1.Esf
 		: Asn1Encodable
 	{
 		private readonly ResponderID		ocspResponderID;
-		private readonly DerGeneralizedTime	producedAt;
+		private readonly Asn1GeneralizedTime producedAt;
 
 		public static OcspIdentifier GetInstance(
 			object obj)
@@ -46,21 +46,30 @@ namespace Org.BouncyCastle.Asn1.Esf
 				throw new ArgumentException("Bad sequence size: " + seq.Count, "seq");
 
 			this.ocspResponderID = ResponderID.GetInstance(seq[0].ToAsn1Object());
-			this.producedAt = (DerGeneralizedTime) seq[1].ToAsn1Object();
+			this.producedAt = (Asn1GeneralizedTime)seq[1].ToAsn1Object();
 		}
 
-		public OcspIdentifier(
-			ResponderID	ocspResponderID,
-			DateTime	producedAt)
+		public OcspIdentifier(ResponderID ocspResponderID, DateTime producedAt)
 		{
 			if (ocspResponderID == null)
-				throw new ArgumentNullException();
+				throw new ArgumentNullException(nameof(ocspResponderID));
 
 			this.ocspResponderID = ocspResponderID;
-			this.producedAt = new DerGeneralizedTime(producedAt);
+			this.producedAt = new Asn1GeneralizedTime(producedAt);
 		}
 
-		public ResponderID OcspResponderID
+        public OcspIdentifier(ResponderID ocspResponderID, Asn1GeneralizedTime producedAt)
+        {
+            if (ocspResponderID == null)
+                throw new ArgumentNullException(nameof(ocspResponderID));
+            if (producedAt == null)
+                throw new ArgumentNullException(nameof(producedAt));
+
+            this.ocspResponderID = ocspResponderID;
+            this.producedAt = producedAt;
+        }
+
+        public ResponderID OcspResponderID
 		{
 			get { return ocspResponderID; }
 		}
diff --git a/crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs b/crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs
index b82c9373d..c9c96cbda 100644
--- a/crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs
+++ b/crypto/src/asn1/isismtt/x509/DeclarationOfMajority.cs
@@ -64,7 +64,7 @@ namespace Org.BouncyCastle.Asn1.IsisMtt.X509
 		}
 
 		public DeclarationOfMajority(
-			DerGeneralizedTime dateOfBirth)
+            Asn1GeneralizedTime dateOfBirth)
 		{
 			this.declaration = new DerTaggedObject(false, 2, dateOfBirth);
 		}
@@ -155,14 +155,14 @@ namespace Org.BouncyCastle.Asn1.IsisMtt.X509
 			}
 		}
 
-		public virtual DerGeneralizedTime DateOfBirth
+		public virtual Asn1GeneralizedTime DateOfBirth
 		{
 			get
 			{
 				switch ((Choice) declaration.TagNo)
 				{
 					case Choice.DateOfBirth:
-						return DerGeneralizedTime.GetInstance(declaration, false);
+						return Asn1GeneralizedTime.GetInstance(declaration, false);
 					default:
 						return null;
 				}
diff --git a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
index 54e4280b9..693c0eaba 100644
--- a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
+++ b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
@@ -40,15 +40,15 @@ namespace Org.BouncyCastle.Asn1.Misc
         // Novell
         //       iso/itu(2) country(16) us(840) organization(1) novell(113719)
         //
-        public static readonly string				Novell					= "2.16.840.1.113719";
-        public static readonly DerObjectIdentifier NovellSecurityAttribs	= new DerObjectIdentifier(Novell + ".1.9.4.1");
+        public static readonly DerObjectIdentifier Novell = new DerObjectIdentifier("2.16.840.1.113719");
+        public static readonly DerObjectIdentifier NovellSecurityAttribs = Novell.Branch("1.9.4.1");
 
         //
         // Entrust
         //       iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7)
         //
-        public static readonly string				Entrust					= "1.2.840.113533.7";
-        public static readonly DerObjectIdentifier EntrustVersionExtension = new DerObjectIdentifier(Entrust + ".65.0");
+        public static readonly DerObjectIdentifier Entrust = new DerObjectIdentifier("1.2.840.113533.7");
+        public static readonly DerObjectIdentifier EntrustVersionExtension = Entrust.Branch("65.0");
 
         public static readonly DerObjectIdentifier cast5CBC = new DerObjectIdentifier(Entrust+ ".66.10");
 
@@ -89,10 +89,32 @@ namespace Org.BouncyCastle.Asn1.Misc
         public static readonly DerObjectIdentifier id_blake2s224 = blake2.Branch("2.7");
         public static readonly DerObjectIdentifier id_blake2s256 = blake2.Branch("2.8");
 
+        public static readonly DerObjectIdentifier blake3 = blake2.Branch("3");
+
+        public static readonly DerObjectIdentifier blake3_256 = blake3.Branch("8");
+
         //
         // Scrypt
         public static readonly DerObjectIdentifier id_scrypt = new DerObjectIdentifier("1.3.6.1.4.1.11591.4.11");
 
-        public static readonly DerObjectIdentifier id_oracle_pkcs12_trusted_key_usage = new DerObjectIdentifier("2.16.840.1.113894.746875.1.1");
+        // 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) }
+        public static readonly DerObjectIdentifier id_alg_composite = new DerObjectIdentifier("1.3.6.1.4.1.18227.2.1");
+
+        // -- To be replaced by IANA
+        //
+        //id-composite-key OBJECT IDENTIFIER ::= {
+        //
+        //    joint-iso-itu-t(2) country(16) us(840) organization(1) entrust(114027)
+        //
+        //    Algorithm(80) Composite(4) CompositeKey(1)
+        public static readonly DerObjectIdentifier id_composite_key =
+            new DerObjectIdentifier("2.16.840.1.114027.80.4.1");
+
+        public static readonly DerObjectIdentifier id_oracle_pkcs12_trusted_key_usage =
+            new DerObjectIdentifier("2.16.840.1.113894.746875.1.1");
     }
 }
diff --git a/crypto/src/asn1/ocsp/CrlID.cs b/crypto/src/asn1/ocsp/CrlID.cs
index fc1e59d22..24dda4edf 100644
--- a/crypto/src/asn1/ocsp/CrlID.cs
+++ b/crypto/src/asn1/ocsp/CrlID.cs
@@ -7,7 +7,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
     {
         private readonly DerIA5String		crlUrl;
         private readonly DerInteger			crlNum;
-        private readonly DerGeneralizedTime	crlTime;
+        private readonly Asn1GeneralizedTime crlTime;
 
 		// TODO Add GetInstance method(s) and make this private?
 		public CrlID(Asn1Sequence seq)
@@ -23,7 +23,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
                     crlNum = DerInteger.GetInstance(o, true);
                     break;
                 case 2:
-                    crlTime = DerGeneralizedTime.GetInstance(o, true);
+                    crlTime = Asn1GeneralizedTime.GetInstance(o, true);
                     break;
                 default:
                     throw new ArgumentException("unknown tag number: " + o.TagNo);
@@ -41,7 +41,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 			get { return crlNum; }
 		}
 
-		public DerGeneralizedTime CrlTime
+		public Asn1GeneralizedTime CrlTime
 		{
 			get { return crlTime; }
 		}
diff --git a/crypto/src/asn1/ocsp/ResponseData.cs b/crypto/src/asn1/ocsp/ResponseData.cs
index a5769c0fa..dfb234bc1 100644
--- a/crypto/src/asn1/ocsp/ResponseData.cs
+++ b/crypto/src/asn1/ocsp/ResponseData.cs
@@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 		private readonly bool                versionPresent;
 		private readonly DerInteger          version;
 		private readonly ResponderID         responderID;
-		private readonly DerGeneralizedTime  producedAt;
+		private readonly Asn1GeneralizedTime producedAt;
 		private readonly Asn1Sequence        responses;
 		private readonly X509Extensions      responseExtensions;
 
@@ -43,7 +43,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 		public ResponseData(
 			DerInteger          version,
 			ResponderID         responderID,
-			DerGeneralizedTime  producedAt,
+            Asn1GeneralizedTime producedAt,
 			Asn1Sequence        responses,
 			X509Extensions      responseExtensions)
 		{
@@ -56,7 +56,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 
 		public ResponseData(
 			ResponderID         responderID,
-			DerGeneralizedTime  producedAt,
+            Asn1GeneralizedTime producedAt,
 			Asn1Sequence        responses,
 			X509Extensions      responseExtensions)
 			: this(V1, responderID, producedAt, responses, responseExtensions)
@@ -90,7 +90,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 			}
 
 			this.responderID = ResponderID.GetInstance(seq[index++]);
-			this.producedAt = (DerGeneralizedTime)seq[index++];
+			this.producedAt = (Asn1GeneralizedTime)seq[index++];
 			this.responses = (Asn1Sequence)seq[index++];
 
 			if (seq.Count > index)
@@ -110,7 +110,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 			get { return responderID; }
 		}
 
-		public DerGeneralizedTime ProducedAt
+		public Asn1GeneralizedTime ProducedAt
 		{
 			get { return producedAt; }
 		}
diff --git a/crypto/src/asn1/ocsp/RevokedInfo.cs b/crypto/src/asn1/ocsp/RevokedInfo.cs
index c67be0678..e6438dd08 100644
--- a/crypto/src/asn1/ocsp/RevokedInfo.cs
+++ b/crypto/src/asn1/ocsp/RevokedInfo.cs
@@ -8,7 +8,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
     public class RevokedInfo
         : Asn1Encodable
     {
-        private readonly DerGeneralizedTime revocationTime;
+        private readonly Asn1GeneralizedTime revocationTime;
         private readonly CrlReason revocationReason;
 
 		public static RevokedInfo GetInstance(
@@ -35,13 +35,13 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 		}
 
 		public RevokedInfo(
-			DerGeneralizedTime revocationTime)
+            Asn1GeneralizedTime revocationTime)
 			: this(revocationTime, null)
 		{
 		}
 
 		public RevokedInfo(
-            DerGeneralizedTime  revocationTime,
+            Asn1GeneralizedTime revocationTime,
             CrlReason           revocationReason)
         {
 			if (revocationTime == null)
@@ -54,7 +54,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 		private RevokedInfo(
             Asn1Sequence seq)
         {
-            this.revocationTime = (DerGeneralizedTime) seq[0];
+            this.revocationTime = (Asn1GeneralizedTime)seq[0];
 
 			if (seq.Count > 1)
             {
@@ -63,7 +63,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
             }
         }
 
-		public DerGeneralizedTime RevocationTime
+		public Asn1GeneralizedTime RevocationTime
 		{
 			get { return revocationTime; }
 		}
diff --git a/crypto/src/asn1/ocsp/SingleResponse.cs b/crypto/src/asn1/ocsp/SingleResponse.cs
index ecdf3dab0..42b451af7 100644
--- a/crypto/src/asn1/ocsp/SingleResponse.cs
+++ b/crypto/src/asn1/ocsp/SingleResponse.cs
@@ -10,15 +10,15 @@ namespace Org.BouncyCastle.Asn1.Ocsp
     {
         private readonly CertID              certID;
         private readonly CertStatus          certStatus;
-        private readonly DerGeneralizedTime  thisUpdate;
-        private readonly DerGeneralizedTime  nextUpdate;
+        private readonly Asn1GeneralizedTime thisUpdate;
+        private readonly Asn1GeneralizedTime nextUpdate;
         private readonly X509Extensions      singleExtensions;
 
 		public SingleResponse(
             CertID              certID,
             CertStatus          certStatus,
-            DerGeneralizedTime  thisUpdate,
-            DerGeneralizedTime  nextUpdate,
+            Asn1GeneralizedTime thisUpdate,
+            Asn1GeneralizedTime nextUpdate,
             X509Extensions      singleExtensions)
         {
             this.certID = certID;
@@ -33,11 +33,11 @@ namespace Org.BouncyCastle.Asn1.Ocsp
         {
             this.certID = CertID.GetInstance(seq[0]);
             this.certStatus = CertStatus.GetInstance(seq[1]);
-            this.thisUpdate = (DerGeneralizedTime)seq[2];
+            this.thisUpdate = (Asn1GeneralizedTime)seq[2];
 
 			if (seq.Count > 4)
             {
-                this.nextUpdate = DerGeneralizedTime.GetInstance(
+                this.nextUpdate = Asn1GeneralizedTime.GetInstance(
 					(Asn1TaggedObject) seq[3], true);
                 this.singleExtensions = X509Extensions.GetInstance(
 					(Asn1TaggedObject) seq[4], true);
@@ -48,7 +48,7 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 
 				if (o.TagNo == 0)
                 {
-                    this.nextUpdate = DerGeneralizedTime.GetInstance(o, true);
+                    this.nextUpdate = Asn1GeneralizedTime.GetInstance(o, true);
                 }
                 else
                 {
@@ -90,12 +90,12 @@ namespace Org.BouncyCastle.Asn1.Ocsp
 			get { return certStatus; }
 		}
 
-		public DerGeneralizedTime ThisUpdate
+		public Asn1GeneralizedTime ThisUpdate
 		{
 			get { return thisUpdate; }
 		}
 
-		public DerGeneralizedTime NextUpdate
+		public Asn1GeneralizedTime NextUpdate
 		{
 			get { return nextUpdate; }
 		}
diff --git a/crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs b/crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs
index 1a6a5417a..570e0ded7 100644
--- a/crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs
+++ b/crypto/src/asn1/pkcs/PKCSObjectIdentifiers.cs
@@ -138,11 +138,12 @@ namespace Org.BouncyCastle.Asn1.Pkcs
 
         public static readonly DerObjectIdentifier IdAlg = IdSmime.Branch("3");
 
-        public static readonly DerObjectIdentifier IdAlgEsdh        = IdAlg.Branch("5");
-        public static readonly DerObjectIdentifier IdAlgCms3DesWrap = IdAlg.Branch("6");
-        public static readonly DerObjectIdentifier IdAlgCmsRC2Wrap  = IdAlg.Branch("7");
-        public static readonly DerObjectIdentifier IdAlgPwriKek     = IdAlg.Branch("9");
-        public static readonly DerObjectIdentifier IdAlgSsdh        = IdAlg.Branch("10");
+        public static readonly DerObjectIdentifier IdAlgEsdh            = IdAlg.Branch("5");
+        public static readonly DerObjectIdentifier IdAlgCms3DesWrap     = IdAlg.Branch("6");
+        public static readonly DerObjectIdentifier IdAlgCmsRC2Wrap      = IdAlg.Branch("7");
+        public static readonly DerObjectIdentifier IdAlgZlibCompress    = IdAlg.Branch("8");
+        public static readonly DerObjectIdentifier IdAlgPwriKek         = IdAlg.Branch("9");
+        public static readonly DerObjectIdentifier IdAlgSsdh            = IdAlg.Branch("10");
 
         /*
          * <pre>
diff --git a/crypto/src/asn1/sec/ECPrivateKeyStructure.cs b/crypto/src/asn1/sec/ECPrivateKeyStructure.cs
index 769ab2970..89b87bf2b 100644
--- a/crypto/src/asn1/sec/ECPrivateKeyStructure.cs
+++ b/crypto/src/asn1/sec/ECPrivateKeyStructure.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities;
 
@@ -12,23 +11,20 @@ namespace Org.BouncyCastle.Asn1.Sec
     public class ECPrivateKeyStructure
         : Asn1Encodable
     {
-        private readonly Asn1Sequence seq;
+        private readonly Asn1Sequence m_seq;
 
         public static ECPrivateKeyStructure GetInstance(object obj)
         {
             if (obj == null)
                 return null;
-            if (obj is ECPrivateKeyStructure)
-                return (ECPrivateKeyStructure)obj;
+            if (obj is ECPrivateKeyStructure ecPrivateKeyStructure)
+                return ecPrivateKeyStructure;
             return new ECPrivateKeyStructure(Asn1Sequence.GetInstance(obj));
         }
 
         private ECPrivateKeyStructure(Asn1Sequence seq)
         {
-            if (seq == null)
-                throw new ArgumentNullException("seq");
-
-            this.seq = seq;
+            m_seq = seq ?? throw new ArgumentNullException(nameof(seq));
         }
 
         public ECPrivateKeyStructure(
@@ -53,9 +49,9 @@ namespace Org.BouncyCastle.Asn1.Sec
             Asn1Encodable   parameters)
         {
             if (key == null)
-                throw new ArgumentNullException("key");
+                throw new ArgumentNullException(nameof(key));
             if (orderBitLength < key.BitLength)
-                throw new ArgumentException("must be >= key bitlength", "orderBitLength");
+                throw new ArgumentException("must be >= key bitlength", nameof(orderBitLength));
 
             byte[] bytes = BigIntegers.AsUnsignedByteArray((orderBitLength + 7) / 8, key);
 
@@ -63,48 +59,42 @@ namespace Org.BouncyCastle.Asn1.Sec
                 new DerInteger(1),
                 new DerOctetString(bytes));
 
-            if (parameters != null)
-            {
-                v.Add(new DerTaggedObject(true, 0, parameters));
-            }
-
-            if (publicKey != null)
-            {
-                v.Add(new DerTaggedObject(true, 1, publicKey));
-            }
+            v.AddOptionalTagged(true, 0, parameters);
+            v.AddOptionalTagged(true, 1, publicKey);
 
-            this.seq = new DerSequence(v);
+            m_seq = new DerSequence(v);
         }
 
         public virtual BigInteger GetKey()
         {
-            Asn1OctetString octs = (Asn1OctetString) seq[1];
+            Asn1OctetString octs = (Asn1OctetString)m_seq[1];
 
             return new BigInteger(1, octs.GetOctets());
         }
 
         public virtual DerBitString GetPublicKey()
         {
-            return (DerBitString) GetObjectInTag(1);
+            return (DerBitString)GetObjectInTag(1, Asn1Tags.BitString);
         }
 
         public virtual Asn1Object GetParameters()
         {
-            return GetObjectInTag(0);
+            return GetObjectInTag(0, -1);
         }
 
-        private Asn1Object GetObjectInTag(int tagNo)
+        private Asn1Object GetObjectInTag(int tagNo, int baseTagNo)
         {
-            foreach (Asn1Encodable ae in seq)
+            foreach (Asn1Encodable ae in m_seq)
             {
                 Asn1Object obj = ae.ToAsn1Object();
 
-                if (obj is Asn1TaggedObject)
+                if (obj is Asn1TaggedObject tag)
                 {
-                    Asn1TaggedObject tag = (Asn1TaggedObject) obj;
-                    if (tag.TagNo == tagNo)
+                    if (tag.HasContextTag(tagNo))
                     {
-                        return tag.GetObject();
+                        return baseTagNo < 0
+                            ? tag.GetExplicitBaseObject().ToAsn1Object()
+                            : tag.GetBaseUniversal(true, baseTagNo);
                     }
                 }
             }
@@ -121,7 +111,7 @@ namespace Org.BouncyCastle.Asn1.Sec
          */
         public override Asn1Object ToAsn1Object()
         {
-            return seq;
+            return m_seq;
         }
     }
 }
diff --git a/crypto/src/asn1/tsp/TSTInfo.cs b/crypto/src/asn1/tsp/TSTInfo.cs
index 28a840e77..dde11494c 100644
--- a/crypto/src/asn1/tsp/TSTInfo.cs
+++ b/crypto/src/asn1/tsp/TSTInfo.cs
@@ -11,7 +11,7 @@ namespace Org.BouncyCastle.Asn1.Tsp
 		private readonly DerObjectIdentifier	tsaPolicyId;
 		private readonly MessageImprint			messageImprint;
 		private readonly DerInteger				serialNumber;
-		private readonly DerGeneralizedTime		genTime;
+		private readonly Asn1GeneralizedTime	genTime;
 		private readonly Accuracy				accuracy;
 		private readonly DerBoolean				ordering;
 		private readonly DerInteger				nonce;
@@ -49,7 +49,7 @@ namespace Org.BouncyCastle.Asn1.Tsp
 
 			// genTime
 			e.MoveNext();
-			genTime = DerGeneralizedTime.GetInstance(e.Current);
+			genTime = Asn1GeneralizedTime.GetInstance(e.Current);
 
 			// default for ordering
 			ordering = DerBoolean.False;
@@ -96,7 +96,7 @@ namespace Org.BouncyCastle.Asn1.Tsp
 			DerObjectIdentifier	tsaPolicyId,
 			MessageImprint		messageImprint,
 			DerInteger			serialNumber,
-			DerGeneralizedTime	genTime,
+            Asn1GeneralizedTime genTime,
 			Accuracy			accuracy,
 			DerBoolean			ordering,
 			DerInteger			nonce,
@@ -140,7 +140,7 @@ namespace Org.BouncyCastle.Asn1.Tsp
 			get { return accuracy; }
 		}
 
-		public DerGeneralizedTime GenTime
+		public Asn1GeneralizedTime GenTime
 		{
 			get { return genTime; }
 		}
diff --git a/crypto/src/asn1/util/Asn1Dump.cs b/crypto/src/asn1/util/Asn1Dump.cs
index 7bae766c3..6c005f512 100644
--- a/crypto/src/asn1/util/Asn1Dump.cs
+++ b/crypto/src/asn1/util/Asn1Dump.cs
@@ -206,12 +206,12 @@ namespace Org.BouncyCastle.Asn1.Utilities
                 buf.Append(indent);
                 buf.AppendLine("VideotexString(" + videotexString.GetString() + ")");
             }
-            else if (obj is DerUtcTime utcTime)
+            else if (obj is Asn1UtcTime utcTime)
             {
                 buf.Append(indent);
                 buf.AppendLine("UTCTime(" + utcTime.TimeString + ")");
             }
-            else if (obj is DerGeneralizedTime generalizedTime)
+            else if (obj is Asn1GeneralizedTime generalizedTime)
             {
                 buf.Append(indent);
                 buf.AppendLine("GeneralizedTime(" + generalizedTime.GetTime() + ")");
diff --git a/crypto/src/asn1/x509/AttCertValidityPeriod.cs b/crypto/src/asn1/x509/AttCertValidityPeriod.cs
index d31e07402..893bc0838 100644
--- a/crypto/src/asn1/x509/AttCertValidityPeriod.cs
+++ b/crypto/src/asn1/x509/AttCertValidityPeriod.cs
@@ -7,8 +7,8 @@ namespace Org.BouncyCastle.Asn1.X509
     public class AttCertValidityPeriod
         : Asn1Encodable
     {
-        private readonly DerGeneralizedTime	notBeforeTime;
-        private readonly DerGeneralizedTime	notAfterTime;
+        private readonly Asn1GeneralizedTime notBeforeTime;
+        private readonly Asn1GeneralizedTime notAfterTime;
 
 		public static AttCertValidityPeriod GetInstance(
             object obj)
@@ -39,24 +39,24 @@ namespace Org.BouncyCastle.Asn1.X509
 			if (seq.Count != 2)
 				throw new ArgumentException("Bad sequence size: " + seq.Count);
 
-			notBeforeTime = DerGeneralizedTime.GetInstance(seq[0]);
-			notAfterTime = DerGeneralizedTime.GetInstance(seq[1]);
+			notBeforeTime = Asn1GeneralizedTime.GetInstance(seq[0]);
+			notAfterTime = Asn1GeneralizedTime.GetInstance(seq[1]);
         }
 
 		public AttCertValidityPeriod(
-            DerGeneralizedTime	notBeforeTime,
-            DerGeneralizedTime	notAfterTime)
+            Asn1GeneralizedTime notBeforeTime,
+            Asn1GeneralizedTime notAfterTime)
         {
             this.notBeforeTime = notBeforeTime;
             this.notAfterTime = notAfterTime;
         }
 
-		public DerGeneralizedTime NotBeforeTime
+		public Asn1GeneralizedTime NotBeforeTime
 		{
 			get { return notBeforeTime; }
 		}
 
-		public DerGeneralizedTime NotAfterTime
+		public Asn1GeneralizedTime NotAfterTime
 		{
 			get { return notAfterTime; }
 		}
diff --git a/crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs b/crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs
index 89e8de6cb..a87c2ee9e 100644
--- a/crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs
+++ b/crypto/src/asn1/x509/PrivateKeyUsagePeriod.cs
@@ -36,7 +36,7 @@ namespace Org.BouncyCastle.Asn1.X509
 			throw new ArgumentException("unknown object in GetInstance: " + Platform.GetTypeName(obj), "obj");
 		}
 
-		private DerGeneralizedTime _notBefore, _notAfter;
+		private Asn1GeneralizedTime _notBefore, _notAfter;
 
 		private PrivateKeyUsagePeriod(
 			Asn1Sequence seq)
@@ -45,21 +45,21 @@ namespace Org.BouncyCastle.Asn1.X509
 			{
 				if (tObj.TagNo == 0)
 				{
-					_notBefore = DerGeneralizedTime.GetInstance(tObj, false);
+					_notBefore = Asn1GeneralizedTime.GetInstance(tObj, false);
 				}
 				else if (tObj.TagNo == 1)
 				{
-					_notAfter = DerGeneralizedTime.GetInstance(tObj, false);
+					_notAfter = Asn1GeneralizedTime.GetInstance(tObj, false);
 				}
 			}
 		}
 
-		public DerGeneralizedTime NotBefore
+		public Asn1GeneralizedTime NotBefore
 		{
 			get { return _notBefore; }
 		}
 
-		public DerGeneralizedTime NotAfter
+		public Asn1GeneralizedTime NotAfter
 		{
 			get { return _notAfter; }
 		}
diff --git a/crypto/src/asn1/x509/TBSCertList.cs b/crypto/src/asn1/x509/TBSCertList.cs
index ab847d563..2b288850f 100644
--- a/crypto/src/asn1/x509/TBSCertList.cs
+++ b/crypto/src/asn1/x509/TBSCertList.cs
@@ -193,8 +193,8 @@ namespace Org.BouncyCastle.Asn1.X509
             thisUpdate = Time.GetInstance(seq[seqPos++]);
 
 			if (seqPos < seq.Count
-                && (seq[seqPos] is DerUtcTime
-                   || seq[seqPos] is DerGeneralizedTime
+                && (seq[seqPos] is Asn1UtcTime
+                   || seq[seqPos] is Asn1GeneralizedTime
                    || seq[seqPos] is Time))
             {
                 nextUpdate = Time.GetInstance(seq[seqPos++]);
diff --git a/crypto/src/asn1/x509/Time.cs b/crypto/src/asn1/x509/Time.cs
index 26c972904..8260043aa 100644
--- a/crypto/src/asn1/x509/Time.cs
+++ b/crypto/src/asn1/x509/Time.cs
@@ -22,7 +22,7 @@ namespace Org.BouncyCastle.Asn1.X509
         {
             if (time == null)
                 throw new ArgumentNullException("time");
-            if (!(time is DerUtcTime) && !(time is DerGeneralizedTime))
+            if (!(time is Asn1UtcTime) && !(time is Asn1GeneralizedTime))
                 throw new ArgumentException("unknown object passed to Time");
 
             this.time = time;
@@ -47,27 +47,26 @@ namespace Org.BouncyCastle.Asn1.X509
             }
         }
 
-        public static Time GetInstance(
-            object obj)
+        public static Time GetInstance(object obj)
         {
-            if (obj == null || obj is Time)
-                return (Time)obj;
-            if (obj is DerUtcTime)
-                return new Time((DerUtcTime)obj);
-            if (obj is DerGeneralizedTime)
-                return new Time((DerGeneralizedTime)obj);
+            if (obj == null)
+                return null;
+            if (obj is Time time)
+                return time;
+            if (obj is Asn1UtcTime utcTime)
+                return new Time(utcTime);
+            if (obj is Asn1GeneralizedTime generalizedTime)
+                return new Time(generalizedTime);
 
             throw new ArgumentException("unknown object in factory: " + Platform.GetTypeName(obj), "obj");
         }
 
         public string GetTime()
         {
-            if (time is DerUtcTime)
-            {
-                return ((DerUtcTime) time).AdjustedTimeString;
-            }
+            if (time is Asn1UtcTime utcTime)
+                return utcTime.AdjustedTimeString;
 
-            return ((DerGeneralizedTime) time).GetTime();
+            return ((Asn1GeneralizedTime)time).GetTime();
         }
 
         /// <summary>
@@ -78,14 +77,10 @@ namespace Org.BouncyCastle.Asn1.X509
         {
             try
             {
-                if (time is DerUtcTime)
-                {
-                    return ((DerUtcTime)time).ToAdjustedDateTime();
-                }
-                else
-                {
-                    return ((DerGeneralizedTime)time).ToDateTime();
-                }
+                if (time is Asn1UtcTime utcTime)
+                    return utcTime.ToAdjustedDateTime();
+
+                return ((Asn1GeneralizedTime)time).ToDateTime();
             }
             catch (FormatException e)
             {
diff --git a/crypto/src/asn1/x509/V1TBSCertificateGenerator.cs b/crypto/src/asn1/x509/V1TBSCertificateGenerator.cs
index 20b525a48..9cbff1ef0 100644
--- a/crypto/src/asn1/x509/V1TBSCertificateGenerator.cs
+++ b/crypto/src/asn1/x509/V1TBSCertificateGenerator.cs
@@ -56,7 +56,7 @@ namespace Org.BouncyCastle.Asn1.X509
         }
 
 		public void SetStartDate(
-            DerUtcTime startDate)
+            Asn1UtcTime startDate)
         {
             this.startDate = new Time(startDate);
         }
@@ -68,7 +68,7 @@ namespace Org.BouncyCastle.Asn1.X509
         }
 
 		public void SetEndDate(
-            DerUtcTime endDate)
+            Asn1UtcTime endDate)
         {
             this.endDate = new Time(endDate);
         }
diff --git a/crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs b/crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs
index 02580b5b8..c78c966b0 100644
--- a/crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs
+++ b/crypto/src/asn1/x509/V2AttributeCertificateInfoGenerator.cs
@@ -26,11 +26,13 @@ namespace Org.BouncyCastle.Asn1.X509
         internal AttCertIssuer			issuer;
         internal AlgorithmIdentifier	signature;
         internal DerInteger				serialNumber;
-//        internal AttCertValidityPeriod	attrCertValidityPeriod;
         internal Asn1EncodableVector	attributes;
         internal DerBitString			issuerUniqueID;
         internal X509Extensions			extensions;
-        internal DerGeneralizedTime		startDate, endDate;
+
+        // Note: validity period start/end dates stored directly
+        //internal AttCertValidityPeriod attrCertValidityPeriod;
+        internal Asn1GeneralizedTime    startDate, endDate;
 
 		public V2AttributeCertificateInfoGenerator()
         {
@@ -78,13 +80,13 @@ namespace Org.BouncyCastle.Asn1.X509
         }
 
 		public void SetStartDate(
-            DerGeneralizedTime startDate)
+            Asn1GeneralizedTime startDate)
         {
             this.startDate = startDate;
         }
 
 		public void SetEndDate(
-            DerGeneralizedTime endDate)
+            Asn1GeneralizedTime endDate)
         {
             this.endDate = endDate;
         }
diff --git a/crypto/src/asn1/x509/V2TBSCertListGenerator.cs b/crypto/src/asn1/x509/V2TBSCertListGenerator.cs
index 1d58751fd..aa1a0b95d 100644
--- a/crypto/src/asn1/x509/V2TBSCertListGenerator.cs
+++ b/crypto/src/asn1/x509/V2TBSCertListGenerator.cs
@@ -55,13 +55,13 @@ namespace Org.BouncyCastle.Asn1.X509
         }
 
 		public void SetThisUpdate(
-            DerUtcTime thisUpdate)
+            Asn1UtcTime thisUpdate)
         {
             this.thisUpdate = new Time(thisUpdate);
         }
 
 		public void SetNextUpdate(
-            DerUtcTime nextUpdate)
+            Asn1UtcTime nextUpdate)
         {
             this.nextUpdate = (nextUpdate != null)
 				?	new Time(nextUpdate)
@@ -90,7 +90,7 @@ namespace Org.BouncyCastle.Asn1.X509
 			crlEntries.Add(crlEntry);
 		}
 
-		public void AddCrlEntry(DerInteger userCertificate, DerUtcTime revocationDate, int reason)
+		public void AddCrlEntry(DerInteger userCertificate, Asn1UtcTime revocationDate, int reason)
 		{
 			AddCrlEntry(userCertificate, new Time(revocationDate), reason);
 		}
@@ -101,7 +101,7 @@ namespace Org.BouncyCastle.Asn1.X509
 		}
 
 		public void AddCrlEntry(DerInteger userCertificate, Time revocationDate, int reason,
-			DerGeneralizedTime invalidityDate)
+            Asn1GeneralizedTime invalidityDate)
 		{
             var extOids = new List<DerObjectIdentifier>();
             var extValues = new List<X509Extension>();
diff --git a/crypto/src/asn1/x509/V3TBSCertificateGenerator.cs b/crypto/src/asn1/x509/V3TBSCertificateGenerator.cs
index beb469a0d..544582ddb 100644
--- a/crypto/src/asn1/x509/V3TBSCertificateGenerator.cs
+++ b/crypto/src/asn1/x509/V3TBSCertificateGenerator.cs
@@ -58,7 +58,7 @@ namespace Org.BouncyCastle.Asn1.X509
         }
 
 		public void SetStartDate(
-            DerUtcTime startDate)
+            Asn1UtcTime startDate)
         {
             this.startDate = new Time(startDate);
         }
@@ -70,7 +70,7 @@ namespace Org.BouncyCastle.Asn1.X509
         }
 
 		public void SetEndDate(
-            DerUtcTime endDate)
+            Asn1UtcTime endDate)
         {
             this.endDate = new Time(endDate);
         }
diff --git a/crypto/src/asn1/x509/X509DefaultEntryConverter.cs b/crypto/src/asn1/x509/X509DefaultEntryConverter.cs
index 7282ead26..d155efc5a 100644
--- a/crypto/src/asn1/x509/X509DefaultEntryConverter.cs
+++ b/crypto/src/asn1/x509/X509DefaultEntryConverter.cs
@@ -46,7 +46,7 @@ namespace Org.BouncyCastle.Asn1.X509
 
 			if (oid.Equals(X509Name.DateOfBirth)) // accept time string as well as # (for compatibility)
 			{
-				return new DerGeneralizedTime(value);
+				return new Asn1GeneralizedTime(value);
 			}
 
 			if (oid.Equals(X509Name.C)
diff --git a/crypto/src/asn1/x509/sigi/PersonalData.cs b/crypto/src/asn1/x509/sigi/PersonalData.cs
index 439039888..e8c75bf93 100644
--- a/crypto/src/asn1/x509/sigi/PersonalData.cs
+++ b/crypto/src/asn1/x509/sigi/PersonalData.cs
@@ -29,7 +29,7 @@ namespace Org.BouncyCastle.Asn1.X509.SigI
 	{
 		private readonly NameOrPseudonym	nameOrPseudonym;
 		private readonly BigInteger			nameDistinguisher;
-		private readonly DerGeneralizedTime	dateOfBirth;
+		private readonly Asn1GeneralizedTime dateOfBirth;
 		private readonly DirectoryString	placeOfBirth;
 		private readonly string				gender;
 		private readonly DirectoryString	postalAddress;
@@ -88,7 +88,7 @@ namespace Org.BouncyCastle.Asn1.X509.SigI
 					nameDistinguisher = DerInteger.GetInstance(o, false).Value;
 					break;
 				case 1:
-					dateOfBirth = DerGeneralizedTime.GetInstance(o, false);
+					dateOfBirth = Asn1GeneralizedTime.GetInstance(o, false);
 					break;
 				case 2:
 					placeOfBirth = DirectoryString.GetInstance(o, true);
@@ -118,7 +118,7 @@ namespace Org.BouncyCastle.Asn1.X509.SigI
 		public PersonalData(
 			NameOrPseudonym		nameOrPseudonym,
 			BigInteger			nameDistinguisher,
-			DerGeneralizedTime	dateOfBirth,
+            Asn1GeneralizedTime dateOfBirth,
 			DirectoryString		placeOfBirth,
 			string				gender,
 			DirectoryString		postalAddress)
@@ -141,7 +141,7 @@ namespace Org.BouncyCastle.Asn1.X509.SigI
 			get { return nameDistinguisher; }
 		}
 
-		public DerGeneralizedTime DateOfBirth
+		public Asn1GeneralizedTime DateOfBirth
 		{
 			get { return dateOfBirth; }
 		}
diff --git a/crypto/src/bcpg/ArmoredInputStream.cs b/crypto/src/bcpg/ArmoredInputStream.cs
index 224ef7d28..4fbb8baae 100644
--- a/crypto/src/bcpg/ArmoredInputStream.cs
+++ b/crypto/src/bcpg/ArmoredInputStream.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
@@ -330,6 +331,26 @@ namespace Org.BouncyCastle.Bcpg
             return pos;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            /*
+             * TODO Currently can't return partial data when exception thrown (breaking test case), so we don't inherit
+             * the base class implementation. Probably the reason is that throws don't mark this instance as 'failed'.
+             */
+            int pos = 0;
+            while (pos < buffer.Length)
+            {
+                int b = ReadByte();
+                if (b < 0)
+                    break;
+
+                buffer[pos++] = (byte)b;
+            }
+            return pos;
+        }
+#endif
+
         public override int ReadByte()
         {
             if (start)
@@ -482,7 +503,6 @@ namespace Org.BouncyCastle.Bcpg
             return c;
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -491,13 +511,6 @@ namespace Org.BouncyCastle.Bcpg
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(input);
-			base.Close();
-		}
-#endif
 
         /**
          * Change how the stream should react if it encounters missing CRC checksum.
diff --git a/crypto/src/bcpg/ArmoredOutputStream.cs b/crypto/src/bcpg/ArmoredOutputStream.cs
index 4a726e2dd..bfed1972a 100644
--- a/crypto/src/bcpg/ArmoredOutputStream.cs
+++ b/crypto/src/bcpg/ArmoredOutputStream.cs
@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
+using System.Reflection;
 
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
@@ -98,7 +99,15 @@ namespace Org.BouncyCastle.Bcpg
         private static readonly string    footerStart = "-----END PGP ";
         private static readonly string    footerTail = "-----";
 
-        private static readonly string Version = "BCPG C# v" + typeof(ArmoredOutputStream).Assembly.GetName().Version;
+        private static string CreateVersion()
+        {
+            var assembly = Assembly.GetExecutingAssembly();
+            var title = assembly.GetCustomAttribute<AssemblyTitleAttribute>().Title;
+            var version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
+            return title + " v" + version;
+        }
+
+        private static readonly string Version = CreateVersion();
 
         private readonly IDictionary<string, IList<string>> m_headers;
 
@@ -331,35 +340,20 @@ namespace Org.BouncyCastle.Bcpg
          * <b>Note</b>: Close() does not close the underlying stream. So it is possible to write
          * multiple objects using armoring to a single stream.
          */
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
             {
-                if (type == null)
-                    return;
-
-                DoClose();
+                if (type != null)
+                {
+                    DoClose();
 
-                type = null;
-                start = true;
+                    type = null;
+                    start = true;
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (type == null)
-                return;
-
-            DoClose();
-
-            type = null;
-            start = true;
-
-            base.Close();
-        }
-#endif
 
         private void DoClose()
         {
diff --git a/crypto/src/bcpg/BcpgInputStream.cs b/crypto/src/bcpg/BcpgInputStream.cs
index 895b03260..3b6f61bbc 100644
--- a/crypto/src/bcpg/BcpgInputStream.cs
+++ b/crypto/src/bcpg/BcpgInputStream.cs
@@ -57,27 +57,46 @@ namespace Org.BouncyCastle.Bcpg
             return 1;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+			if (!next)
+				return m_in.Read(buffer);
+
+			if (nextB < 0)
+				return 0;
+
+            buffer[0] = (byte)nextB;
+            next = false;
+            return 1;
+        }
+#endif
+
         public byte[] ReadAll()
         {
 			return Streams.ReadAll(this);
 		}
 
-		public void ReadFully(
-            byte[]	buffer,
-            int		off,
-            int		len)
+		public void ReadFully(byte[] buffer, int offset, int count)
         {
-			if (Streams.ReadFully(this, buffer, off, len) < len)
+			if (Streams.ReadFully(this, buffer, offset, count) < count)
 				throw new EndOfStreamException();
         }
 
-		public void ReadFully(
-            byte[] buffer)
+		public void ReadFully(byte[] buffer)
         {
             ReadFully(buffer, 0, buffer.Length);
         }
 
-		/// <summary>Returns the next packet tag in the stream.</summary>
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ReadFully(Span<byte> buffer)
+        {
+            if (Streams.ReadFully(this, buffer) < buffer.Length)
+                throw new EndOfStreamException();
+        }
+#endif
+
+        /// <summary>Returns the next packet tag in the stream.</summary>
         public PacketTag NextPacketTag()
         {
             if (!next)
@@ -184,11 +203,7 @@ namespace Org.BouncyCastle.Bcpg
             else
             {
                 PartialInputStream pis = new PartialInputStream(this, partial, bodyLen);
-#if PORTABLE
-                Stream buf = pis;
-#else
 				Stream buf = new BufferedStream(pis);
-#endif
                 objStream = new BcpgInputStream(buf);
             }
 
@@ -251,7 +266,6 @@ namespace Org.BouncyCastle.Bcpg
             return tag;
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -260,13 +274,6 @@ namespace Org.BouncyCastle.Bcpg
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(m_in);
-			base.Close();
-		}
-#endif
 
 		/// <summary>
 		/// A stream that overlays our input stream, allowing the user to only read a segment of it.
@@ -320,9 +327,8 @@ namespace Org.BouncyCastle.Bcpg
 						int readLen = (dataLength > count || dataLength < 0) ? count : dataLength;
 						int len = m_in.Read(buffer, offset, readLen);
 						if (len < 1)
-						{
 							throw new EndOfStreamException("Premature end of stream in PartialInputStream");
-						}
+
 						dataLength -= len;
 						return len;
 					}
@@ -332,6 +338,29 @@ namespace Org.BouncyCastle.Bcpg
 				return 0;
 			}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override int Read(Span<byte> buffer)
+            {
+				do
+				{
+					if (dataLength != 0)
+					{
+                        int count = buffer.Length;
+						int readLen = (dataLength > count || dataLength < 0) ? count : dataLength;
+						int len = m_in.Read(buffer[..readLen]);
+						if (len < 1)
+							throw new EndOfStreamException("Premature end of stream in PartialInputStream");
+
+						dataLength -= len;
+						return len;
+					}
+				}
+				while (partial && ReadPartialDataLength() >= 0);
+
+				return 0;
+            }
+#endif
+
             private int ReadPartialDataLength()
             {
                 int l = m_in.ReadByte();
diff --git a/crypto/src/bcpg/BcpgOutputStream.cs b/crypto/src/bcpg/BcpgOutputStream.cs
index fbce0820e..690686d88 100644
--- a/crypto/src/bcpg/BcpgOutputStream.cs
+++ b/crypto/src/bcpg/BcpgOutputStream.cs
@@ -164,7 +164,7 @@ namespace Org.BouncyCastle.Bcpg
 
             if (partialBuffer != null)
             {
-                PartialFlush(true);
+                PartialFlushLast();
                 partialBuffer = null;
             }
 
@@ -215,19 +215,26 @@ namespace Org.BouncyCastle.Bcpg
             }
         }
 
-        private void PartialFlush(bool isLast)
+        private void PartialFlush()
         {
-            if (isLast)
-            {
-                WriteNewPacketLength(partialOffset);
-                outStr.Write(partialBuffer, 0, partialOffset);
-            }
-            else
-            {
-                outStr.WriteByte((byte)(0xE0 | partialPower));
-                outStr.Write(partialBuffer, 0, partialBufferLength);
-            }
+            outStr.WriteByte((byte)(0xE0 | partialPower));
+            outStr.Write(partialBuffer, 0, partialBufferLength);
+            partialOffset = 0;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void PartialFlush(ref ReadOnlySpan<byte> buffer)
+        {
+            outStr.WriteByte((byte)(0xE0 | partialPower));
+            outStr.Write(buffer[..partialBufferLength]);
+            buffer = buffer[partialBufferLength..];
+        }
+#endif
 
+        private void PartialFlushLast()
+        {
+            WriteNewPacketLength(partialOffset);
+            outStr.Write(partialBuffer, 0, partialOffset);
             partialOffset = 0;
         }
 
@@ -235,40 +242,71 @@ namespace Org.BouncyCastle.Bcpg
         {
             Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            PartialWrite(buffer.AsSpan(offset, count));
+#else
             if (partialOffset == partialBufferLength)
             {
-                PartialFlush(false);
+                PartialFlush();
             }
 
             if (count <= (partialBufferLength - partialOffset))
             {
                 Array.Copy(buffer, offset, partialBuffer, partialOffset, count);
                 partialOffset += count;
+                return;
             }
-            else
+
+            int diff = partialBufferLength - partialOffset;
+            Array.Copy(buffer, offset, partialBuffer, partialOffset, diff);
+            offset += diff;
+            count -= diff;
+            PartialFlush();
+            while (count > partialBufferLength)
             {
-                int diff = partialBufferLength - partialOffset;
-                Array.Copy(buffer, offset, partialBuffer, partialOffset, diff);
-                offset += diff;
-                count -= diff;
-                PartialFlush(false);
-                while (count > partialBufferLength)
-                {
-                    Array.Copy(buffer, offset, partialBuffer, 0, partialBufferLength);
-                    offset += partialBufferLength;
-                    count -= partialBufferLength;
-                    PartialFlush(false);
-                }
-                Array.Copy(buffer, offset, partialBuffer, 0, count);
-                partialOffset += count;
+                Array.Copy(buffer, offset, partialBuffer, 0, partialBufferLength);
+                offset += partialBufferLength;
+                count -= partialBufferLength;
+                PartialFlush();
+            }
+            Array.Copy(buffer, offset, partialBuffer, 0, count);
+            partialOffset = count;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void PartialWrite(ReadOnlySpan<byte> buffer)
+        {
+            if (partialOffset == partialBufferLength)
+            {
+                PartialFlush();
+            }
+
+            if (buffer.Length <= (partialBufferLength - partialOffset))
+            {
+                buffer.CopyTo(partialBuffer.AsSpan(partialOffset));
+                partialOffset += buffer.Length;
+                return;
+            }
+
+            int diff = partialBufferLength - partialOffset;
+            buffer[..diff].CopyTo(partialBuffer.AsSpan(partialOffset));
+            buffer = buffer[diff..];
+            PartialFlush();
+            while (buffer.Length > partialBufferLength)
+            {
+                PartialFlush(ref buffer);
             }
+            buffer.CopyTo(partialBuffer);
+            partialOffset = buffer.Length;
         }
+#endif
 
         private void PartialWriteByte(byte value)
         {
             if (partialOffset == partialBufferLength)
             {
-                PartialFlush(false);
+                PartialFlush();
             }
 
             partialBuffer[partialOffset++] = value;
@@ -286,6 +324,20 @@ namespace Org.BouncyCastle.Bcpg
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (partialBuffer != null)
+            {
+                PartialWrite(buffer);
+            }
+            else
+            {
+                outStr.Write(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             if (partialBuffer != null)
@@ -370,13 +422,12 @@ namespace Org.BouncyCastle.Bcpg
         {
             if (partialBuffer != null)
             {
-                PartialFlush(true);
+                PartialFlushLast();
                 Array.Clear(partialBuffer, 0, partialBuffer.Length);
                 partialBuffer = null;
             }
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -387,14 +438,5 @@ namespace Org.BouncyCastle.Bcpg
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-			this.Finish();
-			outStr.Flush();
-            Platform.Dispose(outStr);
-			base.Close();
-        }
-#endif
     }
 }
diff --git a/crypto/src/bcpg/ECDHPublicBCPGKey.cs b/crypto/src/bcpg/ECDHPublicBCPGKey.cs
index dc225e31e..e43100d3a 100644
--- a/crypto/src/bcpg/ECDHPublicBCPGKey.cs
+++ b/crypto/src/bcpg/ECDHPublicBCPGKey.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
 
 namespace Org.BouncyCastle.Bcpg
@@ -14,15 +15,18 @@ namespace Org.BouncyCastle.Bcpg
         private SymmetricKeyAlgorithmTag symAlgorithmId;
 
         /// <param name="bcpgIn">The stream to read the packet from.</param>
-        public ECDHPublicBcpgKey(
-            BcpgInputStream bcpgIn)
+        public ECDHPublicBcpgKey(BcpgInputStream bcpgIn)
             : base(bcpgIn)
         {
             int length = bcpgIn.ReadByte();
-            byte[] kdfParameters =  new byte[length];
-            if (kdfParameters.Length != 3)
-                throw new InvalidOperationException("kdf parameters size of 3 expected.");
+            if (length != 3)
+                throw new InvalidOperationException("KDF parameters size of 3 expected.");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> kdfParameters = stackalloc byte[3];
+#else
+            byte[] kdfParameters = new byte[3];
+#endif
             bcpgIn.ReadFully(kdfParameters);
 
             reserved = kdfParameters[0];
@@ -48,6 +52,21 @@ namespace Org.BouncyCastle.Bcpg
             VerifySymmetricKeyAlgorithm();
         }
 
+        public ECDHPublicBcpgKey(
+            DerObjectIdentifier oid,
+            BigInteger point,
+            HashAlgorithmTag hashAlgorithm,
+            SymmetricKeyAlgorithmTag symmetricKeyAlgorithm)
+            : base(oid, point)
+        {
+            reserved = 1;
+            hashFunctionId = hashAlgorithm;
+            symAlgorithmId = symmetricKeyAlgorithm;
+
+            VerifyHashAlgorithm();
+            VerifySymmetricKeyAlgorithm();
+        }
+
         public virtual byte Reserved
         {
             get { return reserved; }
@@ -75,7 +94,7 @@ namespace Org.BouncyCastle.Bcpg
 
         private void VerifyHashAlgorithm()
         {
-            switch ((HashAlgorithmTag)hashFunctionId)
+            switch (hashFunctionId)
             {
             case HashAlgorithmTag.Sha256:
             case HashAlgorithmTag.Sha384:
@@ -88,7 +107,7 @@ namespace Org.BouncyCastle.Bcpg
 
         private void VerifySymmetricKeyAlgorithm()
         {
-            switch ((SymmetricKeyAlgorithmTag)symAlgorithmId)
+            switch (symAlgorithmId)
             {
             case SymmetricKeyAlgorithmTag.Aes128:
             case SymmetricKeyAlgorithmTag.Aes192:
diff --git a/crypto/src/bcpg/ECPublicBCPGKey.cs b/crypto/src/bcpg/ECPublicBCPGKey.cs
index 4733ee6c9..560c6ad05 100644
--- a/crypto/src/bcpg/ECPublicBCPGKey.cs
+++ b/crypto/src/bcpg/ECPublicBCPGKey.cs
@@ -26,7 +26,7 @@ namespace Org.BouncyCastle.Bcpg
             DerObjectIdentifier oid,
             ECPoint point)
         {
-            this.point = new BigInteger(1, point.GetEncoded(false));
+            this.point = MPInteger.ToMpiBigInteger(point);
             this.oid = oid;
         }
 
diff --git a/crypto/src/bcpg/ECSecretBCPGKey.cs b/crypto/src/bcpg/ECSecretBCPGKey.cs
index 22e0a3473..eef632263 100644
--- a/crypto/src/bcpg/ECSecretBCPGKey.cs
+++ b/crypto/src/bcpg/ECSecretBCPGKey.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Bcpg
@@ -9,18 +8,18 @@ namespace Org.BouncyCastle.Bcpg
     public class ECSecretBcpgKey
         : BcpgObject, IBcpgKey
     {
-        internal MPInteger x;
+        internal readonly MPInteger m_x;
 
         public ECSecretBcpgKey(
             BcpgInputStream bcpgIn)
         {
-            this.x = new MPInteger(bcpgIn);
+            m_x = new MPInteger(bcpgIn);
         }
 
         public ECSecretBcpgKey(
             BigInteger x)
         {
-            this.x = new MPInteger(x);
+            m_x = new MPInteger(x);
         }
 
 		/// <summary>The format, as a string, always "PGP".</summary>
@@ -45,12 +44,12 @@ namespace Org.BouncyCastle.Bcpg
         public override void Encode(
             BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WriteObject(x);
+            bcpgOut.WriteObject(m_x);
         }
 
         public virtual BigInteger X
         {
-            get { return x.Value; }
+            get { return m_x.Value; }
         }
     }
 }
diff --git a/crypto/src/bcpg/EdDsaPublicBcpgKey.cs b/crypto/src/bcpg/EdDsaPublicBcpgKey.cs
new file mode 100644
index 000000000..f3250b746
--- /dev/null
+++ b/crypto/src/bcpg/EdDsaPublicBcpgKey.cs
@@ -0,0 +1,25 @@
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class EdDsaPublicBcpgKey
+        : ECPublicBcpgKey
+    {
+        internal EdDsaPublicBcpgKey(BcpgInputStream bcpgIn)
+            : base(bcpgIn)
+        {
+        }
+
+        public EdDsaPublicBcpgKey(DerObjectIdentifier oid, ECPoint point)
+            : base(oid, point)
+        {
+        }
+
+        public EdDsaPublicBcpgKey(DerObjectIdentifier oid, BigInteger encodedPoint)
+            : base(oid, encodedPoint)
+        {
+        }
+    }
+}
diff --git a/crypto/src/bcpg/EdSecretBcpgKey.cs b/crypto/src/bcpg/EdSecretBcpgKey.cs
new file mode 100644
index 000000000..5b53f558d
--- /dev/null
+++ b/crypto/src/bcpg/EdSecretBcpgKey.cs
@@ -0,0 +1,43 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    public sealed class EdSecretBcpgKey
+        : BcpgObject, IBcpgKey
+    {
+        internal readonly MPInteger m_x;
+
+        public EdSecretBcpgKey(BcpgInputStream bcpgIn)
+        {
+            m_x = new MPInteger(bcpgIn);
+        }
+
+        public EdSecretBcpgKey(BigInteger x)
+        {
+            m_x = new MPInteger(x);
+        }
+
+        public string Format => "PGP";
+
+        public override byte[] GetEncoded()
+        {
+            try
+            {
+                return base.GetEncoded();
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
+        public override void Encode(BcpgOutputStream bcpgOut)
+        {
+            bcpgOut.WriteObject(m_x);
+        }
+
+        public BigInteger X => m_x.Value;
+    }
+}
diff --git a/crypto/src/bcpg/MPInteger.cs b/crypto/src/bcpg/MPInteger.cs
index 441407244..eb59be6e5 100644
--- a/crypto/src/bcpg/MPInteger.cs
+++ b/crypto/src/bcpg/MPInteger.cs
@@ -1,59 +1,66 @@
 using System;
-using System.IO;
 
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
 
 namespace Org.BouncyCastle.Bcpg
 {
 	/// <remarks>A multiple precision integer</remarks>
-    public class MPInteger
+    public sealed class MPInteger
         : BcpgObject
     {
-        private readonly BigInteger val;
+        private readonly BigInteger m_val;
 
-        public MPInteger(
-            BcpgInputStream bcpgIn)
+        public MPInteger(BcpgInputStream bcpgIn)
         {
 			if (bcpgIn == null)
-				throw new ArgumentNullException("bcpgIn");
+				throw new ArgumentNullException(nameof(bcpgIn));
 
-			int length = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte();
-            byte[] bytes = new byte[(length + 7) / 8];
+			int lengthInBits = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte();
+            int lengthInBytes = (lengthInBits + 7) / 8;
 
-            bcpgIn.ReadFully(bytes);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = lengthInBytes <= 512
+                ? stackalloc byte[lengthInBytes]
+                : new byte[lengthInBytes];
+#else
+            byte[] bytes = new byte[lengthInBytes];
+#endif
 
-            this.val = new BigInteger(1, bytes);
+            bcpgIn.ReadFully(bytes);
+            m_val = new BigInteger(1, bytes);
         }
 
-		public MPInteger(
-            BigInteger val)
+		public MPInteger(BigInteger val)
         {
 			if (val == null)
-				throw new ArgumentNullException("val");
+				throw new ArgumentNullException(nameof(val));
 			if (val.SignValue < 0)
-				throw new ArgumentException("Values must be positive", "val");
+				throw new ArgumentException("Values must be positive", nameof(val));
 
-			this.val = val;
+			m_val = val;
         }
 
-		public BigInteger Value
+        public BigInteger Value => m_val;
+
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
-            get { return val; }
+            bcpgOut.WriteShort((short)m_val.BitLength);
+            bcpgOut.Write(m_val.ToByteArrayUnsigned());
         }
 
-        public override void Encode(
-            BcpgOutputStream bcpgOut)
+        internal static BigInteger ToMpiBigInteger(ECPoint point)
         {
-			bcpgOut.WriteShort((short) val.BitLength);
-			bcpgOut.Write(val.ToByteArrayUnsigned());
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int encodedLength = point.GetEncodedLength(false);
+            Span<byte> encoding = encodedLength <= 512
+                ? stackalloc byte[encodedLength]
+                : new byte[encodedLength];
+            point.EncodeTo(false, encoding);
+#else
+            byte[] encoding = point.GetEncoded(false);
+#endif
+            return new BigInteger(1, encoding);
         }
-
-		internal static void Encode(
-			BcpgOutputStream	bcpgOut,
-			BigInteger			val)
-		{
-			bcpgOut.WriteShort((short) val.BitLength);
-			bcpgOut.Write(val.ToByteArrayUnsigned());
-		}
     }
 }
diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index bbed941dc..40c696a37 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -28,30 +28,33 @@ namespace Org.BouncyCastle.Bcpg
                 validDays = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte();
             }
 
-            algorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte();
+            algorithm = (PublicKeyAlgorithmTag)bcpgIn.ReadByte();
 
-            switch ((PublicKeyAlgorithmTag) algorithm)
+            switch (algorithm)
             {
-                case PublicKeyAlgorithmTag.RsaEncrypt:
-                case PublicKeyAlgorithmTag.RsaGeneral:
-                case PublicKeyAlgorithmTag.RsaSign:
-                    key = new RsaPublicBcpgKey(bcpgIn);
-                    break;
-                case PublicKeyAlgorithmTag.Dsa:
-                    key = new DsaPublicBcpgKey(bcpgIn);
-                    break;
-                case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                case PublicKeyAlgorithmTag.ElGamalGeneral:
-                    key = new ElGamalPublicBcpgKey(bcpgIn);
-                    break;
-                case PublicKeyAlgorithmTag.ECDH:
-                    key = new ECDHPublicBcpgKey(bcpgIn);
-                    break;
-                case PublicKeyAlgorithmTag.ECDsa:
-                    key = new ECDsaPublicBcpgKey(bcpgIn);
-                    break;
-                default:
-                    throw new IOException("unknown PGP public key algorithm encountered");
+            case PublicKeyAlgorithmTag.RsaEncrypt:
+            case PublicKeyAlgorithmTag.RsaGeneral:
+            case PublicKeyAlgorithmTag.RsaSign:
+                key = new RsaPublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.Dsa:
+                key = new DsaPublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.ElGamalEncrypt:
+            case PublicKeyAlgorithmTag.ElGamalGeneral:
+                key = new ElGamalPublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.ECDH:
+                key = new ECDHPublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.ECDsa:
+                key = new ECDsaPublicBcpgKey(bcpgIn);
+                break;
+            case PublicKeyAlgorithmTag.EdDsa:
+                key = new EdDsaPublicBcpgKey(bcpgIn);
+                break;
+            default:
+                throw new IOException("unknown PGP public key algorithm encountered");
             }
         }
 
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 3256ee35e..a0e8588b3 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -10,7 +10,7 @@ namespace Org.BouncyCastle.Bcpg
 {
 	/// <remarks>Generic signature packet.</remarks>
     public class SignaturePacket
-        : ContainedPacket //, PublicKeyAlgorithmTag
+        : ContainedPacket
     {
 		private int						version;
         private int						signatureType;
@@ -128,41 +128,38 @@ namespace Org.BouncyCastle.Bcpg
                 case PublicKeyAlgorithmTag.RsaGeneral:
                 case PublicKeyAlgorithmTag.RsaSign:
                     MPInteger v = new MPInteger(bcpgIn);
-					signature = new MPInteger[]{ v };
+					signature = new MPInteger[1]{ v };
                     break;
 				case PublicKeyAlgorithmTag.Dsa:
                     MPInteger r = new MPInteger(bcpgIn);
                     MPInteger s = new MPInteger(bcpgIn);
-					signature = new MPInteger[]{ r, s };
+					signature = new MPInteger[2]{ r, s };
                     break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt: // yep, this really does happen sometimes.
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     MPInteger p = new MPInteger(bcpgIn);
                     MPInteger g = new MPInteger(bcpgIn);
                     MPInteger y = new MPInteger(bcpgIn);
-					signature = new MPInteger[]{ p, g, y };
+					signature = new MPInteger[3]{ p, g, y };
                     break;
                 case PublicKeyAlgorithmTag.ECDsa:
+                case PublicKeyAlgorithmTag.EdDsa:
                     MPInteger ecR = new MPInteger(bcpgIn);
                     MPInteger ecS = new MPInteger(bcpgIn);
-                    signature = new MPInteger[]{ ecR, ecS };
+                    signature = new MPInteger[2]{ ecR, ecS };
                     break;
                 default:
-					if (keyAlgorithm >= PublicKeyAlgorithmTag.Experimental_1 && keyAlgorithm <= PublicKeyAlgorithmTag.Experimental_11)
-					{
-						signature = null;
-						MemoryStream bOut = new MemoryStream();
-						int ch;
-						while ((ch = bcpgIn.ReadByte()) >= 0)
-						{
-							bOut.WriteByte((byte) ch);
-						}
-						signatureEncoding = bOut.ToArray();
-					}
-					else
+					if (keyAlgorithm < PublicKeyAlgorithmTag.Experimental_1 || keyAlgorithm > PublicKeyAlgorithmTag.Experimental_11)
+                        throw new IOException("unknown signature key algorithm: " + keyAlgorithm);
+
+                    signature = null;
+					MemoryStream bOut = new MemoryStream();
+					int ch;
+					while ((ch = bcpgIn.ReadByte()) >= 0)
 					{
-						throw new IOException("unknown signature key algorithm: " + keyAlgorithm);
+						bOut.WriteByte((byte) ch);
 					}
+					signatureEncoding = bOut.ToArray();
 					break;
             }
         }
@@ -237,7 +234,7 @@ namespace Org.BouncyCastle.Bcpg
 
 			if (hashedData != null)
 			{
-				setCreationTime();
+				SetCreationTime();
 			}
 		}
 
@@ -268,56 +265,53 @@ namespace Org.BouncyCastle.Bcpg
         */
         public byte[] GetSignatureTrailer()
         {
-            byte[] trailer = null;
-
 			if (version == 3)
             {
-                trailer = new byte[5];
-
-				long time = creationTime / 1000L;
+                long time = creationTime / 1000L;
 
+                byte[] trailer = new byte[5];
 				trailer[0] = (byte)signatureType;
                 trailer[1] = (byte)(time >> 24);
                 trailer[2] = (byte)(time >> 16);
-                trailer[3] = (byte)(time >> 8);
-                trailer[4] = (byte)(time);
+                trailer[3] = (byte)(time >>  8);
+                trailer[4] = (byte)(time      );
+                return trailer;
             }
-            else
-            {
-                MemoryStream sOut = new MemoryStream();
 
-				sOut.WriteByte((byte)this.Version);
-                sOut.WriteByte((byte)this.SignatureType);
-                sOut.WriteByte((byte)this.KeyAlgorithm);
-                sOut.WriteByte((byte)this.HashAlgorithm);
+            MemoryStream sOut = new MemoryStream();
 
-				MemoryStream hOut = new MemoryStream();
-                SignatureSubpacket[] hashed = this.GetHashedSubPackets();
+			sOut.WriteByte((byte)Version);
+            sOut.WriteByte((byte)SignatureType);
+            sOut.WriteByte((byte)KeyAlgorithm);
+            sOut.WriteByte((byte)HashAlgorithm);
 
-				for (int i = 0; i != hashed.Length; i++)
-                {
-                    hashed[i].Encode(hOut);
-                }
+            // Mark position an reserve two bytes for length
+            long lengthPosition = sOut.Position;
+            sOut.WriteByte(0x00);
+            sOut.WriteByte(0x00);
 
-				byte[] data = hOut.ToArray();
-
-				sOut.WriteByte((byte)(data.Length >> 8));
-                sOut.WriteByte((byte)data.Length);
-                sOut.Write(data, 0, data.Length);
+            SignatureSubpacket[] hashed = GetHashedSubPackets();
+			for (int i = 0; i != hashed.Length; i++)
+            {
+                hashed[i].Encode(sOut);
+            }
 
-				byte[] hData = sOut.ToArray();
+            ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2);
+            uint hDataLength = Convert.ToUInt32(sOut.Position);
 
-				sOut.WriteByte((byte)this.Version);
-                sOut.WriteByte((byte)0xff);
-                sOut.WriteByte((byte)(hData.Length>> 24));
-                sOut.WriteByte((byte)(hData.Length >> 16));
-                sOut.WriteByte((byte)(hData.Length >> 8));
-                sOut.WriteByte((byte)(hData.Length));
+			sOut.WriteByte((byte)Version);
+            sOut.WriteByte(0xff);
+            sOut.WriteByte((byte)(hDataLength >> 24));
+            sOut.WriteByte((byte)(hDataLength >> 16));
+            sOut.WriteByte((byte)(hDataLength >>  8));
+            sOut.WriteByte((byte)(hDataLength      ));
 
-				trailer = sOut.ToArray();
-            }
+            // Reset position and fill in length
+            sOut.Position = lengthPosition;
+            sOut.WriteByte((byte)(dataLength >> 8));
+            sOut.WriteByte((byte)(dataLength     ));
 
-			return trailer;
+            return sOut.ToArray();
         }
 
 		public PublicKeyAlgorithmTag KeyAlgorithm
@@ -446,18 +440,18 @@ namespace Org.BouncyCastle.Bcpg
 			return sOut.ToArray();
 		}
 
-		private void setCreationTime()
+		private void SetCreationTime()
 		{
 			foreach (SignatureSubpacket p in hashedData)
 			{
-				if (p is SignatureCreationTime)
+				if (p is SignatureCreationTime signatureCreationTime)
 				{
-                    creationTime = DateTimeUtilities.DateTimeToUnixMs(
-						((SignatureCreationTime)p).GetTime());
+                    creationTime = DateTimeUtilities.DateTimeToUnixMs(signatureCreationTime.GetTime());
 					break;
 				}
 			}
 		}
+
         public static SignaturePacket FromByteArray(byte[] data)
         {
             BcpgInputStream input = BcpgInputStream.Wrap(new MemoryStream(data));
diff --git a/crypto/src/bcpg/sig/Exportable.cs b/crypto/src/bcpg/sig/Exportable.cs
index 4d030346f..8a9eafe09 100644
--- a/crypto/src/bcpg/sig/Exportable.cs
+++ b/crypto/src/bcpg/sig/Exportable.cs
@@ -10,17 +10,7 @@ namespace Org.BouncyCastle.Bcpg.Sig
     {
         private static byte[] BooleanToByteArray(bool val)
         {
-            byte[]    data = new byte[1];
-
-            if (val)
-            {
-                data[0] = 1;
-                return data;
-            }
-            else
-            {
-                return data;
-            }
+            return new byte[1]{ Convert.ToByte(val) };
         }
 
         public Exportable(
diff --git a/crypto/src/bcpg/sig/Features.cs b/crypto/src/bcpg/sig/Features.cs
index 135d7f54e..f6123d612 100644
--- a/crypto/src/bcpg/sig/Features.cs
+++ b/crypto/src/bcpg/sig/Features.cs
@@ -19,31 +19,25 @@ namespace Org.BouncyCastle.Bcpg.Sig
            fingerprint format */
         public static readonly byte FEATURE_VERSION_5_PUBLIC_KEY = 0x04;
 
-        private static byte[] featureToByteArray(byte feature)
+        private static byte[] FeatureToByteArray(byte feature)
         {
-            byte[] data = new byte[1];
-            data[0] = feature;
-            return data;
+            return new byte[1]{ feature };
         }
 
         public Features(
             bool critical,
             bool isLongLength,
-            byte[] data): base(SignatureSubpacketTag.Features, critical, isLongLength, data)
+            byte[] data)
+            : base(SignatureSubpacketTag.Features, critical, isLongLength, data)
         {
-
         }
-      
 
-        public Features(bool critical, byte features): this(critical, false, featureToByteArray(features))
+        public Features(bool critical, byte features): this(critical, false, FeatureToByteArray(features))
         {
-
         }
-   
 
-        public Features(bool critical, int features):  this(critical, false, featureToByteArray((byte)features))
+        public Features(bool critical, int features):  this(critical, false, FeatureToByteArray((byte)features))
         {
-           
         }
 
         /**
diff --git a/crypto/src/bcpg/sig/IssuerKeyId.cs b/crypto/src/bcpg/sig/IssuerKeyId.cs
index 627ea3ecf..1281a110e 100644
--- a/crypto/src/bcpg/sig/IssuerKeyId.cs
+++ b/crypto/src/bcpg/sig/IssuerKeyId.cs
@@ -1,6 +1,4 @@
-using System;
-
-
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.Sig
 {
@@ -10,21 +8,9 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class IssuerKeyId
         : SignatureSubpacket
     {
-        protected static byte[] KeyIdToBytes(
-            long    keyId)
+        protected static byte[] KeyIdToBytes(long keyId)
         {
-            byte[]    data = new byte[8];
-
-            data[0] = (byte)(keyId >> 56);
-            data[1] = (byte)(keyId >> 48);
-            data[2] = (byte)(keyId >> 40);
-            data[3] = (byte)(keyId >> 32);
-            data[4] = (byte)(keyId >> 24);
-            data[5] = (byte)(keyId >> 16);
-            data[6] = (byte)(keyId >> 8);
-            data[7] = (byte)keyId;
-
-            return data;
+            return Pack.UInt64_To_BE((ulong)keyId);
         }
 
         public IssuerKeyId(
@@ -42,21 +28,6 @@ namespace Org.BouncyCastle.Bcpg.Sig
         {
         }
 
-        public long KeyId
-        {
-			get
-			{
-				long keyId = ((long)(data[0] & 0xff) << 56)
-					| ((long)(data[1] & 0xff) << 48)
-					| ((long)(data[2] & 0xff) << 40)
-					| ((long)(data[3] & 0xff) << 32)
-					| ((long)(data[4] & 0xff) << 24)
-					| ((long)(data[5] & 0xff) << 16)
-					| ((long)(data[6] & 0xff) << 8)
-					| ((long)data[7] & 0xff);
-
-				return keyId;
-			}
-        }
+        public long KeyId => (long)Pack.BE_To_UInt64(data);
     }
 }
diff --git a/crypto/src/bcpg/sig/KeyExpirationTime.cs b/crypto/src/bcpg/sig/KeyExpirationTime.cs
index dfd3e76fd..62184b2a1 100644
--- a/crypto/src/bcpg/sig/KeyExpirationTime.cs
+++ b/crypto/src/bcpg/sig/KeyExpirationTime.cs
@@ -1,4 +1,4 @@
-using System;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.Sig
 {
@@ -8,17 +8,9 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class KeyExpirationTime
         : SignatureSubpacket
     {
-        protected static byte[] TimeToBytes(
-            long    t)
+        protected static byte[] TimeToBytes(long t)
         {
-            byte[]    data = new byte[4];
-
-            data[0] = (byte)(t >> 24);
-            data[1] = (byte)(t >> 16);
-            data[2] = (byte)(t >> 8);
-            data[3] = (byte)t;
-
-            return data;
+            return Pack.UInt32_To_BE((uint)t);
         }
 
         public KeyExpirationTime(
@@ -41,15 +33,6 @@ namespace Org.BouncyCastle.Bcpg.Sig
         *
         * @return second count for key validity.
         */
-        public long Time
-        {
-			get
-			{
-				long time = ((long)(data[0] & 0xff) << 24) | ((long)(data[1] & 0xff) << 16)
-					| ((long)(data[2] & 0xff) << 8) | ((long)data[3] & 0xff);
-
-				return time;
-			}
-        }
+        public long Time => (long)Pack.BE_To_UInt32(data);
     }
 }
diff --git a/crypto/src/bcpg/sig/NotationData.cs b/crypto/src/bcpg/sig/NotationData.cs
index 9ac6f89cf..71c34d3de 100644
--- a/crypto/src/bcpg/sig/NotationData.cs
+++ b/crypto/src/bcpg/sig/NotationData.cs
@@ -79,7 +79,7 @@ namespace Org.BouncyCastle.Bcpg.Sig
 
 		public bool IsHumanReadable
 		{
-			get { return data[0] == (byte)0x80; }
+			get { return data[0] == 0x80; }
 		}
 
 		public string GetNotationName()
diff --git a/crypto/src/bcpg/sig/PrimaryUserId.cs b/crypto/src/bcpg/sig/PrimaryUserId.cs
index 1f16f40eb..184dfe8f7 100644
--- a/crypto/src/bcpg/sig/PrimaryUserId.cs
+++ b/crypto/src/bcpg/sig/PrimaryUserId.cs
@@ -8,20 +8,9 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class PrimaryUserId
         : SignatureSubpacket
     {
-        private static byte[] BooleanToByteArray(
-            bool    val)
+        private static byte[] BooleanToByteArray(bool val)
         {
-            byte[]    data = new byte[1];
-
-            if (val)
-            {
-                data[0] = 1;
-                return data;
-            }
-            else
-            {
-                return data;
-            }
+            return new byte[1]{ Convert.ToByte(val) };
         }
 
         public PrimaryUserId(
diff --git a/crypto/src/bcpg/sig/Revocable.cs b/crypto/src/bcpg/sig/Revocable.cs
index 7aa91391f..6ded4d865 100644
--- a/crypto/src/bcpg/sig/Revocable.cs
+++ b/crypto/src/bcpg/sig/Revocable.cs
@@ -8,20 +8,9 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class Revocable
         : SignatureSubpacket
     {
-        private static byte[] BooleanToByteArray(
-            bool    value)
+        private static byte[] BooleanToByteArray(bool value)
         {
-            byte[]    data = new byte[1];
-
-            if (value)
-            {
-                data[0] = 1;
-                return data;
-            }
-            else
-            {
-                return data;
-            }
+            return new byte[1]{ Convert.ToByte(value) };
         }
 
         public Revocable(
diff --git a/crypto/src/bcpg/sig/RevocationReason.cs b/crypto/src/bcpg/sig/RevocationReason.cs
index 42afd5f5b..bd0c91428 100644
--- a/crypto/src/bcpg/sig/RevocationReason.cs
+++ b/crypto/src/bcpg/sig/RevocationReason.cs
@@ -46,9 +46,7 @@ namespace Org.BouncyCastle.Bcpg
         {
             byte[] data = GetData();
             if (data.Length == 1)
-            {
                 return string.Empty;
-            }
 
             byte[] description = new byte[data.Length - 1];
             Array.Copy(data, 1, description, 0, description.Length);
diff --git a/crypto/src/bcpg/sig/SignatureCreationTime.cs b/crypto/src/bcpg/sig/SignatureCreationTime.cs
index d172e5d52..233dd18e6 100644
--- a/crypto/src/bcpg/sig/SignatureCreationTime.cs
+++ b/crypto/src/bcpg/sig/SignatureCreationTime.cs
@@ -1,5 +1,6 @@
 using System;
 
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities.Date;
 
 namespace Org.BouncyCastle.Bcpg.Sig
@@ -10,41 +11,25 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class SignatureCreationTime
         : SignatureSubpacket
     {
-		protected static byte[] TimeToBytes(
-            DateTime time)
+		protected static byte[] TimeToBytes(DateTime time)
         {
 			long t = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
-			byte[] data = new byte[4];
-			data[0] = (byte)(t >> 24);
-            data[1] = (byte)(t >> 16);
-            data[2] = (byte)(t >> 8);
-            data[3] = (byte)t;
-            return data;
+            return Pack.UInt32_To_BE((uint)t);
         }
 
-        public SignatureCreationTime(
-            bool    critical,
-            bool    isLongLength,
-            byte[]  data)
+        public SignatureCreationTime(bool critical, bool isLongLength, byte[] data)
             : base(SignatureSubpacketTag.CreationTime, critical, isLongLength, data)
         {
         }
 
-        public SignatureCreationTime(
-            bool        critical,
-            DateTime    date)
+        public SignatureCreationTime(bool critical, DateTime date)
             : base(SignatureSubpacketTag.CreationTime, critical, false, TimeToBytes(date))
         {
         }
 
         public DateTime GetTime()
         {
-			long time = (long)(
-					((uint)data[0] << 24)
-				|	((uint)data[1] << 16)
-				|	((uint)data[2] << 8)
-				|	((uint)data[3])
-				);
+            uint time = Pack.BE_To_UInt32(data, 0);
 			return DateTimeUtilities.UnixMsToDateTime(time * 1000L);
         }
     }
diff --git a/crypto/src/bcpg/sig/SignatureExpirationTime.cs b/crypto/src/bcpg/sig/SignatureExpirationTime.cs
index 24f0a9f8a..44c714f33 100644
--- a/crypto/src/bcpg/sig/SignatureExpirationTime.cs
+++ b/crypto/src/bcpg/sig/SignatureExpirationTime.cs
@@ -1,4 +1,4 @@
-using System;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.Sig
 {
@@ -8,28 +8,17 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class SignatureExpirationTime
         : SignatureSubpacket
     {
-        protected static byte[] TimeToBytes(
-            long    t)
+        protected static byte[] TimeToBytes(long t)
         {
-            byte[] data = new byte[4];
-            data[0] = (byte)(t >> 24);
-            data[1] = (byte)(t >> 16);
-            data[2] = (byte)(t >> 8);
-            data[3] = (byte)t;
-            return data;
+            return Pack.UInt32_To_BE((uint)t);
         }
 
-        public SignatureExpirationTime(
-            bool    critical,
-            bool    isLongLength,
-            byte[]  data)
+        public SignatureExpirationTime(bool critical, bool isLongLength, byte[] data)
             : base(SignatureSubpacketTag.ExpireTime, critical, isLongLength, data)
         {
         }
 
-        public SignatureExpirationTime(
-            bool    critical,
-            long    seconds)
+        public SignatureExpirationTime(bool critical, long seconds)
             : base(SignatureSubpacketTag.ExpireTime, critical, false, TimeToBytes(seconds))
         {
         }
@@ -37,15 +26,6 @@ namespace Org.BouncyCastle.Bcpg.Sig
         /**
         * return time in seconds before signature expires after creation time.
         */
-        public long Time
-        {
-            get
-            {
-                long time = ((long)(data[0] & 0xff) << 24) | ((long)(data[1] & 0xff) << 16)
-                    | ((long)(data[2] & 0xff) << 8) | ((long)data[3] & 0xff);
-
-                return time;
-            }
-        }
+        public long Time => Pack.BE_To_UInt32(data, 0);
     }
 }
diff --git a/crypto/src/bcpg/sig/SignerUserId.cs b/crypto/src/bcpg/sig/SignerUserId.cs
index 8ab62ed2e..6f812e210 100644
--- a/crypto/src/bcpg/sig/SignerUserId.cs
+++ b/crypto/src/bcpg/sig/SignerUserId.cs
@@ -1,7 +1,3 @@
-using System;
-
-
-
 namespace Org.BouncyCastle.Bcpg.Sig
 {
     /**
diff --git a/crypto/src/cmp/CertificateConfirmationContent.cs b/crypto/src/cmp/CertificateConfirmationContent.cs
index ad46ca039..ceb34e2c8 100644
--- a/crypto/src/cmp/CertificateConfirmationContent.cs
+++ b/crypto/src/cmp/CertificateConfirmationContent.cs
@@ -7,33 +7,33 @@ namespace Org.BouncyCastle.Cmp
 {
     public class CertificateConfirmationContent
     {
-        private readonly DefaultDigestAlgorithmIdentifierFinder digestAlgFinder;
-        private readonly CertConfirmContent content;
+        private readonly DefaultDigestAlgorithmIdentifierFinder m_digestAlgFinder;
+        private readonly CertConfirmContent m_content;
 
         public CertificateConfirmationContent(CertConfirmContent content)
         {
-            this.content = content;
+            this.m_content = content;
         }
 
         public CertificateConfirmationContent(CertConfirmContent content,
             DefaultDigestAlgorithmIdentifierFinder digestAlgFinder)
         {
-            this.content = content;
-            this.digestAlgFinder = digestAlgFinder;
+            this.m_content = content;
+            this.m_digestAlgFinder = digestAlgFinder;
         }
 
         public CertConfirmContent ToAsn1Structure()
         {
-            return content;
+            return m_content;
         }
 
         public CertificateStatus[] GetStatusMessages()
         {
-            CertStatus[] statusArray = content.ToCertStatusArray();
+            CertStatus[] statusArray = m_content.ToCertStatusArray();
             CertificateStatus[] ret = new CertificateStatus[statusArray.Length];
             for (int i = 0; i != ret.Length; i++)
             {
-                ret[i] = new CertificateStatus(digestAlgFinder, statusArray[i]);
+                ret[i] = new CertificateStatus(m_digestAlgFinder, statusArray[i]);
             }
 
             return ret;
diff --git a/crypto/src/cmp/CertificateConfirmationContentBuilder.cs b/crypto/src/cmp/CertificateConfirmationContentBuilder.cs
index b0647f9a5..fa7e5a897 100644
--- a/crypto/src/cmp/CertificateConfirmationContentBuilder.cs
+++ b/crypto/src/cmp/CertificateConfirmationContentBuilder.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cmp;
@@ -11,13 +10,14 @@ using Org.BouncyCastle.X509;
 
 namespace Org.BouncyCastle.Cmp
 {
-    public class CertificateConfirmationContentBuilder
+    public sealed class CertificateConfirmationContentBuilder
     {
-        private static readonly DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+        private static readonly DefaultSignatureAlgorithmIdentifierFinder SigAlgFinder =
+            new DefaultSignatureAlgorithmIdentifierFinder();
 
-        private readonly DefaultDigestAlgorithmIdentifierFinder digestAlgFinder;
-        private readonly IList<X509Certificate> acceptedCerts = new List<X509Certificate>();
-        private readonly IList<BigInteger> acceptedReqIds = new List<BigInteger>();
+        private readonly DefaultDigestAlgorithmIdentifierFinder m_digestAlgFinder;
+        private readonly IList<X509Certificate> m_acceptedCerts = new List<X509Certificate>();
+        private readonly IList<BigInteger> m_acceptedReqIDs = new List<BigInteger>();
 
         public CertificateConfirmationContentBuilder()
             : this(new DefaultDigestAlgorithmIdentifierFinder())
@@ -26,39 +26,40 @@ namespace Org.BouncyCastle.Cmp
 
         public CertificateConfirmationContentBuilder(DefaultDigestAlgorithmIdentifierFinder digestAlgFinder)
         {
-            this.digestAlgFinder = digestAlgFinder;
+            this.m_digestAlgFinder = digestAlgFinder;
         }
 
         public CertificateConfirmationContentBuilder AddAcceptedCertificate(X509Certificate certHolder,
             BigInteger certReqId)
         {
-            acceptedCerts.Add(certHolder);
-            acceptedReqIds.Add(certReqId);
+            m_acceptedCerts.Add(certHolder);
+            m_acceptedReqIDs.Add(certReqId);
             return this;
         }
 
         public CertificateConfirmationContent Build()
         {
             Asn1EncodableVector v = new Asn1EncodableVector();
-            for (int i = 0; i != acceptedCerts.Count; i++)
+            for (int i = 0; i != m_acceptedCerts.Count; i++)
             {
-                X509Certificate cert = acceptedCerts[i];
-                BigInteger reqId = acceptedReqIds[i];
+                X509Certificate cert = m_acceptedCerts[i];
+                BigInteger reqID = m_acceptedReqIDs[i];
 
+                AlgorithmIdentifier algorithmIdentifier = SigAlgFinder.Find(cert.SigAlgName);
+                if (null == algorithmIdentifier)
+                    throw new CmpException("cannot find algorithm identifier for signature name");
 
-                AlgorithmIdentifier algorithmIdentifier = sigAlgFinder.Find(cert.SigAlgName);
-
-                AlgorithmIdentifier digAlg = digestAlgFinder.Find(algorithmIdentifier);
+                AlgorithmIdentifier digAlg = m_digestAlgFinder.Find(algorithmIdentifier);
                 if (null == digAlg)
                     throw new CmpException("cannot find algorithm for digest from signature");
 
                 byte[] digest = DigestUtilities.CalculateDigest(digAlg.Algorithm, cert.GetEncoded());
 
-                v.Add(new CertStatus(digest, reqId));
+                v.Add(new CertStatus(digest, reqID));
             }
 
             return new CertificateConfirmationContent(CertConfirmContent.GetInstance(new DerSequence(v)),
-                digestAlgFinder);
+                m_digestAlgFinder);
         }
     }
 }
diff --git a/crypto/src/cmp/CertificateStatus.cs b/crypto/src/cmp/CertificateStatus.cs
index 2e3afe3b6..e697f73bc 100644
--- a/crypto/src/cmp/CertificateStatus.cs
+++ b/crypto/src/cmp/CertificateStatus.cs
@@ -24,17 +24,11 @@ namespace Org.BouncyCastle.Cmp
             this.certStatus = certStatus;
         }
 
-        public PkiStatusInfo PkiStatusInfo
-        {
-            get { return certStatus.StatusInfo; }
-        }
+        public virtual PkiStatusInfo StatusInfo => certStatus.StatusInfo;
 
-        public BigInteger CertRequestId
-        {
-            get { return certStatus.CertReqID.Value; }
-        }
+        public virtual BigInteger CertRequestID => certStatus.CertReqID.Value;
 
-        public bool IsVerified(X509Certificate cert)
+        public virtual bool IsVerified(X509Certificate cert)
         {
             AlgorithmIdentifier digAlg = digestAlgFinder.Find(sigAlgFinder.Find(cert.SigAlgName));
             if (null == digAlg)
diff --git a/crypto/src/cmp/GeneralPkiMessage.cs b/crypto/src/cmp/GeneralPkiMessage.cs
index 9b12ee77b..d52161f6c 100644
--- a/crypto/src/cmp/GeneralPkiMessage.cs
+++ b/crypto/src/cmp/GeneralPkiMessage.cs
@@ -1,13 +1,11 @@
-using System;
-
-using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cmp;
 
 namespace Org.BouncyCastle.Cmp
 {
     public class GeneralPkiMessage
     {
-        private readonly PkiMessage pkiMessage;
+        private readonly PkiMessage m_pkiMessage;
 
         private static PkiMessage ParseBytes(byte[] encoding)
         {
@@ -20,7 +18,7 @@ namespace Org.BouncyCastle.Cmp
         /// <param name="pkiMessage">PKI message.</param>
         public GeneralPkiMessage(PkiMessage pkiMessage)
         {
-            this.pkiMessage = pkiMessage;
+            this.m_pkiMessage = pkiMessage;
         }
 
         /// <summary>
@@ -32,28 +30,16 @@ namespace Org.BouncyCastle.Cmp
         {
         }
 
-        public PkiHeader Header
-        {
-            get { return pkiMessage.Header; }
-        }
+        public virtual PkiHeader Header => m_pkiMessage.Header;
 
-        public PkiBody Body
-        {
-            get { return pkiMessage.Body; }
-        }
+        public virtual PkiBody Body => m_pkiMessage.Body;
 
         /// <summary>
         /// Return true if this message has protection bits on it. A return value of true
         /// indicates the message can be used to construct a ProtectedPKIMessage.
         /// </summary>
-        public bool HasProtection
-        {
-            get { return pkiMessage.Protection != null; }
-        }
+        public virtual bool HasProtection => m_pkiMessage.Protection != null;
 
-        public PkiMessage ToAsn1Structure()
-        {
-            return pkiMessage;
-        }
+        public virtual PkiMessage ToAsn1Structure() => m_pkiMessage;
     }
 }
diff --git a/crypto/src/cmp/ProtectedPkiMessage.cs b/crypto/src/cmp/ProtectedPkiMessage.cs
index 770fe5443..f99672cc7 100644
--- a/crypto/src/cmp/ProtectedPkiMessage.cs
+++ b/crypto/src/cmp/ProtectedPkiMessage.cs
@@ -15,7 +15,7 @@ namespace Org.BouncyCastle.Cmp
     /// </summary>
     public class ProtectedPkiMessage
     {
-        private readonly PkiMessage pkiMessage;
+        private readonly PkiMessage m_pkiMessage;
 
         /// <summary>
         /// Wrap a general message.
@@ -25,11 +25,12 @@ namespace Org.BouncyCastle.Cmp
         public ProtectedPkiMessage(GeneralPkiMessage pkiMessage)
         {
             if (!pkiMessage.HasProtection)
-                throw new ArgumentException("pki message not protected");
+                throw new ArgumentException("GeneralPkiMessage not protected");
 
-            this.pkiMessage = pkiMessage.ToAsn1Structure();
+            this.m_pkiMessage = pkiMessage.ToAsn1Structure();
         }
 
+        // TODO[cmp] Make internal? (Has test that uses it)
         /// <summary>
         /// Wrap a PKI message.
         /// </summary>
@@ -38,63 +39,49 @@ namespace Org.BouncyCastle.Cmp
         public ProtectedPkiMessage(PkiMessage pkiMessage)
         {
             if (null == pkiMessage.Header.ProtectionAlg)
-                throw new ArgumentException("pki message not protected");
+                throw new ArgumentException("PkiMessage not protected");
 
-            this.pkiMessage = pkiMessage;
+            this.m_pkiMessage = pkiMessage;
         }
 
-        /// <summary>
-        /// Message header
-        /// </summary>
-        public PkiHeader Header
-        {
-            get { return pkiMessage.Header; }
-        }
+        /// <summary>Message header</summary>
+        public virtual PkiHeader Header => m_pkiMessage.Header;
 
-        /// <summary>
-        /// Message Body
-        /// </summary>
-        public PkiBody Body
-        {
-            get { return pkiMessage.Body; }
-        }
+        /// <summary>Message body</summary>
+        public virtual PkiBody Body => m_pkiMessage.Body;
 
         /// <summary>
         /// Return the underlying ASN.1 structure contained in this object.
         /// </summary>
-        /// <returns>PKI Message structure</returns>
-        public PkiMessage ToAsn1Message()
-        {
-            return pkiMessage;
-        }
+        /// <returns>PkiMessage structure</returns>
+        public virtual PkiMessage ToAsn1Message() => m_pkiMessage;
 
         /// <summary>
         /// Determine whether the message is protected by a password based MAC. Use verify(PKMACBuilder, char[])
         /// to verify the message if this method returns true.
         /// </summary>
         /// <returns>true if protection MAC PBE based, false otherwise.</returns>
-        public bool HasPasswordBasedMacProtected
+        public virtual bool HasPasswordBasedMacProtected
         {
-            get { return Header.ProtectionAlg.Algorithm.Equals(CmpObjectIdentifiers.passwordBasedMac); }
+            get { return CmpObjectIdentifiers.passwordBasedMac.Equals(Header.ProtectionAlg.Algorithm); }
         }
 
         /// <summary>
         /// Return the extra certificates associated with this message.
         /// </summary>
         /// <returns>an array of extra certificates, zero length if none present.</returns>
-        public X509Certificate[] GetCertificates()
+        public virtual X509Certificate[] GetCertificates()
         {
-            CmpCertificate[] certs = pkiMessage.GetExtraCerts();
+            CmpCertificate[] certs = m_pkiMessage.GetExtraCerts();
             if (null == certs)
                 return new X509Certificate[0];
 
-            X509Certificate[] res = new X509Certificate[certs.Length];
+            X509Certificate[] result = new X509Certificate[certs.Length];
             for (int t = 0; t < certs.Length; t++)
             {
-                res[t] = new X509Certificate(X509CertificateStructure.GetInstance(certs[t].GetEncoded()));
+                result[t] = new X509Certificate(certs[t].X509v3PKCert);
             }
-
-            return res;
+            return result;
         }
 
         /// <summary>
@@ -102,27 +89,13 @@ namespace Org.BouncyCastle.Cmp
         /// </summary>
         /// <param name="verifierFactory">a factory of signature verifiers.</param>
         /// <returns>true if the provider is able to create a verifier that validates the signature, false otherwise.</returns>      
-        public bool Verify(IVerifierFactory verifierFactory)
+        public virtual bool Verify(IVerifierFactory verifierFactory)
         {
-            IStreamCalculator streamCalculator = verifierFactory.CreateCalculator();
+            IStreamCalculator<IVerifier> streamCalculator = verifierFactory.CreateCalculator();
 
-            IVerifier result = (IVerifier)Process(streamCalculator);
+            IVerifier result = Process(streamCalculator);
 
-            return result.IsVerified(pkiMessage.Protection.GetBytes());
-        }
-
-        private object Process(IStreamCalculator streamCalculator)
-        {
-            Asn1EncodableVector avec = new Asn1EncodableVector();
-            avec.Add(pkiMessage.Header);
-            avec.Add(pkiMessage.Body);
-            byte[] enc = new DerSequence(avec).GetDerEncoded();
-
-            streamCalculator.Stream.Write(enc, 0, enc.Length);
-            streamCalculator.Stream.Flush();
-            Platform.Dispose(streamCalculator.Stream);
-
-            return streamCalculator.GetResult();
+            return result.IsVerified(m_pkiMessage.Protection.GetBytes());
         }
 
         /// <summary>
@@ -132,18 +105,32 @@ namespace Org.BouncyCastle.Cmp
         /// <param name="password">the MAC password</param>
         /// <returns>true if the passed in password and MAC builder verify the message, false otherwise.</returns>
         /// <exception cref="InvalidOperationException">if algorithm not MAC based, or an exception is thrown verifying the MAC.</exception>
-        public bool Verify(PKMacBuilder pkMacBuilder, char[] password)
+        public virtual bool Verify(PKMacBuilder pkMacBuilder, char[] password)
         {
-            if (!CmpObjectIdentifiers.passwordBasedMac.Equals(pkiMessage.Header.ProtectionAlg.Algorithm))
+            if (!CmpObjectIdentifiers.passwordBasedMac.Equals(m_pkiMessage.Header.ProtectionAlg.Algorithm))
                 throw new InvalidOperationException("protection algorithm is not mac based");
 
-            PbmParameter parameter = PbmParameter.GetInstance(pkiMessage.Header.ProtectionAlg.Parameters);
+            PbmParameter parameter = PbmParameter.GetInstance(m_pkiMessage.Header.ProtectionAlg.Parameters);
 
             pkMacBuilder.SetParameters(parameter);
 
-            IBlockResult result = (IBlockResult)Process(pkMacBuilder.Build(password).CreateCalculator());
+            IBlockResult result = Process(pkMacBuilder.Build(password).CreateCalculator());
+
+            return Arrays.ConstantTimeAreEqual(result.Collect(), m_pkiMessage.Protection.GetBytes());
+        }
+
+        private TResult Process<TResult>(IStreamCalculator<TResult> streamCalculator)
+        {
+            Asn1EncodableVector avec = new Asn1EncodableVector();
+            avec.Add(m_pkiMessage.Header);
+            avec.Add(m_pkiMessage.Body);
+            byte[] enc = new DerSequence(avec).GetDerEncoded();
+
+            streamCalculator.Stream.Write(enc, 0, enc.Length);
+            streamCalculator.Stream.Flush();
+            Platform.Dispose(streamCalculator.Stream);
 
-            return Arrays.ConstantTimeAreEqual(result.Collect(), this.pkiMessage.Protection.GetBytes());
+            return streamCalculator.GetResult();
         }
     }
 }
diff --git a/crypto/src/cmp/ProtectedPkiMessageBuilder.cs b/crypto/src/cmp/ProtectedPkiMessageBuilder.cs
index 6440c3f4e..505747960 100644
--- a/crypto/src/cmp/ProtectedPkiMessageBuilder.cs
+++ b/crypto/src/cmp/ProtectedPkiMessageBuilder.cs
@@ -3,19 +3,19 @@ using System.Collections.Generic;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cmp;
+using Org.BouncyCastle.Asn1.Cms;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Operators;
 using Org.BouncyCastle.X509;
 
 namespace Org.BouncyCastle.Cmp
 {
-    public class ProtectedPkiMessageBuilder
+    public sealed class ProtectedPkiMessageBuilder
     {
-        private PkiHeaderBuilder hdrBuilBuilder;
+        private readonly PkiHeaderBuilder m_hdrBuilder;
         private PkiBody body;
-        private List<InfoTypeAndValue> generalInfos = new List<InfoTypeAndValue>();
-        private List<X509Certificate> extraCerts = new List<X509Certificate>();
+        private readonly List<InfoTypeAndValue> generalInfos = new List<InfoTypeAndValue>();
+        private readonly List<X509Certificate> extraCerts = new List<X509Certificate>();
 
         public ProtectedPkiMessageBuilder(GeneralName sender, GeneralName recipient)
             : this(PkiHeader.CMP_2000, sender, recipient)
@@ -24,18 +24,18 @@ namespace Org.BouncyCastle.Cmp
 
         public ProtectedPkiMessageBuilder(int pvno, GeneralName sender, GeneralName recipient)
         {
-            hdrBuilBuilder = new PkiHeaderBuilder(pvno, sender, recipient);
+            m_hdrBuilder = new PkiHeaderBuilder(pvno, sender, recipient);
         }
 
         public ProtectedPkiMessageBuilder SetTransactionId(byte[] tid)
         {
-            hdrBuilBuilder.SetTransactionID(tid);
+            m_hdrBuilder.SetTransactionID(tid);
             return this;
         }
 
         public ProtectedPkiMessageBuilder SetFreeText(PkiFreeText freeText)
         {
-            hdrBuilBuilder.SetFreeText(freeText);
+            m_hdrBuilder.SetFreeText(freeText);
             return this;
         }
 
@@ -45,33 +45,39 @@ namespace Org.BouncyCastle.Cmp
             return this;
         }
 
-        public ProtectedPkiMessageBuilder SetMessageTime(DerGeneralizedTime generalizedTime)
+        public ProtectedPkiMessageBuilder SetMessageTime(DateTime time)
         {
-            hdrBuilBuilder.SetMessageTime(generalizedTime);
+            m_hdrBuilder.SetMessageTime(new Asn1GeneralizedTime(time));
+            return this;
+        }
+
+        public ProtectedPkiMessageBuilder SetMessageTime(Asn1GeneralizedTime generalizedTime)
+        {
+            m_hdrBuilder.SetMessageTime(generalizedTime);
             return this;
         }
 
         public ProtectedPkiMessageBuilder SetRecipKID(byte[] id)
         {
-            hdrBuilBuilder.SetRecipKID(id);
+            m_hdrBuilder.SetRecipKID(id);
             return this;
         }
 
         public ProtectedPkiMessageBuilder SetRecipNonce(byte[] nonce)
         {
-            hdrBuilBuilder.SetRecipNonce(nonce);
+            m_hdrBuilder.SetRecipNonce(nonce);
             return this;
         }
 
         public ProtectedPkiMessageBuilder SetSenderKID(byte[] id)
         {
-            hdrBuilBuilder.SetSenderKID(id);
+            m_hdrBuilder.SetSenderKID(id);
             return this;
         }
 
         public ProtectedPkiMessageBuilder SetSenderNonce(byte[] nonce)
         {
-            hdrBuilBuilder.SetSenderNonce(nonce);
+            m_hdrBuilder.SetSenderNonce(nonce);
             return this;
         }
 
@@ -92,76 +98,60 @@ namespace Org.BouncyCastle.Cmp
             if (null == body)
                 throw new InvalidOperationException("body must be set before building");
 
-            IStreamCalculator calculator = signatureFactory.CreateCalculator();
+            IStreamCalculator<IBlockResult> calculator = signatureFactory.CreateCalculator();
 
-            if (!(signatureFactory.AlgorithmDetails is AlgorithmIdentifier))
-            {
+            if (!(signatureFactory.AlgorithmDetails is AlgorithmIdentifier algorithmDetails))
                 throw new ArgumentException("AlgorithmDetails is not AlgorithmIdentifier");
-            }
 
-            FinalizeHeader((AlgorithmIdentifier)signatureFactory.AlgorithmDetails);
-            PkiHeader header = hdrBuilBuilder.Build();
+            FinalizeHeader(algorithmDetails);
+            PkiHeader header = m_hdrBuilder.Build();
             DerBitString protection = new DerBitString(CalculateSignature(calculator, header, body));
             return FinalizeMessage(header, protection);
         }
 
-        public ProtectedPkiMessage Build(IMacFactory factory)
+        public ProtectedPkiMessage Build(IMacFactory macFactory)
         {
             if (null == body)
                 throw new InvalidOperationException("body must be set before building");
 
-            IStreamCalculator calculator = factory.CreateCalculator();
-            FinalizeHeader((AlgorithmIdentifier)factory.AlgorithmDetails);
-            PkiHeader header = hdrBuilBuilder.Build();
+            IStreamCalculator<IBlockResult> calculator = macFactory.CreateCalculator();
+
+            if (!(macFactory.AlgorithmDetails is AlgorithmIdentifier algorithmDetails))
+                throw new ArgumentException("AlgorithmDetails is not AlgorithmIdentifier");
+
+            FinalizeHeader(algorithmDetails);
+            PkiHeader header = m_hdrBuilder.Build();
             DerBitString protection = new DerBitString(CalculateSignature(calculator, header, body));
             return FinalizeMessage(header, protection);
         }
 
         private void FinalizeHeader(AlgorithmIdentifier algorithmIdentifier)
         {
-            hdrBuilBuilder.SetProtectionAlg(algorithmIdentifier);
+            m_hdrBuilder.SetProtectionAlg(algorithmIdentifier);
             if (generalInfos.Count > 0)
             {
-                hdrBuilBuilder.SetGeneralInfo(generalInfos.ToArray());
+                m_hdrBuilder.SetGeneralInfo(generalInfos.ToArray());
             }
         }
 
         private ProtectedPkiMessage FinalizeMessage(PkiHeader header, DerBitString protection)
         {
-            if (extraCerts.Count > 0)
+            if (extraCerts.Count < 1)
+                return new ProtectedPkiMessage(new PkiMessage(header, body, protection));
+
+            CmpCertificate[] cmpCertificates = new CmpCertificate[extraCerts.Count];
+            for (int i = 0; i < cmpCertificates.Length; i++)
             {
-                CmpCertificate[] cmpCertificates = new CmpCertificate[extraCerts.Count];
-                for (int i = 0; i < cmpCertificates.Length; i++)
-                {
-                    byte[] cert = extraCerts[i].GetEncoded();
-                    cmpCertificates[i] = CmpCertificate.GetInstance(Asn1Object.FromByteArray(cert));
-                }
-
-                return new ProtectedPkiMessage(new PkiMessage(header, body, protection, cmpCertificates));
+                cmpCertificates[i] = new CmpCertificate(extraCerts[i].CertificateStructure);
             }
 
-            return new ProtectedPkiMessage(new PkiMessage(header, body, protection));
+            return new ProtectedPkiMessage(new PkiMessage(header, body, protection, cmpCertificates));
         }
 
-        private byte[] CalculateSignature(IStreamCalculator signer, PkiHeader header, PkiBody body)
+        private byte[] CalculateSignature(IStreamCalculator<IBlockResult> signer, PkiHeader header, PkiBody body)
         {
             new DerSequence(header, body).EncodeTo(signer.Stream);
-            object result = signer.GetResult();
-
-            if (result is DefaultSignatureResult sigResult)
-            {
-                return sigResult.Collect();
-            }
-            else if (result is IBlockResult blockResult)
-            {
-                return blockResult.Collect();
-            }
-            else if (result is byte[] bytesResult)
-            {
-                return bytesResult;
-            }
-
-            throw new InvalidOperationException("result is not byte[] or DefaultSignatureResult");
+            return signer.GetResult().Collect();
         }
     }
 }
diff --git a/crypto/src/cmp/RevocationDetails.cs b/crypto/src/cmp/RevocationDetails.cs
index 2d3f9a5eb..6060c6575 100644
--- a/crypto/src/cmp/RevocationDetails.cs
+++ b/crypto/src/cmp/RevocationDetails.cs
@@ -1,38 +1,24 @@
-using System;
-
-using Org.BouncyCastle.Asn1.Cmp;
+using Org.BouncyCastle.Asn1.Cmp;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Cmp
 {
-    public class RevocationDetails
+    public struct RevocationDetails
     {
-        private readonly RevDetails revDetails;
+        private readonly RevDetails m_revDetails;
 
         public RevocationDetails(RevDetails revDetails)
         {
-            this.revDetails = revDetails;
+            m_revDetails = revDetails;
         }
 
-        public X509Name Subject
-        {
-            get { return revDetails.CertDetails.Subject; }
-        }
+        public X509Name Subject => m_revDetails.CertDetails.Subject;
 
-        public X509Name Issuer
-        {
-            get { return revDetails.CertDetails.Issuer; }
-        }
+        public X509Name Issuer => m_revDetails.CertDetails.Issuer;
 
-        public BigInteger SerialNumber
-        {
-            get { return revDetails.CertDetails.SerialNumber.Value; }
-        }
+        public BigInteger SerialNumber => m_revDetails.CertDetails.SerialNumber.Value;
 
-        public RevDetails ToASN1Structure()
-        {
-            return revDetails;
-        }
+        public RevDetails ToASN1Structure() => m_revDetails;
     }
 }
diff --git a/crypto/src/cmp/RevocationDetailsBuilder.cs b/crypto/src/cmp/RevocationDetailsBuilder.cs
index b3be01242..086bf9c11 100644
--- a/crypto/src/cmp/RevocationDetailsBuilder.cs
+++ b/crypto/src/cmp/RevocationDetailsBuilder.cs
@@ -1,6 +1,4 @@
-using System;
-
-using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cmp;
 using Org.BouncyCastle.Asn1.Crmf;
 using Org.BouncyCastle.Asn1.X509;
@@ -8,15 +6,15 @@ using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Cmp
 {
-    public class RevocationDetailsBuilder
+    public sealed class RevocationDetailsBuilder
     {
-        private readonly CertTemplateBuilder _templateBuilder = new CertTemplateBuilder();
+        private readonly CertTemplateBuilder m_templateBuilder = new CertTemplateBuilder();
 
         public RevocationDetailsBuilder SetPublicKey(SubjectPublicKeyInfo publicKey)
         {
             if (publicKey != null)
             {
-                _templateBuilder.SetPublicKey(publicKey);
+                m_templateBuilder.SetPublicKey(publicKey);
             }
 
             return this;
@@ -26,7 +24,7 @@ namespace Org.BouncyCastle.Cmp
         {
             if (issuer != null)
             {
-                _templateBuilder.SetIssuer(issuer);
+                m_templateBuilder.SetIssuer(issuer);
             }
 
             return this;
@@ -36,7 +34,7 @@ namespace Org.BouncyCastle.Cmp
         {
             if (serialNumber != null)
             {
-                _templateBuilder.SetSerialNumber(new DerInteger(serialNumber));
+                m_templateBuilder.SetSerialNumber(new DerInteger(serialNumber));
             }
 
             return this;
@@ -46,7 +44,7 @@ namespace Org.BouncyCastle.Cmp
         {
             if (subject != null)
             {
-                _templateBuilder.SetSubject(subject);
+                m_templateBuilder.SetSubject(subject);
             }
 
             return this;
@@ -54,7 +52,7 @@ namespace Org.BouncyCastle.Cmp
 
         public RevocationDetails Build()
         {
-            return new RevocationDetails(new RevDetails(_templateBuilder.Build()));
+            return new RevocationDetails(new RevDetails(m_templateBuilder.Build()));
         }
     }
 }
\ No newline at end of file
diff --git a/crypto/src/cms/CMSAuthenticatedDataGenerator.cs b/crypto/src/cms/CMSAuthenticatedDataGenerator.cs
index 9bfabe8b1..f6827157c 100644
--- a/crypto/src/cms/CMSAuthenticatedDataGenerator.cs
+++ b/crypto/src/cms/CMSAuthenticatedDataGenerator.cs
@@ -29,20 +29,14 @@ namespace Org.BouncyCastle.Cms
 	public class CmsAuthenticatedDataGenerator
 	    : CmsAuthenticatedGenerator
 	{
-	    /**
-	     * base constructor
-	     */
 	    public CmsAuthenticatedDataGenerator()
 	    {
 	    }
 
-	    /**
-	     * constructor allowing specific source of randomness
-	     * @param rand instance of SecureRandom to use
-	     */
-	    public CmsAuthenticatedDataGenerator(
-	        SecureRandom rand)
-	        : base(rand)
+        /// <summary>Constructor allowing specific source of randomness</summary>
+        /// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+	    public CmsAuthenticatedDataGenerator(SecureRandom random)
+	        : base(random)
 	    {
 	    }
 
@@ -68,9 +62,7 @@ namespace Org.BouncyCastle.Cms
 
 				Asn1Encodable asn1Params = GenerateAsn1Parameters(macOid, encKeyBytes);
 
-				ICipherParameters cipherParameters;
-				macAlgId = GetAlgorithmIdentifier(
-				macOid, encKey, asn1Params, out cipherParameters);
+				macAlgId = GetAlgorithmIdentifier(macOid, encKey, asn1Params, out var cipherParameters);
 
 				IMac mac = MacUtilities.GetMac(macOid);
 				// TODO Confirm no ParametersWithRandom needed
@@ -78,7 +70,7 @@ namespace Org.BouncyCastle.Cms
 //	            mac.Init(cipherParameters);
 				mac.Init(encKey);
 
-				MemoryStream bOut = new MemoryStream();
+				var bOut = new MemoryStream();
 				Stream mOut = new TeeOutputStream(bOut, new MacSink(mac));
 
 				content.Write(mOut);
@@ -103,13 +95,13 @@ namespace Org.BouncyCastle.Cms
 				throw new CmsException("exception decoding algorithm parameters.", e);
 			}
 
-			Asn1EncodableVector recipientInfos = new Asn1EncodableVector();
+			var recipientInfos = new Asn1EncodableVector();
 
 			foreach (RecipientInfoGenerator rig in recipientInfoGenerators) 
 			{
 				try
 				{
-					recipientInfos.Add(rig.Generate(encKey, rand));
+					recipientInfos.Add(rig.Generate(encKey, m_random));
 				}
 				catch (InvalidKeyException e)
 				{
@@ -121,11 +113,11 @@ namespace Org.BouncyCastle.Cms
 				}
 			}
 			
-			ContentInfo eci = new ContentInfo(CmsObjectIdentifiers.Data, encContent);
-			
-			ContentInfo contentInfo = new ContentInfo(
-			CmsObjectIdentifiers.AuthenticatedData,
-			new AuthenticatedData(null, new DerSet(recipientInfos), macAlgId, null, eci, null, macResult, null));
+			var eci = new ContentInfo(CmsObjectIdentifiers.Data, encContent);
+
+			var contentInfo = new ContentInfo(
+				CmsObjectIdentifiers.AuthenticatedData,
+				new AuthenticatedData(null, new DerSet(recipientInfos), macAlgId, null, eci, null, macResult, null));
 			
 			return new CmsAuthenticatedData(contentInfo);
 		}
@@ -142,7 +134,7 @@ namespace Org.BouncyCastle.Cms
 				// FIXME Will this work for macs?
 				CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
 
-				keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+				keyGen.Init(new KeyGenerationParameters(m_random, keyGen.DefaultStrength));
 
 				return Generate(content, encryptionOid, keyGen);
             }
diff --git a/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs b/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
index 4ac2b34c9..6348431a2 100644
--- a/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
@@ -42,20 +42,14 @@ namespace Org.BouncyCastle.Cms
 		private int                 _bufferSize;
 		private bool                _berEncodeRecipientSet;
 
-		/**
-		* base constructor
-		*/
 		public CmsAuthenticatedDataStreamGenerator()
 		{
 		}
 
-		/**
-		* constructor allowing specific source of randomness
-		* @param rand instance of SecureRandom to use
-		*/
-		public CmsAuthenticatedDataStreamGenerator(
-			SecureRandom rand)
-			: base(rand)
+        /// <summary>Constructor allowing specific source of randomness</summary>
+        /// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsAuthenticatedDataStreamGenerator(SecureRandom random)
+			: base(random)
 		{
 		}
 
@@ -105,7 +99,7 @@ namespace Org.BouncyCastle.Cms
 			{
 				try
 				{
-					recipientInfos.Add(rig.Generate(encKey, rand));
+					recipientInfos.Add(rig.Generate(encKey, m_random));
 				}
 				catch (InvalidKeyException e)
 				{
@@ -195,7 +189,7 @@ namespace Org.BouncyCastle.Cms
 		{
 			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
 
-			keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+			keyGen.Init(new KeyGenerationParameters(m_random, keyGen.DefaultStrength));
 
 			return Open(outStr, encryptionOid, keyGen);
 		}
@@ -210,7 +204,7 @@ namespace Org.BouncyCastle.Cms
 		{
 			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
 
-			keyGen.Init(new KeyGenerationParameters(rand, keySize));
+			keyGen.Init(new KeyGenerationParameters(m_random, keySize));
 
 			return Open(outStr, encryptionOid, keyGen);
 		}
@@ -243,12 +237,18 @@ namespace Org.BouncyCastle.Cms
                 macStream.Write(buffer, offset, count);
             }
 
-			public override void WriteByte(byte value)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void Write(ReadOnlySpan<byte> buffer)
+            {
+                macStream.Write(buffer);
+            }
+#endif
+
+            public override void WriteByte(byte value)
 			{
 				macStream.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
@@ -269,25 +269,6 @@ namespace Org.BouncyCastle.Cms
                 }
                 base.Dispose(disposing);
             }
-#else
-			public override void Close()
-			{
-                Platform.Dispose(macStream);
-
-                // TODO Parent context(s) should really be be closed explicitly
-
-				eiGen.Close();
-
-				// [TODO] auth attributes go here 
-				byte[] macOctets = MacUtilities.DoFinal(mac);
-				authGen.AddObject(new DerOctetString(macOctets));
-				// [TODO] unauth attributes go here
-
-				authGen.Close();
-				cGen.Close();
-                base.Close();
-			}
-#endif
 		}
 	}
 }
diff --git a/crypto/src/cms/CMSAuthenticatedGenerator.cs b/crypto/src/cms/CMSAuthenticatedGenerator.cs
index 8824d1913..1f73c9b19 100644
--- a/crypto/src/cms/CMSAuthenticatedGenerator.cs
+++ b/crypto/src/cms/CMSAuthenticatedGenerator.cs
@@ -14,21 +14,14 @@ namespace Org.BouncyCastle.Cms
 	public class CmsAuthenticatedGenerator
 		: CmsEnvelopedGenerator
 	{
-		/**
-		* base constructor
-		*/
 		public CmsAuthenticatedGenerator()
 		{
 		}
 
-		/**
-		* constructor allowing specific source of randomness
-		*
-		* @param rand instance of SecureRandom to use
-		*/
-		public CmsAuthenticatedGenerator(
-			SecureRandom rand)
-			: base(rand)
+        /// <summary>Constructor allowing specific source of randomness</summary>
+        /// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+        public CmsAuthenticatedGenerator(SecureRandom random)
+			: base(random)
 		{
 		}
 	}
diff --git a/crypto/src/cms/CMSCompressedData.cs b/crypto/src/cms/CMSCompressedData.cs
index 21651f041..5f8165005 100644
--- a/crypto/src/cms/CMSCompressedData.cs
+++ b/crypto/src/cms/CMSCompressedData.cs
@@ -1,10 +1,9 @@
-using System;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cms;
 using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.Utilities.Zlib;
+using Org.BouncyCastle.Utilities.IO.Compression;
 
 namespace Org.BouncyCastle.Cms
 {
@@ -45,7 +44,7 @@ namespace Org.BouncyCastle.Cms
             ContentInfo content = comData.EncapContentInfo;
 
 			Asn1OctetString bytes = (Asn1OctetString) content.Content;
-			ZInputStream zIn = new ZInputStream(bytes.GetOctetStream());
+			Stream zIn = ZLib.DecompressInput(bytes.GetOctetStream());
 
 			try
 			{
@@ -76,8 +75,7 @@ namespace Org.BouncyCastle.Cms
 			ContentInfo     content = comData.EncapContentInfo;
 
 			Asn1OctetString bytes = (Asn1OctetString)content.Content;
-
-			ZInputStream zIn = new ZInputStream(new MemoryStream(bytes.GetOctets(), false));
+            Stream zIn = ZLib.DecompressInput(bytes.GetOctetStream());
 
 			try
 			{
diff --git a/crypto/src/cms/CMSCompressedDataGenerator.cs b/crypto/src/cms/CMSCompressedDataGenerator.cs
index bea04752a..70515e8d3 100644
--- a/crypto/src/cms/CMSCompressedDataGenerator.cs
+++ b/crypto/src/cms/CMSCompressedDataGenerator.cs
@@ -5,7 +5,6 @@ using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cms;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.Utilities.Zlib;
 
 namespace Org.BouncyCastle.Cms
 {
@@ -22,32 +21,34 @@ namespace Org.BouncyCastle.Cms
     */
     public class CmsCompressedDataGenerator
     {
-        public const string ZLib = "1.2.840.113549.1.9.16.3.8";
+        public static readonly string ZLib = CmsObjectIdentifiers.ZlibCompress.Id;
 
-		public CmsCompressedDataGenerator()
+        public CmsCompressedDataGenerator()
         {
         }
 
 		/**
         * Generate an object that contains an CMS Compressed Data
         */
-        public CmsCompressedData Generate(
-            CmsProcessable	content,
-            string			compressionOid)
+        public CmsCompressedData Generate(CmsProcessable content, string compressionOid)
         {
+            if (ZLib != compressionOid)
+                throw new ArgumentException("Unsupported compression algorithm: " + compressionOid,
+                    nameof(compressionOid));
+
             AlgorithmIdentifier comAlgId;
             Asn1OctetString comOcts;
 
             try
             {
                 MemoryStream bOut = new MemoryStream();
-                ZOutputStream zOut = new ZOutputStream(bOut, JZlib.Z_DEFAULT_COMPRESSION);
+                Stream zOut = Utilities.IO.Compression.ZLib.CompressOutput(bOut, -1);
 
 				content.Write(zOut);
 
                 Platform.Dispose(zOut);
 
-                comAlgId = new AlgorithmIdentifier(new DerObjectIdentifier(compressionOid));
+                comAlgId = new AlgorithmIdentifier(CmsObjectIdentifiers.ZlibCompress);
 				comOcts = new BerOctetString(bOut.ToArray());
             }
             catch (IOException e)
diff --git a/crypto/src/cms/CMSCompressedDataParser.cs b/crypto/src/cms/CMSCompressedDataParser.cs
index b107ff608..38ff88968 100644
--- a/crypto/src/cms/CMSCompressedDataParser.cs
+++ b/crypto/src/cms/CMSCompressedDataParser.cs
@@ -3,7 +3,7 @@ using System.IO;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cms;
-using Org.BouncyCastle.Utilities.Zlib;
+using Org.BouncyCastle.Utilities.IO.Compression;
 
 namespace Org.BouncyCastle.Cms
 {
@@ -44,8 +44,9 @@ namespace Org.BouncyCastle.Cms
                 ContentInfoParser content = comData.GetEncapContentInfo();
 
                 Asn1OctetStringParser bytes = (Asn1OctetStringParser)content.GetContent(Asn1Tags.OctetString);
+                Stream zIn = ZLib.DecompressInput(bytes.GetOctetStream());
 
-                return new CmsTypedStream(content.ContentType.ToString(), new ZInputStream(bytes.GetOctetStream()));
+                return new CmsTypedStream(content.ContentType.ToString(), zIn);
             }
             catch (IOException e)
             {
diff --git a/crypto/src/cms/CMSCompressedDataStreamGenerator.cs b/crypto/src/cms/CMSCompressedDataStreamGenerator.cs
index 1a9513ce6..3669c0b3a 100644
--- a/crypto/src/cms/CMSCompressedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSCompressedDataStreamGenerator.cs
@@ -4,9 +4,9 @@ using System.IO;
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cms;
 using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
-using Org.BouncyCastle.Utilities.Zlib;
 
 namespace Org.BouncyCastle.Cms
 {
@@ -27,10 +27,10 @@ namespace Org.BouncyCastle.Cms
 	*/
 	public class CmsCompressedDataStreamGenerator
 	{
-		public const string ZLib = "1.2.840.113549.1.9.16.3.8";
+        public static readonly string ZLib = CmsObjectIdentifiers.ZlibCompress.Id;
+
+        private int _bufferSize;
 
-		private int _bufferSize;
-		
 		/**
 		* base constructor
 		*/
@@ -43,24 +43,27 @@ namespace Org.BouncyCastle.Cms
 		*
 		* @param bufferSize length of octet strings to buffer the data.
 		*/
-		public void SetBufferSize(
-			int bufferSize)
+		public void SetBufferSize(int bufferSize)
 		{
 			_bufferSize = bufferSize;
 		}
 
-		public Stream Open(
-			Stream	outStream,
-			string	compressionOID)
+        public Stream Open(Stream outStream)
+        {
+            return Open(outStream, CmsObjectIdentifiers.Data.Id, ZLib);
+        }
+
+        public Stream Open(Stream outStream, string compressionOid)
 		{
-			return Open(outStream, CmsObjectIdentifiers.Data.Id, compressionOID);
+			return Open(outStream, CmsObjectIdentifiers.Data.Id, compressionOid);
 		}
 
-		public Stream Open(
-			Stream	outStream,
-			string	contentOID,
-			string	compressionOID)
+		public Stream Open(Stream outStream, string contentOid, string compressionOid)
 		{
+			if (ZLib != compressionOid)
+				throw new ArgumentException("Unsupported compression algorithm: " + compressionOid,
+					nameof(compressionOid));
+
 			BerSequenceGenerator sGen = new BerSequenceGenerator(outStream);
 
 			sGen.AddObject(CmsObjectIdentifiers.CompressedData);
@@ -75,32 +78,32 @@ namespace Org.BouncyCastle.Cms
 			cGen.AddObject(new DerInteger(0));
 
 			// CompressionAlgorithmIdentifier
-			cGen.AddObject(new AlgorithmIdentifier(new DerObjectIdentifier(ZLib)));
+			cGen.AddObject(new AlgorithmIdentifier(CmsObjectIdentifiers.ZlibCompress));
 
 			//
 			// Encapsulated ContentInfo
 			//
 			BerSequenceGenerator eiGen = new BerSequenceGenerator(cGen.GetRawOutputStream());
 
-			eiGen.AddObject(new DerObjectIdentifier(contentOID));
+			eiGen.AddObject(new DerObjectIdentifier(contentOid));
 
 			Stream octetStream = CmsUtilities.CreateBerOctetOutputStream(
 				eiGen.GetRawOutputStream(), 0, true, _bufferSize);
 
 			return new CmsCompressedOutputStream(
-				new ZOutputStream(octetStream, JZlib.Z_DEFAULT_COMPRESSION), sGen, cGen, eiGen);
+				Utilities.IO.Compression.ZLib.CompressOutput(octetStream, -1), sGen, cGen, eiGen);
 		}
 
 		private class CmsCompressedOutputStream
 			: BaseOutputStream
 		{
-			private ZOutputStream _out;
+			private Stream _out;
 			private BerSequenceGenerator _sGen;
 			private BerSequenceGenerator _cGen;
 			private BerSequenceGenerator _eiGen;
 
 			internal CmsCompressedOutputStream(
-				ZOutputStream			outStream,
+				Stream					outStream,
 				BerSequenceGenerator	sGen,
 				BerSequenceGenerator	cGen,
 				BerSequenceGenerator	eiGen)
@@ -116,12 +119,18 @@ namespace Org.BouncyCastle.Cms
 				_out.Write(buffer, offset, count);
 			}
 
-			public override void WriteByte(byte value)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void Write(ReadOnlySpan<byte> buffer)
+			{
+                _out.Write(buffer);
+            }
+#endif
+
+            public override void WriteByte(byte value)
 			{
 				_out.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
@@ -136,19 +145,6 @@ namespace Org.BouncyCastle.Cms
                 }
                 base.Dispose(disposing);
             }
-#else
-			public override void Close()
-			{
-                Platform.Dispose(_out);
-
-                // TODO Parent context(s) should really be be closed explicitly
-
-                _eiGen.Close();
-				_cGen.Close();
-				_sGen.Close();
-				base.Close();
-			}
-#endif
 		}
 	}
 }
diff --git a/crypto/src/cms/CMSEnvelopedDataGenerator.cs b/crypto/src/cms/CMSEnvelopedDataGenerator.cs
index d646480e0..1b618b331 100644
--- a/crypto/src/cms/CMSEnvelopedDataGenerator.cs
+++ b/crypto/src/cms/CMSEnvelopedDataGenerator.cs
@@ -33,10 +33,9 @@ namespace Org.BouncyCastle.Cms
         }
 
 		/// <summary>Constructor allowing specific source of randomness</summary>
-		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
-		public CmsEnvelopedDataGenerator(
-			SecureRandom rand)
-			: base(rand)
+		/// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsEnvelopedDataGenerator(SecureRandom random)
+			: base(random)
 		{
 		}
 
@@ -65,7 +64,7 @@ namespace Org.BouncyCastle.Cms
 					encryptionOid, encKey, asn1Params, out cipherParameters);
 
 				IBufferedCipher cipher = CipherUtilities.GetCipher(encryptionOid);
-				cipher.Init(true, new ParametersWithRandom(cipherParameters, rand));
+				cipher.Init(true, new ParametersWithRandom(cipherParameters, m_random));
 
 				MemoryStream bOut = new MemoryStream();
 				CipherStream cOut = new CipherStream(bOut, null, cipher);
@@ -96,7 +95,7 @@ namespace Org.BouncyCastle.Cms
             {
                 try
                 {
-                    recipientInfos.Add(rig.Generate(encKey, rand));
+                    recipientInfos.Add(rig.Generate(encKey, m_random));
                 }
                 catch (InvalidKeyException e)
                 {
@@ -138,7 +137,7 @@ namespace Org.BouncyCastle.Cms
             {
 				CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
                
-				keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+				keyGen.Init(new KeyGenerationParameters(m_random, keyGen.DefaultStrength));
 
 				return Generate(content, encryptionOid, keyGen);
             }
@@ -185,7 +184,7 @@ namespace Org.BouncyCastle.Cms
             {
                 try
                 {
-                    recipientInfos.Add(rig.Generate(encKey, rand));
+                    recipientInfos.Add(rig.Generate(encKey, m_random));
                 }
                 catch (InvalidKeyException e)
                 {
@@ -228,7 +227,7 @@ namespace Org.BouncyCastle.Cms
             {
 				CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
 
-				keyGen.Init(new KeyGenerationParameters(rand, keySize));
+				keyGen.Init(new KeyGenerationParameters(m_random, keySize));
 
 				return Generate(content, encryptionOid, keyGen);
             }
diff --git a/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs b/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
index 90ecf0748..ad356a208 100644
--- a/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
@@ -46,10 +46,9 @@ namespace Org.BouncyCastle.Cms
 		}
 
 		/// <summary>Constructor allowing specific source of randomness</summary>
-		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
-		public CmsEnvelopedDataStreamGenerator(
-			SecureRandom rand)
-			: base(rand)
+		/// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsEnvelopedDataStreamGenerator(SecureRandom random)
+			: base(random)
 		{
 		}
 
@@ -104,7 +103,7 @@ namespace Org.BouncyCastle.Cms
 			{
 				try
 				{
-					recipientInfos.Add(rig.Generate(encKey, rand));
+					recipientInfos.Add(rig.Generate(encKey, m_random));
 				}
 				catch (InvalidKeyException e)
 				{
@@ -162,7 +161,7 @@ namespace Org.BouncyCastle.Cms
 					eiGen.GetRawOutputStream(), 0, false, _bufferSize);
 
                 IBufferedCipher cipher = CipherUtilities.GetCipher(encAlgID.Algorithm);
-				cipher.Init(true, new ParametersWithRandom(cipherParameters, rand));
+				cipher.Init(true, new ParametersWithRandom(cipherParameters, m_random));
 				CipherStream cOut = new CipherStream(octetOutputStream, null, cipher);
 
 				return new CmsEnvelopedDataOutputStream(this, cOut, cGen, envGen, eiGen);
@@ -191,7 +190,7 @@ namespace Org.BouncyCastle.Cms
 		{
 			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
 
-			keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+			keyGen.Init(new KeyGenerationParameters(m_random, keyGen.DefaultStrength));
 
 			return Open(outStream, encryptionOid, keyGen);
 		}
@@ -207,7 +206,7 @@ namespace Org.BouncyCastle.Cms
 		{
 			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
 
-			keyGen.Init(new KeyGenerationParameters(rand, keySize));
+			keyGen.Init(new KeyGenerationParameters(m_random, keySize));
 
 			return Open(outStream, encryptionOid, keyGen);
 		}
@@ -241,49 +240,43 @@ namespace Org.BouncyCastle.Cms
 				_out.Write(buffer, offset, count);
 			}
 
-			public override void WriteByte(byte value)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void Write(ReadOnlySpan<byte> buffer)
+            {
+                _out.Write(buffer);
+            }
+#endif
+
+            public override void WriteByte(byte value)
 			{
 				_out.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
  				{
-					ImplClose();
-                }
-                base.Dispose(disposing);
-            }
-#else
-			public override void Close()
-			{
-				ImplClose();
-				base.Close();
-			}
-#endif
+                    Platform.Dispose(_out);
 
-			private void ImplClose()
-            {
-				Platform.Dispose(_out);
+                    // TODO Parent context(s) should really be closed explicitly
 
-				// TODO Parent context(s) should really be closed explicitly
+                    _eiGen.Close();
 
-				_eiGen.Close();
+                    if (_outer.unprotectedAttributeGenerator != null)
+                    {
+                        Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(
+                            new Dictionary<CmsAttributeTableParameter, object>());
 
-				if (_outer.unprotectedAttributeGenerator != null)
-				{
-					Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(
-						new Dictionary<CmsAttributeTableParameter, object>());
+                        Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector());
 
-					Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector());
-
-					_envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs));
-				}
+                        _envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs));
+                    }
 
-				_envGen.Close();
-				_cGen.Close();
-			}
+                    _envGen.Close();
+                    _cGen.Close();
+                }
+                base.Dispose(disposing);
+            }
 		}
 	}
 }
diff --git a/crypto/src/cms/CMSEnvelopedGenerator.cs b/crypto/src/cms/CMSEnvelopedGenerator.cs
index e0a94c4d3..eef572878 100644
--- a/crypto/src/cms/CMSEnvelopedGenerator.cs
+++ b/crypto/src/cms/CMSEnvelopedGenerator.cs
@@ -30,7 +30,7 @@ namespace Org.BouncyCastle.Cms
 	*      CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
 	* </pre>
 	*/
-	public class CmsEnvelopedGenerator
+	public abstract class CmsEnvelopedGenerator
 	{
 		// Note: These tables are complementary: If rc2Table[i]==j, then rc2Ekb[j]==i
 		internal static readonly short[] rc2Table =
@@ -100,21 +100,23 @@ namespace Org.BouncyCastle.Cms
 		public static readonly string ECMqvSha1Kdf		= X9ObjectIdentifiers.MqvSinglePassSha1KdfScheme.Id;
 
 		internal readonly IList<RecipientInfoGenerator> recipientInfoGenerators = new List<RecipientInfoGenerator>();
-		internal readonly SecureRandom rand;
+		internal readonly SecureRandom m_random;
 
         internal CmsAttributeTableGenerator unprotectedAttributeGenerator = null;
 
-		public CmsEnvelopedGenerator()
-			: this(new SecureRandom())
+        protected CmsEnvelopedGenerator()
+			: this(CryptoServicesRegistrar.GetSecureRandom())
 		{
 		}
 
 		/// <summary>Constructor allowing specific source of randomness</summary>
-		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
-		public CmsEnvelopedGenerator(
-			SecureRandom rand)
+		/// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+		protected CmsEnvelopedGenerator(SecureRandom random)
 		{
-			this.rand = rand;
+			if (random == null)
+				throw new ArgumentNullException(nameof(random));
+
+			m_random = random;
 		}
 
         public CmsAttributeTableGenerator UnprotectedAttributeGenerator
@@ -304,7 +306,7 @@ namespace Org.BouncyCastle.Cms
 				if (encryptionOid.Equals(RC2Cbc))
 				{
 					byte[] iv = new byte[8];
-					rand.NextBytes(iv);
+                    m_random.NextBytes(iv);
 
 					// TODO Is this detailed repeat of Java version really necessary?
 					int effKeyBits = encKeyBytes.Length * 8;
@@ -323,7 +325,7 @@ namespace Org.BouncyCastle.Cms
 				}
 				else
 				{
-					asn1Params = ParameterUtilities.GenerateParameters(encryptionOid, rand);
+					asn1Params = ParameterUtilities.GenerateParameters(encryptionOid, m_random);
 				}
 			}
 			catch (SecurityUtilityException)
diff --git a/crypto/src/cms/CMSEnvelopedHelper.cs b/crypto/src/cms/CMSEnvelopedHelper.cs
index 91f5ab910..86baa3642 100644
--- a/crypto/src/cms/CMSEnvelopedHelper.cs
+++ b/crypto/src/cms/CMSEnvelopedHelper.cs
@@ -114,21 +114,21 @@ namespace Org.BouncyCastle.Cms
 			CmsSecureReadable secureReadable)
 		{
 			Asn1Encodable recipInfo = info.Info;
-			if (recipInfo is KeyTransRecipientInfo)
+			if (recipInfo is KeyTransRecipientInfo keyTransRecipientInfo)
 			{
-				infos.Add(new KeyTransRecipientInformation((KeyTransRecipientInfo)recipInfo, secureReadable));
+				infos.Add(new KeyTransRecipientInformation(keyTransRecipientInfo, secureReadable));
 			}
-			else if (recipInfo is KekRecipientInfo)
+			else if (recipInfo is KekRecipientInfo kekRecipientInfo)
 			{
-				infos.Add(new KekRecipientInformation((KekRecipientInfo)recipInfo, secureReadable));
+				infos.Add(new KekRecipientInformation(kekRecipientInfo, secureReadable));
 			}
-			else if (recipInfo is KeyAgreeRecipientInfo)
+			else if (recipInfo is KeyAgreeRecipientInfo keyAgreeRecipientInfo)
 			{
-				KeyAgreeRecipientInformation.ReadRecipientInfo(infos, (KeyAgreeRecipientInfo)recipInfo, secureReadable);
+				KeyAgreeRecipientInformation.ReadRecipientInfo(infos, keyAgreeRecipientInfo, secureReadable);
 			}
-			else if (recipInfo is PasswordRecipientInfo)
+			else if (recipInfo is PasswordRecipientInfo passwordRecipientInfo)
 			{
-				infos.Add(new PasswordRecipientInformation((PasswordRecipientInfo)recipInfo, secureReadable));
+				infos.Add(new PasswordRecipientInformation(passwordRecipientInfo, secureReadable));
 			}
 		}
 
diff --git a/crypto/src/cms/CMSSignedDataGenerator.cs b/crypto/src/cms/CMSSignedDataGenerator.cs
index 3a612a635..fff22e057 100644
--- a/crypto/src/cms/CMSSignedDataGenerator.cs
+++ b/crypto/src/cms/CMSSignedDataGenerator.cs
@@ -55,6 +55,7 @@ namespace Org.BouncyCastle.Cms
 			internal SignerInf(
                 CmsSignedGenerator			outer,
 	            AsymmetricKeyParameter		key,
+				SecureRandom                random,
 	            SignerIdentifier			signerIdentifier,
 	            string						digestOID,
 	            string						encOID,
@@ -67,7 +68,7 @@ namespace Org.BouncyCastle.Cms
                 string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(encOID);
 
                 this.outer = outer;
-                this.sigCalc = new Asn1SignatureFactory(signatureName, key);
+                this.sigCalc = new Asn1SignatureFactory(signatureName, key, random);
                 this.signerIdentifier = signerIdentifier;
                 this.digestOID = digestOID;
                 this.encOID = encOID;
@@ -110,10 +111,7 @@ namespace Org.BouncyCastle.Cms
 				get { return unsAttr; }
             }
 
-			internal SignerInfo ToSignerInfo(
-                DerObjectIdentifier	contentType,
-                CmsProcessable		content,
-				SecureRandom		random)
+			internal SignerInfo ToSignerInfo(DerObjectIdentifier contentType, CmsProcessable content)
             {
                 AlgorithmIdentifier digAlgId = DigestAlgorithmID;
 				string digestName = Helper.GetDigestAlgName(digestOID);
@@ -133,7 +131,7 @@ namespace Org.BouncyCastle.Cms
 
 				Asn1Set signedAttr = null;
 
-				IStreamCalculator calculator = sigCalc.CreateCalculator();
+				IStreamCalculator<IBlockResult> calculator = sigCalc.CreateCalculator();
 				using (Stream sigStr = calculator.Stream)
                 {
 					if (sAttr != null)
@@ -165,7 +163,7 @@ namespace Org.BouncyCastle.Cms
 					}
 				}
 
-                byte[] sigBytes = ((IBlockResult)calculator.GetResult()).Collect();
+                byte[] sigBytes = calculator.GetResult().Collect();
 
 				Asn1Set unsignedAttr = null;
 				if (unsAttr != null)
@@ -196,10 +194,9 @@ namespace Org.BouncyCastle.Cms
         }
 
 		/// <summary>Constructor allowing specific source of randomness</summary>
-		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
-		public CmsSignedDataGenerator(
-			SecureRandom rand)
-			: base(rand)
+		/// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsSignedDataGenerator(SecureRandom random)
+			: base(random)
 		{
 		}
 
@@ -425,7 +422,7 @@ namespace Org.BouncyCastle.Cms
 			CmsAttributeTableGenerator  unsignedAttrGen,
 			Asn1.Cms.AttributeTable		baseSignedTable)
 		{
-			signerInfs.Add(new SignerInf(this, privateKey, signerIdentifier, digestOID, encryptionOID,
+			signerInfs.Add(new SignerInf(this, privateKey, m_random, signerIdentifier, digestOID, encryptionOID,
 				signedAttrGen, unsignedAttrGen, baseSignedTable));
 		}
 
@@ -480,7 +477,7 @@ namespace Org.BouncyCastle.Cms
 				try
                 {
 					digestAlgs.Add(signer.DigestAlgorithmID);
-                    signerInfos.Add(signer.ToSignerInfo(contentTypeOid, content, rand));
+                    signerInfos.Add(signer.ToSignerInfo(contentTypeOid, content));
 				}
                 catch (IOException e)
                 {
diff --git a/crypto/src/cms/CMSSignedDataStreamGenerator.cs b/crypto/src/cms/CMSSignedDataStreamGenerator.cs
index 0dbdccbeb..3f2da5f7e 100644
--- a/crypto/src/cms/CMSSignedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSSignedDataStreamGenerator.cs
@@ -137,7 +137,7 @@ namespace Org.BouncyCastle.Cms
 					}
 				}
 
-				_sig.Init(true, new ParametersWithRandom(key, outer.rand));
+				_sig.Init(true, new ParametersWithRandom(key, outer.m_random));
 			}
 
 			public SignerInfo Generate(DerObjectIdentifier contentType, AlgorithmIdentifier digestAlgorithm,
@@ -234,10 +234,9 @@ namespace Org.BouncyCastle.Cms
         }
 
 		/// <summary>Constructor allowing specific source of randomness</summary>
-		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
-		public CmsSignedDataStreamGenerator(
-			SecureRandom rand)
-			: base(rand)
+		/// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsSignedDataStreamGenerator(SecureRandom random)
+			: base(random)
 		{
 		}
 
@@ -660,10 +659,8 @@ namespace Org.BouncyCastle.Cms
 			{
 				foreach (object obj in _certs)
 				{
-					if (obj is Asn1TaggedObject)
+					if (obj is Asn1TaggedObject tagged)
 					{
-						Asn1TaggedObject tagged = (Asn1TaggedObject) obj;
-
 						if (tagged.TagNo == 1)
 						{
 							attrCertV1Found = true;
@@ -741,9 +738,7 @@ namespace Org.BouncyCastle.Cms
 
 		private static Stream GetSafeOutputStream(Stream s)
 		{
-			if (s == null)
-				return new NullOutputStream();
-			return s;
+			return s ?? Stream.Null;
 		}
 
 		private static Stream GetSafeTeeOutputStream(Stream s1, Stream s2)
@@ -788,12 +783,18 @@ namespace Org.BouncyCastle.Cms
 				_out.Write(buffer, offset, count);
 			}
 
-			public override void WriteByte(byte value)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void Write(ReadOnlySpan<byte> buffer)
+            {
+                _out.Write(buffer);
+            }
+#endif
+
+            public override void WriteByte(byte value)
 			{
 				_out.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
@@ -802,13 +803,6 @@ namespace Org.BouncyCastle.Cms
                 }
                 base.Dispose(disposing);
             }
-#else
-			public override void Close()
-            {
-                DoClose();
-				base.Close();
-			}
-#endif
 
             private void DoClose()
             {
diff --git a/crypto/src/cms/CMSSignedGenerator.cs b/crypto/src/cms/CMSSignedGenerator.cs
index 58f66f214..c16f6e83c 100644
--- a/crypto/src/cms/CMSSignedGenerator.cs
+++ b/crypto/src/cms/CMSSignedGenerator.cs
@@ -15,6 +15,7 @@ using Org.BouncyCastle.Asn1.Rosstandart;
 using Org.BouncyCastle.Asn1.TeleTrust;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.Collections;
 using Org.BouncyCastle.X509;
@@ -481,7 +482,7 @@ namespace Org.BouncyCastle.Cms
         }
     }
 
-    public class CmsSignedGenerator
+    public abstract class CmsSignedGenerator
     {
         /**
         * Default type for the signed data.
@@ -516,19 +517,21 @@ namespace Org.BouncyCastle.Cms
         internal bool _useDerForCerts = false;
         internal bool _useDerForCrls = false;
 
-        protected readonly SecureRandom rand;
+        protected readonly SecureRandom m_random;
 
         protected CmsSignedGenerator()
-            : this(new SecureRandom())
+            : this(CryptoServicesRegistrar.GetSecureRandom())
         {
         }
 
         /// <summary>Constructor allowing specific source of randomness</summary>
-        /// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
-        protected CmsSignedGenerator(
-            SecureRandom rand)
+        /// <param name="random">Instance of <c>SecureRandom</c> to use.</param>
+        protected CmsSignedGenerator(SecureRandom random)
         {
-            this.rand = rand;
+            if (random == null)
+                throw new ArgumentNullException(nameof(random));
+
+            m_random = random;
         }
 
         internal protected virtual IDictionary<CmsAttributeTableParameter, object> GetBaseParameters(
diff --git a/crypto/src/cms/CMSSignedHelper.cs b/crypto/src/cms/CMSSignedHelper.cs
index 0aced112e..8df9e8f01 100644
--- a/crypto/src/cms/CMSSignedHelper.cs
+++ b/crypto/src/cms/CMSSignedHelper.cs
@@ -7,6 +7,7 @@ using Org.BouncyCastle.Asn1.Eac;
 using Org.BouncyCastle.Asn1.Nist;
 using Org.BouncyCastle.Asn1.Oiw;
 using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Rosstandart;
 using Org.BouncyCastle.Asn1.TeleTrust;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
@@ -82,15 +83,21 @@ namespace Org.BouncyCastle.Cms
 			AddEntries(EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA");
 			AddEntries(EacObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1");
 			AddEntries(EacObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1");
+            AddEntries(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94, "GOST3411", "GOST3410");
+            AddEntries(CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001, "GOST3411", "ECGOST3410");
+            AddEntries(RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_256, "GOST3411_2012_256", "ECGOST3410");
+            AddEntries(RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512, "GOST3411_2012_512", "ECGOST3410");
 
-			m_encryptionAlgs.Add(X9ObjectIdentifiers.IdDsa.Id, "DSA");
+            m_encryptionAlgs.Add(X9ObjectIdentifiers.IdDsa.Id, "DSA");
 			m_encryptionAlgs.Add(PkcsObjectIdentifiers.RsaEncryption.Id, "RSA");
 			m_encryptionAlgs.Add(TeleTrusTObjectIdentifiers.TeleTrusTRsaSignatureAlgorithm.Id, "RSA");
 			m_encryptionAlgs.Add(X509ObjectIdentifiers.IdEARsa.Id, "RSA");
 			m_encryptionAlgs.Add(CmsSignedGenerator.EncryptionRsaPss, "RSAandMGF1");
 			m_encryptionAlgs.Add(CryptoProObjectIdentifiers.GostR3410x94.Id, "GOST3410");
 			m_encryptionAlgs.Add(CryptoProObjectIdentifiers.GostR3410x2001.Id, "ECGOST3410");
-			m_encryptionAlgs.Add("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
+            m_encryptionAlgs.Add(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256.Id, "ECGOST3410");
+            m_encryptionAlgs.Add(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512.Id, "ECGOST3410");
+            m_encryptionAlgs.Add("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
 			m_encryptionAlgs.Add("1.3.6.1.4.1.5849.1.1.5", "GOST3410");
 
 			m_digestAlgs.Add(PkcsObjectIdentifiers.MD2.Id, "MD2");
@@ -112,15 +119,17 @@ namespace Org.BouncyCastle.Cms
 			m_digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, "RIPEMD256");
 			m_digestAlgs.Add(CryptoProObjectIdentifiers.GostR3411.Id,  "GOST3411");
 			m_digestAlgs.Add("1.3.6.1.4.1.5849.1.2.1",  "GOST3411");
+            m_digestAlgs.Add(RosstandartObjectIdentifiers.id_tc26_gost_3411_12_256.Id, "GOST3411_2012_256");
+            m_digestAlgs.Add(RosstandartObjectIdentifiers.id_tc26_gost_3411_12_512.Id, "GOST3411_2012_512");
 
-			m_digestAliases.Add("SHA1", new string[]{ "SHA-1" });
+            m_digestAliases.Add("SHA1", new string[]{ "SHA-1" });
 			m_digestAliases.Add("SHA224", new string[]{ "SHA-224" });
 			m_digestAliases.Add("SHA256", new string[]{ "SHA-256" });
 			m_digestAliases.Add("SHA384", new string[]{ "SHA-384" });
 			m_digestAliases.Add("SHA512", new string[]{ "SHA-512" });
 
             noParams.Add(CmsSignedGenerator.EncryptionDsa);
-            //			noParams.Add(EncryptionECDsa);
+            //noParams.Add(EncryptionECDsa);
             noParams.Add(EncryptionECDsaWithSha1);
             noParams.Add(EncryptionECDsaWithSha224);
             noParams.Add(EncryptionECDsaWithSha256);
@@ -210,9 +219,9 @@ namespace Org.BouncyCastle.Cms
         {
             string encOID = null;
 
-            if (key is RsaKeyParameters)
+            if (key is RsaKeyParameters rsaKeyParameters)
             {
-                if (!((RsaKeyParameters)key).IsPrivate)
+                if (!rsaKeyParameters.IsPrivate)
                     throw new ArgumentException("Expected RSA private key");
 
                 encOID = CmsSignedGenerator.EncryptionRsa;
@@ -244,9 +253,8 @@ namespace Org.BouncyCastle.Cms
                     throw new ArgumentException("can't mix DSA with anything but SHA1/SHA2");
                 }
             }
-            else if (key is ECPrivateKeyParameters)
+            else if (key is ECPrivateKeyParameters ecPrivKey)
             {
-                ECPrivateKeyParameters ecPrivKey = (ECPrivateKeyParameters)key;
                 string algName = ecPrivKey.AlgorithmName;
 
                 if (algName == "ECGOST3410")
diff --git a/crypto/src/cms/CMSTypedStream.cs b/crypto/src/cms/CMSTypedStream.cs
index e99d904b5..7a66f0c74 100644
--- a/crypto/src/cms/CMSTypedStream.cs
+++ b/crypto/src/cms/CMSTypedStream.cs
@@ -33,11 +33,7 @@ namespace Org.BouncyCastle.Cms
 			int		bufSize)
 		{
 			_oid = oid;
-#if PORTABLE
-			_in = new FullReaderStream(inStream);
-#else
 			_in = new FullReaderStream(new BufferedStream(inStream, bufSize));
-#endif
 		}
 
 		public string ContentType
@@ -63,10 +59,17 @@ namespace Org.BouncyCastle.Cms
 			{
 			}
 
-			public override int Read(byte[]	buf, int off, int len)
+            public override int Read(byte[]	buf, int off, int len)
 			{
-				return Streams.ReadFully(base.s, buf, off, len);
+				return Streams.ReadFully(s, buf, off, len);
 			}
-		}
-	}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override int Read(Span<byte> buffer)
+            {
+                return Streams.ReadFully(s, buffer);
+            }
+#endif
+        }
+    }
 }
diff --git a/crypto/src/cms/CMSUtils.cs b/crypto/src/cms/CMSUtils.cs
index e30ac0491..6800c1d2a 100644
--- a/crypto/src/cms/CMSUtils.cs
+++ b/crypto/src/cms/CMSUtils.cs
@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Cms;
@@ -84,9 +83,10 @@ namespace Org.BouncyCastle.Cms
 			var result = new List<Asn1TaggedObject>();
 			if (attrCertStore != null)
             {
-				result.AddRange(
-					attrCertStore.EnumerateMatches(null)
-								 .Select(c => new DerTaggedObject(false, 2, c.AttributeCertificate)));
+				foreach (var attrCert in attrCertStore.EnumerateMatches(null))
+				{
+					result.Add(new DerTaggedObject(false, 2, attrCert.AttributeCertificate));
+				}
             }
 			return result;
 		}
@@ -96,9 +96,10 @@ namespace Org.BouncyCastle.Cms
 			var result = new List<X509CertificateStructure>();
 			if (certStore != null)
             {
-				result.AddRange(
-					certStore.EnumerateMatches(null)
-					         .Select(c => c.CertificateStructure));
+                foreach (var cert in certStore.EnumerateMatches(null))
+                {
+                    result.Add(cert.CertificateStructure);
+                }
 			}
 			return result;
 		}
@@ -108,9 +109,10 @@ namespace Org.BouncyCastle.Cms
 			var result = new List<CertificateList>();
 			if (crlStore != null)
 			{
-				result.AddRange(
-					crlStore.EnumerateMatches(null)
-					        .Select(c => c.CertificateList));
+                foreach (var crl in crlStore.EnumerateMatches(null))
+                {
+                    result.Add(crl.CertificateList);
+                }
 			}
 			return result;
 		}
diff --git a/crypto/src/cms/KeyAgreeRecipientInformation.cs b/crypto/src/cms/KeyAgreeRecipientInformation.cs
index 8843ede0d..398082810 100644
--- a/crypto/src/cms/KeyAgreeRecipientInformation.cs
+++ b/crypto/src/cms/KeyAgreeRecipientInformation.cs
@@ -208,11 +208,9 @@ namespace Org.BouncyCastle.Cms
         public override CmsTypedStream GetContentStream(
             ICipherParameters key)
         {
-            if (!(key is AsymmetricKeyParameter))
+            if (!(key is AsymmetricKeyParameter receiverPrivateKey))
                 throw new ArgumentException("KeyAgreement requires asymmetric key", "key");
 
-            AsymmetricKeyParameter receiverPrivateKey = (AsymmetricKeyParameter) key;
-
             if (!receiverPrivateKey.IsPrivate)
                 throw new ArgumentException("Expected private key", "key");
 
diff --git a/crypto/src/cms/RecipientInformation.cs b/crypto/src/cms/RecipientInformation.cs
index 272b841f2..978962711 100644
--- a/crypto/src/cms/RecipientInformation.cs
+++ b/crypto/src/cms/RecipientInformation.cs
@@ -112,15 +112,15 @@ namespace Org.BouncyCastle.Cms
 			if (resultMac == null)
 			{
 				object cryptoObject = secureReadable.CryptoObject;
-				if (cryptoObject is IMac)
+				if (cryptoObject is IMac mac)
 				{
-					resultMac = MacUtilities.DoFinal((IMac)cryptoObject);
+					resultMac = MacUtilities.DoFinal(mac);
 				}
 			}
 
 			return Arrays.Clone(resultMac);
 		}
-		
+
 		public abstract CmsTypedStream GetContentStream(ICipherParameters key);
 	}
 }
diff --git a/crypto/src/cms/SignerInformation.cs b/crypto/src/cms/SignerInformation.cs
index 551d9b737..99d300121 100644
--- a/crypto/src/cms/SignerInformation.cs
+++ b/crypto/src/cms/SignerInformation.cs
@@ -433,11 +433,9 @@ namespace Org.BouncyCastle.Cms
 					if (isCounterSignature)
 						throw new CmsException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
 
-					if (!(validContentType is DerObjectIdentifier))
+					if (!(validContentType is DerObjectIdentifier signedContentType))
 						throw new CmsException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
 
-					DerObjectIdentifier signedContentType = (DerObjectIdentifier)validContentType;
-
 					if (!signedContentType.Equals(contentType))
 						throw new CmsException("content-type attribute value does not match eContentType");
 				}
@@ -454,12 +452,8 @@ namespace Org.BouncyCastle.Cms
 				}
 				else
 				{
-					if (!(validMessageDigest is Asn1OctetString))
-					{
+					if (!(validMessageDigest is Asn1OctetString signedMessageDigest))
 						throw new CmsException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
-					}
-
-					Asn1OctetString signedMessageDigest = (Asn1OctetString)validMessageDigest;
 
 					if (!Arrays.AreEqual(resultDigest, signedMessageDigest.GetOctets()))
 						throw new CmsException("message-digest attribute value does not match calculated value");
diff --git a/crypto/src/crmf/CertificateRequestMessage.cs b/crypto/src/crmf/CertificateRequestMessage.cs
index c733eecbb..0a246aaa4 100644
--- a/crypto/src/crmf/CertificateRequestMessage.cs
+++ b/crypto/src/crmf/CertificateRequestMessage.cs
@@ -190,7 +190,7 @@ namespace Org.BouncyCastle.Crmf
         private bool verifySignature(IVerifierFactoryProvider verifierFactoryProvider, PopoSigningKey signKey)
         {
             IVerifierFactory verifer;
-            IStreamCalculator calculator;
+            IStreamCalculator<IVerifier> calculator;
             try
             {
                 verifer = verifierFactoryProvider.CreateVerifierFactory(signKey.AlgorithmIdentifier);
@@ -212,7 +212,7 @@ namespace Org.BouncyCastle.Crmf
                 calculator.Stream.Write(b, 0, b.Length);
             }
 
-            DefaultVerifierResult result = (DefaultVerifierResult)calculator.GetResult();
+            IVerifier result = calculator.GetResult();
 
             return result.IsVerified(signKey.Signature.GetBytes());
         }
diff --git a/crypto/src/crmf/CertificateRequestMessageBuilder.cs b/crypto/src/crmf/CertificateRequestMessageBuilder.cs
index 9a33fd964..38e95dfe7 100644
--- a/crypto/src/crmf/CertificateRequestMessageBuilder.cs
+++ b/crypto/src/crmf/CertificateRequestMessageBuilder.cs
@@ -71,9 +71,9 @@ namespace Org.BouncyCastle.Crmf
             return this;
         }
 
-        public CertificateRequestMessageBuilder SetValidity(Time notBefore, Time notAfter)
+        public CertificateRequestMessageBuilder SetValidity(DateTime? notBefore, DateTime? notAfter)
         {
-            _templateBuilder.SetValidity(new OptionalValidity(notBefore, notAfter));
+            _templateBuilder.SetValidity(new OptionalValidity(CreateTime(notBefore), CreateTime(notAfter)));
             return this;
         }
 
@@ -256,5 +256,10 @@ namespace Org.BouncyCastle.Crmf
 
             return new CertificateRequestMessage(CertReqMsg.GetInstance(new DerSequence(v)));
         }
+
+        private static Time CreateTime(DateTime? dateTime)
+        {
+            return dateTime == null ? null : new Time(dateTime.Value);
+        }
     }
 }
diff --git a/crypto/src/crmf/PKMacBuilder.cs b/crypto/src/crmf/PKMacBuilder.cs
index 156936eac..ae9baa3d0 100644
--- a/crypto/src/crmf/PKMacBuilder.cs
+++ b/crypto/src/crmf/PKMacBuilder.cs
@@ -15,7 +15,7 @@ using Org.BouncyCastle.Utilities;
 namespace Org.BouncyCastle.Crmf
 {
     internal class PKMacStreamCalculator
-        : IStreamCalculator
+        : IStreamCalculator<DefaultPKMacResult>
     {
         private readonly MacSink _stream;
 
@@ -29,7 +29,7 @@ namespace Org.BouncyCastle.Crmf
             get { return _stream; }
         }
 
-        public object GetResult()
+        public DefaultPKMacResult GetResult()
         {
             return new DefaultPKMacResult(_stream.Mac);
         }
@@ -52,7 +52,7 @@ namespace Org.BouncyCastle.Crmf
             get { return new AlgorithmIdentifier(CmpObjectIdentifiers.passwordBasedMac, parameters); }
         }
 
-        public virtual IStreamCalculator CreateCalculator()
+        public virtual IStreamCalculator<IBlockResult> CreateCalculator()
         {
             IMac mac = MacUtilities.GetMac(parameters.Mac.Algorithm);
             mac.Init(new KeyParameter(key));
@@ -83,6 +83,15 @@ namespace Org.BouncyCastle.Crmf
             signature.CopyTo(sig, sigOff);
             return signature.Length;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Collect(Span<byte> destination)
+        {
+            byte[] result = Collect();
+            result.CopyTo(destination);
+            return result.Length;
+        }
+#endif
     }
 
     public class PKMacBuilder
@@ -215,10 +224,7 @@ namespace Org.BouncyCastle.Crmf
 
             byte[] salt = new byte[saltLength];
 
-            if (random == null)
-            {
-                this.random = new SecureRandom();
-            }
+            this.random = CryptoServicesRegistrar.GetSecureRandom(random);
 
             random.NextBytes(salt);
 
diff --git a/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs b/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
index 50c2ea65c..eed66b083 100644
--- a/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
+++ b/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
@@ -38,7 +38,7 @@ namespace Org.BouncyCastle.Crmf
         {
             IMacFactory fact = generator.Build(password);
 
-            IStreamCalculator calc = fact.CreateCalculator();
+            IStreamCalculator<IBlockResult> calc = fact.CreateCalculator();
             byte[] d = _pubKeyInfo.GetDerEncoded();
             calc.Stream.Write(d, 0, d.Length);
             calc.Stream.Flush();
@@ -46,7 +46,7 @@ namespace Org.BouncyCastle.Crmf
 
             this._publicKeyMAC = new PKMacValue(
                 (AlgorithmIdentifier)fact.AlgorithmDetails,
-                new DerBitString(((IBlockResult)calc.GetResult()).Collect()));
+                new DerBitString(calc.GetResult().Collect()));
 
             return this;
         }
@@ -60,7 +60,7 @@ namespace Org.BouncyCastle.Crmf
 
             PopoSigningKeyInput popo;
 
-            IStreamCalculator calc = signer.CreateCalculator();
+            IStreamCalculator<IBlockResult> calc = signer.CreateCalculator();
             using (Stream sigStream = calc.Stream)
             {
                 if (_certRequest != null)
@@ -80,7 +80,7 @@ namespace Org.BouncyCastle.Crmf
                 }
             }
 
-            var signature = ((IBlockResult)calc.GetResult()).Collect();
+            var signature = calc.GetResult().Collect();
 
             return new PopoSigningKey(popo, (AlgorithmIdentifier)signer.AlgorithmDetails, new DerBitString(signature));
         }
diff --git a/crypto/src/crypto/AesUtilities.cs b/crypto/src/crypto/AesUtilities.cs
index 7cabf340c..1ec255c09 100644
--- a/crypto/src/crypto/AesUtilities.cs
+++ b/crypto/src/crypto/AesUtilities.cs
@@ -7,11 +7,17 @@ namespace Org.BouncyCastle.Crypto
         public static IBlockCipher CreateEngine()
         {
 #if NETCOREAPP3_0_OR_GREATER
-            if (AesX86Engine.IsSupported)
-                return new AesX86Engine();
+            if (AesEngine_X86.IsSupported)
+                return new AesEngine_X86();
 #endif
 
             return new AesEngine();
         }
+
+#if NETCOREAPP3_0_OR_GREATER
+        public static bool IsHardwareAccelerated => AesEngine_X86.IsSupported;
+#else
+        public static bool IsHardwareAccelerated => false;
+#endif
     }
 }
diff --git a/crypto/src/crypto/BufferedAeadBlockCipher.cs b/crypto/src/crypto/BufferedAeadBlockCipher.cs
index 7ba41090f..bf453feea 100644
--- a/crypto/src/crypto/BufferedAeadBlockCipher.cs
+++ b/crypto/src/crypto/BufferedAeadBlockCipher.cs
@@ -97,12 +97,9 @@ namespace Org.BouncyCastle.Crypto
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
-		{
-			return cipher.ProcessByte(input, output, outOff);
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            return cipher.ProcessByte(input, output, outOff);
 		}
 
 		public override byte[] ProcessByte(
@@ -124,7 +121,14 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		public override byte[] ProcessBytes(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            return cipher.ProcessByte(input, output);
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
@@ -172,7 +176,14 @@ namespace Org.BouncyCastle.Crypto
 			return cipher.ProcessBytes(input, inOff, length, output, outOff);
 		}
 
-		public override byte[] DoFinal()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            return cipher.ProcessBytes(input, output);
+        }
+#endif
+
+        public override byte[] DoFinal()
 		{
             byte[] outBytes = new byte[GetOutputSize(0)];
 
@@ -235,11 +246,25 @@ namespace Org.BouncyCastle.Crypto
 			return cipher.DoFinal(output, outOff);
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+		{
+            return cipher.DoFinal(output);
+        }
+
+        public override int DoFinal(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int len = cipher.ProcessBytes(input, output);
+            len += cipher.DoFinal(output[len..]);
+            return len;
+        }
+#endif
+
+        /**
 		* Reset the buffer and cipher. After resetting the object is in the same
 		* state as it was after the last init (if there was one).
 		*/
-		public override void Reset()
+        public override void Reset()
 		{
 			cipher.Reset();
 		}
diff --git a/crypto/src/crypto/BufferedAeadCipher.cs b/crypto/src/crypto/BufferedAeadCipher.cs
index c689c1eab..fb3408e12 100644
--- a/crypto/src/crypto/BufferedAeadCipher.cs
+++ b/crypto/src/crypto/BufferedAeadCipher.cs
@@ -96,10 +96,7 @@ namespace Org.BouncyCastle.Crypto
         * @exception DataLengthException if there isn't enough space in out.
         * @exception InvalidOperationException if the cipher isn't initialised.
         */
-        public override int ProcessByte(
-            byte input,
-            byte[] output,
-            int outOff)
+        public override int ProcessByte(byte input, byte[] output, int outOff)
         {
             return cipher.ProcessByte(input, output, outOff);
         }
@@ -123,6 +120,13 @@ namespace Org.BouncyCastle.Crypto
             return outBytes;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            return cipher.ProcessByte(input, output);
+        }
+#endif
+
         public override byte[] ProcessBytes(
             byte[] input,
             int inOff,
@@ -171,6 +175,13 @@ namespace Org.BouncyCastle.Crypto
             return cipher.ProcessBytes(input, inOff, length, output, outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            return cipher.ProcessBytes(input, output);
+        }
+#endif
+
         public override byte[] DoFinal()
         {
             byte[] outBytes = new byte[GetOutputSize(0)];
@@ -234,6 +245,20 @@ namespace Org.BouncyCastle.Crypto
             return cipher.DoFinal(output, outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            return cipher.DoFinal(output);
+        }
+
+        public override int DoFinal(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int len = cipher.ProcessBytes(input, output);
+            len += cipher.DoFinal(output[len..]);
+            return len;
+        }
+#endif
+
         /**
         * Reset the buffer and cipher. After resetting the object is in the same
         * state as it was after the last init (if there was one).
diff --git a/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs b/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs
index bf00f3ece..be933a028 100644
--- a/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs
+++ b/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs
@@ -1,7 +1,4 @@
 using System;
-using System.Diagnostics;
-
-using Org.BouncyCastle.Crypto.Engines;
 
 namespace Org.BouncyCastle.Crypto
 {
@@ -93,7 +90,27 @@ namespace Org.BouncyCastle.Crypto
 			return null;
 		}
 
-		public override byte[] ProcessBytes(
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            if (bufOff >= buffer.Length)
+                throw new DataLengthException("attempt to process message too long for cipher");
+
+            buffer[bufOff++] = input;
+            return 0;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            if (bufOff >= buffer.Length)
+                throw new DataLengthException("attempt to process message too long for cipher");
+
+            buffer[bufOff++] = input;
+            return 0;
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
@@ -111,7 +128,18 @@ namespace Org.BouncyCastle.Crypto
 			return null;
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			Check.DataLength(input, buffer.Length - bufOff, "attempt to process message too long for cipher");
+
+			input.CopyTo(buffer.AsSpan(bufOff));
+            bufOff += input.Length;
+            return 0;
+        }
+#endif
+
+        /**
         * process the contents of the buffer using the underlying
         * cipher.
         *
@@ -139,7 +167,24 @@ namespace Org.BouncyCastle.Crypto
 			return DoFinal();
 		}
 
-		/// <summary>Reset the buffer</summary>
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+		{
+			int result = 0;
+			if (bufOff > 0)
+			{
+				byte[] outBytes = cipher.ProcessBlock(buffer, 0, bufOff);
+				outBytes.CopyTo(output);
+				result = outBytes.Length;
+            }
+
+            Reset();
+
+            return result;
+        }
+#endif
+
+        /// <summary>Reset the buffer</summary>
         public override void Reset()
         {
 			if (buffer != null)
diff --git a/crypto/src/crypto/BufferedBlockCipher.cs b/crypto/src/crypto/BufferedBlockCipher.cs
index c87d2daf9..788ab138b 100644
--- a/crypto/src/crypto/BufferedBlockCipher.cs
+++ b/crypto/src/crypto/BufferedBlockCipher.cs
@@ -1,6 +1,6 @@
 using System;
-using System.Diagnostics;
 
+using Org.BouncyCastle.Crypto.Modes;
 using Org.BouncyCastle.Crypto.Parameters;
 
 namespace Org.BouncyCastle.Crypto
@@ -17,10 +17,10 @@ namespace Org.BouncyCastle.Crypto
 	public class BufferedBlockCipher
 		: BufferedCipherBase
 	{
-		internal byte[]			buf;
-		internal int			bufOff;
-		internal bool			forEncryption;
-		internal IBlockCipher	cipher;
+        internal byte[] buf;
+		internal int bufOff;
+		internal bool forEncryption;
+		internal IBlockCipherMode m_cipherMode;
 
 		/**
 		* constructor for subclasses
@@ -29,26 +29,30 @@ namespace Org.BouncyCastle.Crypto
 		{
 		}
 
-		/**
+        public BufferedBlockCipher(IBlockCipher cipher)
+			: this(EcbBlockCipher.GetBlockCipherMode(cipher))
+        {
+        }
+
+        /**
 		* Create a buffered block cipher without padding.
 		*
 		* @param cipher the underlying block cipher this buffering object wraps.
 		* false otherwise.
 		*/
-		public BufferedBlockCipher(
-			IBlockCipher cipher)
+        public BufferedBlockCipher(IBlockCipherMode cipherMode)
 		{
-			if (cipher == null)
-				throw new ArgumentNullException("cipher");
+			if (cipherMode == null)
+				throw new ArgumentNullException(nameof(cipherMode));
 
-			this.cipher = cipher;
-			buf = new byte[cipher.GetBlockSize()];
+            m_cipherMode = cipherMode;
+			buf = new byte[cipherMode.GetBlockSize()];
 			bufOff = 0;
 		}
 
 		public override string AlgorithmName
 		{
-			get { return cipher.AlgorithmName; }
+			get { return m_cipherMode.AlgorithmName; }
 		}
 
 		/**
@@ -61,19 +65,18 @@ namespace Org.BouncyCastle.Crypto
 		* inappropriate.
 		*/
 		// Note: This doubles as the Init in the event that this cipher is being used as an IWrapper
-		public override void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+		public override void Init(bool forEncryption, ICipherParameters parameters)
 		{
 			this.forEncryption = forEncryption;
 
-            ParametersWithRandom pwr = parameters as ParametersWithRandom;
-            if (pwr != null)
-                parameters = pwr.Parameters;
+			if (parameters is ParametersWithRandom withRandom)
+			{
+				parameters = withRandom.Parameters;
+			}
 
             Reset();
 
-			cipher.Init(forEncryption, parameters);
+			m_cipherMode.Init(forEncryption, parameters);
 		}
 
 		/**
@@ -83,7 +86,7 @@ namespace Org.BouncyCastle.Crypto
 		*/
 		public override int GetBlockSize()
 		{
-			return cipher.GetBlockSize();
+			return m_cipherMode.GetBlockSize();
 		}
 
 		/**
@@ -94,8 +97,7 @@ namespace Org.BouncyCastle.Crypto
 		* @return the space required to accommodate a call to update
 		* with len bytes of input.
 		*/
-		public override int GetUpdateOutputSize(
-			int length)
+		public override int GetUpdateOutputSize(int length)
 		{
 			int total = length + bufOff;
 			int leftOver = total % buf.Length;
@@ -110,8 +112,7 @@ namespace Org.BouncyCastle.Crypto
 		* @return the space required to accommodate a call to update and doFinal
 		* with len bytes of input.
 		*/
-		public override int GetOutputSize(
-			int length)
+		public override int GetOutputSize(int length)
 		{
 			// Note: Can assume IsPartialBlockOkay is true for purposes of this calculation
 			return length + bufOff;
@@ -127,10 +128,7 @@ namespace Org.BouncyCastle.Crypto
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
+		public override int ProcessByte(byte input, byte[] output, int outOff)
 		{
 			buf[bufOff++] = input;
 
@@ -140,14 +138,13 @@ namespace Org.BouncyCastle.Crypto
 					throw new DataLengthException("output buffer too short");
 
 				bufOff = 0;
-				return cipher.ProcessBlock(buf, 0, output, outOff);
+				return m_cipherMode.ProcessBlock(buf, 0, output, outOff);
 			}
 
 			return 0;
 		}
 
-		public override byte[] ProcessByte(
-			byte input)
+		public override byte[] ProcessByte(byte input)
 		{
 			int outLength = GetUpdateOutputSize(1);
 
@@ -165,13 +162,27 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		public override byte[] ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length)
-		{
-			if (input == null)
-				throw new ArgumentNullException("input");
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            buf[bufOff++] = input;
+
+            if (bufOff == buf.Length)
+            {
+				Check.OutputLength(output, buf.Length, "output buffer too short");
+
+                bufOff = 0;
+                return m_cipherMode.ProcessBlock(buf, output);
+            }
+
+            return 0;
+        }
+#endif
+
+        public override byte[] ProcessBytes(byte[] input, int inOff, int length)
+        {
+            if (input == null)
+				throw new ArgumentNullException(nameof(input));
 			if (length < 1)
 				return null;
 
@@ -191,7 +202,7 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		/**
+        /**
 		* process an array of bytes, producing output if necessary.
 		*
 		* @param in the input byte array.
@@ -203,14 +214,9 @@ namespace Org.BouncyCastle.Crypto
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length,
-			byte[]	output,
-			int		outOff)
-		{
-			if (length < 1)
+        public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
+        {
+            if (length < 1)
 			{
 				if (length < 0)
 					throw new ArgumentException("Can't have a negative input length!");
@@ -228,31 +234,60 @@ namespace Org.BouncyCastle.Crypto
 
             int resultLen = 0;
 			int gapLen = buf.Length - bufOff;
-			if (length > gapLen)
+			if (length >= gapLen)
 			{
 				Array.Copy(input, inOff, buf, bufOff, gapLen);
-				resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
+				resultLen = m_cipherMode.ProcessBlock(buf, 0, output, outOff);
 				bufOff = 0;
 				length -= gapLen;
 				inOff += gapLen;
-				while (length > buf.Length)
+				while (length >= buf.Length)
 				{
-					resultLen += cipher.ProcessBlock(input, inOff, output, outOff + resultLen);
+					resultLen += m_cipherMode.ProcessBlock(input, inOff, output, outOff + resultLen);
 					length -= blockSize;
 					inOff += blockSize;
 				}
 			}
 			Array.Copy(input, inOff, buf, bufOff, length);
 			bufOff += length;
-			if (bufOff == buf.Length)
-			{
-				resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen);
-				bufOff = 0;
-			}
 			return resultLen;
 		}
 
-		public override byte[] DoFinal()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (input.IsEmpty)
+                return 0;
+
+            int blockSize = GetBlockSize();
+            int outLength = GetUpdateOutputSize(input.Length);
+
+            if (outLength > 0)
+            {
+                Check.OutputLength(output, outLength, "output buffer too short");
+            }
+
+            int resultLen = 0;
+            int gapLen = buf.Length - bufOff;
+            if (input.Length >= gapLen)
+            {
+				input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+                resultLen = m_cipherMode.ProcessBlock(buf, output);
+                bufOff = 0;
+				input = input[gapLen..];
+                while (input.Length >= buf.Length)
+                {
+                    resultLen += m_cipherMode.ProcessBlock(input, output[resultLen..]);
+					input = input[blockSize..];
+                }
+            }
+            input.CopyTo(buf.AsSpan(bufOff));
+            bufOff += input.Length;
+            return resultLen;
+        }
+#endif
+
+        public override byte[] DoFinal()
 		{
 			byte[] outBytes = EmptyBuffer;
 
@@ -277,13 +312,10 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		public override byte[] DoFinal(
-			byte[]	input,
-			int		inOff,
-			int		inLen)
-		{
-			if (input == null)
-				throw new ArgumentNullException("input");
+        public override byte[] DoFinal(byte[] input, int inOff, int inLen)
+        {
+            if (input == null)
+				throw new ArgumentNullException(nameof(input));
 
 			int length = GetOutputSize(inLen);
 
@@ -314,7 +346,7 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		/**
+        /**
 		* Process the last block in the buffer.
 		*
 		* @param out the array the block currently being held is copied into.
@@ -328,19 +360,17 @@ namespace Org.BouncyCastle.Crypto
 		* @exception DataLengthException if the input is not block size
 		* aligned.
 		*/
-		public override int DoFinal(
-			byte[]	output,
-			int		outOff)
-		{
-			try
+        public override int DoFinal(byte[] output, int outOff)
+        {
+            try
 			{
 				if (bufOff != 0)
 				{
-                    Check.DataLength(!cipher.IsPartialBlockOkay, "data not block size aligned");
+                    Check.DataLength(!m_cipherMode.IsPartialBlockOkay, "data not block size aligned");
                     Check.OutputLength(output, outOff, bufOff, "output buffer too short for DoFinal()");
 
                     // NB: Can't copy directly, or we may write too much output
-					cipher.ProcessBlock(buf, 0, buf, 0);
+                    m_cipherMode.ProcessBlock(buf, 0, buf, 0);
 					Array.Copy(buf, 0, output, outOff, bufOff);
 				}
 
@@ -352,7 +382,31 @@ namespace Org.BouncyCastle.Crypto
 			}
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+		{
+            try
+            {
+                if (bufOff != 0)
+                {
+                    Check.DataLength(!m_cipherMode.IsPartialBlockOkay, "data not block size aligned");
+                    Check.OutputLength(output, bufOff, "output buffer too short for DoFinal()");
+
+                    // NB: Can't copy directly, or we may write too much output
+                    m_cipherMode.ProcessBlock(buf, buf);
+					buf.AsSpan(0, bufOff).CopyTo(output);
+                }
+
+                return bufOff;
+            }
+            finally
+            {
+                Reset();
+            }
+        }
+#endif
+
+        /**
 		* Reset the buffer and cipher. After resetting the object is in the same
 		* state as it was after the last init (if there was one).
 		*/
@@ -361,7 +415,7 @@ namespace Org.BouncyCastle.Crypto
 			Array.Clear(buf, 0, buf.Length);
 			bufOff = 0;
 
-			cipher.Reset();
+            m_cipherMode.Reset();
 		}
 	}
 }
diff --git a/crypto/src/crypto/BufferedCipherBase.cs b/crypto/src/crypto/BufferedCipherBase.cs
index 9d8610211..2e9026b14 100644
--- a/crypto/src/crypto/BufferedCipherBase.cs
+++ b/crypto/src/crypto/BufferedCipherBase.cs
@@ -32,7 +32,11 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes.Length;
 		}
 
-		public virtual byte[] ProcessBytes(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public abstract int ProcessByte(byte input, Span<byte> output);
+#endif
+
+        public virtual byte[] ProcessBytes(
 			byte[] input)
 		{
 			return ProcessBytes(input, 0, input.Length);
@@ -64,6 +68,10 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes.Length;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public abstract int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
+
 		public abstract byte[] DoFinal();
 
 		public virtual byte[] DoFinal(
@@ -108,6 +116,17 @@ namespace Org.BouncyCastle.Crypto
 			return len;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public abstract int DoFinal(Span<byte> output);
+
+		public virtual int DoFinal(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			int len = ProcessBytes(input, output);
+			len += DoFinal(output[len..]);
+			return len;
+		}
+#endif
+
 		public abstract void Reset();
 	}
 }
diff --git a/crypto/src/crypto/BufferedIesCipher.cs b/crypto/src/crypto/BufferedIesCipher.cs
index b0330e416..6497f7204 100644
--- a/crypto/src/crypto/BufferedIesCipher.cs
+++ b/crypto/src/crypto/BufferedIesCipher.cs
@@ -2,8 +2,6 @@ using System;
 using System.IO;
 
 using Org.BouncyCastle.Crypto.Engines;
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto
 {
@@ -62,14 +60,27 @@ namespace Org.BouncyCastle.Crypto
 			return 0;
 		}
 
-		public override byte[] ProcessByte(
-			byte input)
+		public override byte[] ProcessByte(byte input)
 		{
 			buffer.WriteByte(input);
 			return null;
 		}
 
-		public override byte[] ProcessBytes(
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            buffer.WriteByte(input);
+            return 0;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            buffer.WriteByte(input);
+            return 0;
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
@@ -87,7 +98,15 @@ namespace Org.BouncyCastle.Crypto
 			return null;
 		}
 
-		public override byte[] DoFinal()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			buffer.Write(input);
+			return 0;
+		}
+#endif
+
+        public override byte[] DoFinal()
 		{
 			byte[] buf = buffer.ToArray();
 
@@ -105,7 +124,20 @@ namespace Org.BouncyCastle.Crypto
 			return DoFinal();
 		}
 
-		public override void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+		{
+            byte[] buf = buffer.ToArray();
+
+            Reset();
+
+            byte[] block = engine.ProcessBlock(buf, 0, buf.Length);
+			block.CopyTo(output);
+			return block.Length;
+        }
+#endif
+
+        public override void Reset()
 		{
 			buffer.SetLength(0);
 		}
diff --git a/crypto/src/crypto/BufferedStreamCipher.cs b/crypto/src/crypto/BufferedStreamCipher.cs
index 2d4987bba..8ee41c1e5 100644
--- a/crypto/src/crypto/BufferedStreamCipher.cs
+++ b/crypto/src/crypto/BufferedStreamCipher.cs
@@ -7,32 +7,29 @@ namespace Org.BouncyCastle.Crypto
 	public class BufferedStreamCipher
 		: BufferedCipherBase
 	{
-		private readonly IStreamCipher cipher;
+		private readonly IStreamCipher m_cipher;
 
-		public BufferedStreamCipher(
-			IStreamCipher cipher)
+		public BufferedStreamCipher(IStreamCipher cipher)
 		{
 			if (cipher == null)
 				throw new ArgumentNullException("cipher");
 
-			this.cipher = cipher;
+			this.m_cipher = cipher;
 		}
 
 		public override string AlgorithmName
 		{
-			get { return cipher.AlgorithmName; }
+			get { return m_cipher.AlgorithmName; }
 		}
 
-		public override void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
-		{
-			if (parameters is ParametersWithRandom)
+        public override void Init(bool forEncryption, ICipherParameters parameters)
+        {
+            if (parameters is ParametersWithRandom withRandom)
 			{
-				parameters = ((ParametersWithRandom) parameters).Parameters;
+				parameters = withRandom.Parameters;
 			}
 
-			cipher.Init(forEncryption, parameters);
+			m_cipher.Init(forEncryption, parameters);
 		}
 
 		public override int GetBlockSize()
@@ -40,92 +37,101 @@ namespace Org.BouncyCastle.Crypto
 			return 0;
 		}
 
-		public override int GetOutputSize(
-			int inputLen)
+		public override int GetOutputSize(int inputLen)
 		{
 			return inputLen;
 		}
 
-		public override int GetUpdateOutputSize(
-			int inputLen)
+		public override int GetUpdateOutputSize(int inputLen)
 		{
 			return inputLen;
 		}
 
-		public override byte[] ProcessByte(
-			byte input)
+		public override byte[] ProcessByte(byte input)
 		{
-			return new byte[]{ cipher.ReturnByte(input) };
+			return new byte[]{ m_cipher.ReturnByte(input) };
 		}
 
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
-		{
-			if (outOff >= output.Length)
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            if (outOff >= output.Length)
 				throw new DataLengthException("output buffer too short");
 
-			output[outOff] = cipher.ReturnByte(input);
+			output[outOff] = m_cipher.ReturnByte(input);
 			return 1;
 		}
 
-		public override byte[] ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length)
-		{
-			if (length < 1)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            output[0] = m_cipher.ReturnByte(input);
+            return 1;
+        }
+#endif
+
+        public override byte[] ProcessBytes(byte[] input, int inOff, int length)
+        {
+            if (length < 1)
 				return null;
 
 			byte[] output = new byte[length];
-			cipher.ProcessBytes(input, inOff, length, output, 0);
+			m_cipher.ProcessBytes(input, inOff, length, output, 0);
 			return output;
 		}
 
-		public override int ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length,
-			byte[]	output,
-			int		outOff)
-		{
-			if (length < 1)
+        public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
+        {
+            if (length < 1)
 				return 0;
 
-			if (length > 0)
-			{
-				cipher.ProcessBytes(input, inOff, length, output, outOff);
-			}
-
+			m_cipher.ProcessBytes(input, inOff, length, output, outOff);
 			return length;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			m_cipher.ProcessBytes(input, output);
+			return input.Length;
+		}
+#endif
+
 		public override byte[] DoFinal()
 		{
-			Reset();
+			m_cipher.Reset();
 
 			return EmptyBuffer;
 		}
 
-		public override byte[] DoFinal(
-			byte[]	input,
-			int		inOff,
-			int		length)
+		public override byte[] DoFinal(byte[] input, int inOff, int length)
 		{
 			if (length < 1)
 				return EmptyBuffer;
 
-			byte[] output = ProcessBytes(input, inOff, length);
-
-			Reset();
+            byte[] output = new byte[length];
+            m_cipher.ProcessBytes(input, inOff, length, output, 0);
+            m_cipher.Reset();
+            return output;
+		}
 
-			return output;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+		{
+			m_cipher.Reset();
+			return 0;
 		}
 
-		public override void Reset()
+        public override int DoFinal(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            m_cipher.ProcessBytes(input, output);
+            m_cipher.Reset();
+            return input.Length;
+        }
+#endif
+
+        public override void Reset()
 		{
-			cipher.Reset();
+			m_cipher.Reset();
 		}
 	}
 }
diff --git a/crypto/src/crypto/CipherKeyGenerator.cs b/crypto/src/crypto/CipherKeyGenerator.cs
index d8d9b29b5..2d5d8c2e0 100644
--- a/crypto/src/crypto/CipherKeyGenerator.cs
+++ b/crypto/src/crypto/CipherKeyGenerator.cs
@@ -37,19 +37,17 @@ namespace Org.BouncyCastle.Crypto
 		 *
 		 * @param param the parameters to be used for key generation
 		 */
-		public void Init(
-			KeyGenerationParameters parameters)
+		public void Init(KeyGenerationParameters parameters)
 		{
 			if (parameters == null)
-				throw new ArgumentNullException("parameters");
+				throw new ArgumentNullException(nameof(parameters));
 
 			this.uninitialised = false;
 
-			engineInit(parameters);
+			EngineInit(parameters);
 		}
 
-		protected virtual void engineInit(
-			KeyGenerationParameters parameters)
+		protected virtual void EngineInit(KeyGenerationParameters parameters)
 		{
 			this.random = parameters.Random;
 			this.strength = (parameters.Strength + 7) / 8;
@@ -69,13 +67,13 @@ namespace Org.BouncyCastle.Crypto
 
 				uninitialised = false;
 
-				engineInit(new KeyGenerationParameters(new SecureRandom(), defaultStrength));
+				EngineInit(new KeyGenerationParameters(CryptoServicesRegistrar.GetSecureRandom(), defaultStrength));
 			}
 
-			return engineGenerateKey();
+			return EngineGenerateKey();
 		}
 
-        protected virtual byte[] engineGenerateKey()
+        protected virtual byte[] EngineGenerateKey()
 		{
             return SecureRandom.GetNextBytes(random, strength);
 		}
diff --git a/crypto/src/crypto/CryptoServicesRegistrar.cs b/crypto/src/crypto/CryptoServicesRegistrar.cs
new file mode 100644
index 000000000..33bf47386
--- /dev/null
+++ b/crypto/src/crypto/CryptoServicesRegistrar.cs
@@ -0,0 +1,17 @@
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Crypto
+{
+    public static class CryptoServicesRegistrar
+    {
+        public static SecureRandom GetSecureRandom()
+        {
+            return new SecureRandom();
+        }
+
+        public static SecureRandom GetSecureRandom(SecureRandom secureRandom)
+        {
+            return secureRandom ?? new SecureRandom();
+        }
+    }
+}
diff --git a/crypto/src/crypto/IBlockCipher.cs b/crypto/src/crypto/IBlockCipher.cs
index a3ad6d6e5..36f10d3e8 100644
--- a/crypto/src/crypto/IBlockCipher.cs
+++ b/crypto/src/crypto/IBlockCipher.cs
@@ -16,9 +16,6 @@ namespace Org.BouncyCastle.Crypto
 		/// <returns>The block size for this cipher, in bytes.</returns>
 		int GetBlockSize();
 
-		/// <summary>Indicates whether this cipher can handle partial blocks.</summary>
-		bool IsPartialBlockOkay { get; }
-
 		/// <summary>Process a block.</summary>
 		/// <param name="inBuf">The input buffer.</param>
 		/// <param name="inOff">The offset into <paramref>inBuf</paramref> that the input block begins.</param>
@@ -28,9 +25,14 @@ namespace Org.BouncyCastle.Crypto
 		/// <returns>The number of bytes processed and produced.</returns>
 		int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff);
 
-		/// <summary>
-		/// Reset the cipher to the same state as it was after the last init (if there was one).
-		/// </summary>
-        void Reset();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+
+		/// <summary>Process a block.</summary>
+		/// <param name="input">The input block as a span.</param>
+		/// <param name="output">The output span.</param>
+		/// <exception cref="DataLengthException">If input block is wrong size, or output span too small.</exception>
+		/// <returns>The number of bytes processed and produced.</returns>
+		int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/IBlockResult.cs b/crypto/src/crypto/IBlockResult.cs
index 0f054fedc..3ed96a7c1 100644
--- a/crypto/src/crypto/IBlockResult.cs
+++ b/crypto/src/crypto/IBlockResult.cs
@@ -1,4 +1,5 @@
-
+using System;
+
 namespace Org.BouncyCastle.Crypto
 {
     /// <summary>
@@ -20,5 +21,14 @@ namespace Org.BouncyCastle.Crypto
         /// <param name="destination">The byte array to copy the result into.</param>
         /// <param name="offset">The offset into destination to start copying the result at.</param>
         int Collect(byte[] destination, int offset);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>
+        /// Store the final result of the operation by copying it into the destination span.
+        /// </summary>
+        /// <returns>The number of bytes copied into destination.</returns>
+        /// <param name="destination">The span to copy the result into.</param>
+        int Collect(Span<byte> destination);
+#endif
     }
 }
diff --git a/crypto/src/crypto/IBufferedCipher.cs b/crypto/src/crypto/IBufferedCipher.cs
index 69dec9596..4471c42c9 100644
--- a/crypto/src/crypto/IBufferedCipher.cs
+++ b/crypto/src/crypto/IBufferedCipher.cs
@@ -23,11 +23,19 @@ namespace Org.BouncyCastle.Crypto
 		byte[] ProcessByte(byte input);
 		int ProcessByte(byte input, byte[] output, int outOff);
 
-		byte[] ProcessBytes(byte[] input);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int ProcessByte(byte input, Span<byte> output);
+#endif
+
+        byte[] ProcessBytes(byte[] input);
 		byte[] ProcessBytes(byte[] input, int inOff, int length);
 		int ProcessBytes(byte[] input, byte[] output, int outOff);
 		int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
+
 		byte[] DoFinal();
 		byte[] DoFinal(byte[] input);
 		byte[] DoFinal(byte[] input, int inOff, int length);
@@ -35,6 +43,11 @@ namespace Org.BouncyCastle.Crypto
 		int DoFinal(byte[] input, byte[] output, int outOff);
 		int DoFinal(byte[] input, int inOff, int length, byte[] output, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		int DoFinal(Span<byte> output);
+		int DoFinal(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
+
 		/// <summary>
 		/// Reset the cipher. After resetting the cipher is in the same state
 		/// as it was after the last init (if there was one).
diff --git a/crypto/src/crypto/IDSA.cs b/crypto/src/crypto/IDSA.cs
index 7d1fd5197..459914f1f 100644
--- a/crypto/src/crypto/IDSA.cs
+++ b/crypto/src/crypto/IDSA.cs
@@ -4,38 +4,29 @@ using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Crypto
 {
-    /**
-     * interface for classes implementing the Digital Signature Algorithm
-     */
+    /// <summary>Interface for classes implementing the Digital Signature Algorithm</summary>
     public interface IDsa
     {
+        /// <summary>The algorithm name.</summary>
 		string AlgorithmName { get; }
 
-		/**
-         * initialise the signer for signature generation or signature
-         * verification.
-         *
-         * @param forSigning true if we are generating a signature, false
-         * otherwise.
-         * @param param key parameters for signature generation.
-         */
+        /// <summary>Initialise the signer for signature generation or signature verification.</summary>
+        /// <param name="forSigning">true if we are generating a signature, false otherwise.</param>
+        /// <param name="parameters">key parameters for signature generation.</param>
         void Init(bool forSigning, ICipherParameters parameters);
 
-        /**
-         * sign the passed in message (usually the output of a hash function).
-         *
-         * @param message the message to be signed.
-         * @return two big integers representing the r and s values respectively.
-         */
+        /// <summary>Sign the passed in message (usually the output of a hash function).</summary>
+        /// <param name="message">the message to be signed.</param>
+        /// <returns>two big integers representing the r and s values respectively.</returns>
         BigInteger[] GenerateSignature(byte[] message);
 
-        /**
-         * verify the message message against the signature values r and s.
-         *
-         * @param message the message that was supposed to have been signed.
-         * @param r the r signature value.
-         * @param s the s signature value.
-         */
+        /// <summary>The order of the group that the r, s values in signatures belong to.</summary>
+        BigInteger Order { get; }
+
+        /// <summary>Verify the message message against the signature values r and s.</summary>
+        /// <param name="message">the message that was supposed to have been signed.</param>
+        /// <param name="r">the r signature value.</param>
+        /// <param name="s">the s signature value.</param>
         bool VerifySignature(byte[] message, BigInteger  r, BigInteger s);
     }
 }
diff --git a/crypto/src/crypto/IDerivationFunction.cs b/crypto/src/crypto/IDerivationFunction.cs
index 7f289f790..9c0228ab0 100644
--- a/crypto/src/crypto/IDerivationFunction.cs
+++ b/crypto/src/crypto/IDerivationFunction.cs
@@ -12,13 +12,12 @@ namespace Org.BouncyCastle.Crypto
         /**
          * return the message digest used as the basis for the function
          */
-        IDigest Digest
-        {
-            get;
-        }
+        IDigest Digest { get; }
 
         int GenerateBytes(byte[] output, int outOff, int length);
-        //throws DataLengthException, ArgumentException;
-    }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int GenerateBytes(Span<byte> output);
+#endif
+    }
 }
diff --git a/crypto/src/crypto/IDigest.cs b/crypto/src/crypto/IDigest.cs
index 6769dcc42..36caf3728 100644
--- a/crypto/src/crypto/IDigest.cs
+++ b/crypto/src/crypto/IDigest.cs
@@ -2,60 +2,52 @@ using System;
 
 namespace Org.BouncyCastle.Crypto
 {
-    /**
-     * interface that a message digest conforms to.
-     */
+    /// <remarks>Base interface for a message digest.</remarks>
     public interface IDigest
     {
-        /**
-         * return the algorithm name
-         *
-         * @return the algorithm name
-         */
+        /// <summary>The algorithm name.</summary>
         string AlgorithmName { get; }
 
-		/**
-         * return the size, in bytes, of the digest produced by this message digest.
-         *
-         * @return the size, in bytes, of the digest produced by this message digest.
-         */
-		int GetDigestSize();
-
-		/**
-         * return the size, in bytes, of the internal buffer used by this digest.
-         *
-         * @return the size, in bytes, of the internal buffer used by this digest.
-         */
-		int GetByteLength();
-
-		/**
-         * update the message digest with a single byte.
-         *
-         * @param inByte the input byte to be entered.
-         */
+        /// <summary>Return the size, in bytes, of the digest produced by this message digest.</summary>
+        /// <returns>the size, in bytes, of the digest produced by this message digest.</returns>
+        int GetDigestSize();
+
+        /// <summary>Return the size, in bytes, of the internal buffer used by this digest.</summary>
+        /// <returns>the size, in bytes, of the internal buffer used by this digest.</returns>
+        int GetByteLength();
+
+        /// <summary>Update the message digest with a single byte.</summary>
+        /// <param name="input">the input byte to be entered.</param>
         void Update(byte input);
 
-        /**
-         * update the message digest with a block of bytes.
-         *
-         * @param input the byte array containing the data.
-         * @param inOff the offset into the byte array where the data starts.
-         * @param len the length of the data.
-         */
-        void BlockUpdate(byte[] input, int inOff, int length);
-
-        /**
-         * Close the digest, producing the final digest value. The doFinal
-         * call leaves the digest reset.
-         *
-         * @param output the array the digest is to be copied into.
-         * @param outOff the offset into the out array the digest is to start at.
-         */
+        /// <summary>Update the message digest with a block of bytes.</summary>
+        /// <param name="input">the byte array containing the data.</param>
+        /// <param name="inOff">the offset into the byte array where the data starts.</param>
+        /// <param name="inLen">the length of the data.</param>
+        void BlockUpdate(byte[] input, int inOff, int inLen);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Update the message digest with a span of bytes.</summary>
+        /// <param name="input">the span containing the data.</param>
+        void BlockUpdate(ReadOnlySpan<byte> input);
+#endif
+
+        /// <summary>Close the digest, producing the final digest value.</summary>
+        /// <remarks>This call leaves the digest reset.</remarks>
+        /// <param name="output">the byte array the digest is to be copied into.</param>
+        /// <param name="outOff">the offset into the byte array the digest is to start at.</param>
+        /// <returns>the number of bytes written</returns>
         int DoFinal(byte[] output, int outOff);
 
-        /**
-         * reset the digest back to it's initial state.
-         */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Close the digest, producing the final digest value.</summary>
+        /// <remarks>This call leaves the digest reset.</remarks>
+        /// <param name="output">the span the digest is to be copied into.</param>
+        /// <returns>the number of bytes written</returns>
+        int DoFinal(Span<byte> output);
+#endif
+
+        /// <summary>Reset the digest back to its initial state.</summary>
         void Reset();
     }
 }
diff --git a/crypto/src/crypto/IDigestFactory.cs b/crypto/src/crypto/IDigestFactory.cs
index eedac14e4..33d8f0974 100644
--- a/crypto/src/crypto/IDigestFactory.cs
+++ b/crypto/src/crypto/IDigestFactory.cs
@@ -20,6 +20,6 @@ namespace Org.BouncyCastle.Crypto
 		/// and producing the digest block.
 		/// </summary>
 		/// <returns>A calculator producing an IBlockResult with the final digest in it.</returns>
-		IStreamCalculator CreateCalculator();
+		IStreamCalculator<IBlockResult> CreateCalculator();
 	}
 }
diff --git a/crypto/src/crypto/IDsaExt.cs b/crypto/src/crypto/IDsaExt.cs
deleted file mode 100644
index 15b55787a..000000000
--- a/crypto/src/crypto/IDsaExt.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-using Org.BouncyCastle.Math;
-
-namespace Org.BouncyCastle.Crypto
-{
-    /// <summary>
-    /// An "extended" interface for classes implementing DSA-style algorithms, that provides access
-    /// to the group order.
-    /// </summary>
-    public interface IDsaExt
-        : IDsa
-    {
-        /// <summary>The order of the group that the r, s values in signatures belong to.</summary>
-        BigInteger Order { get; }
-    }
-}
diff --git a/crypto/src/crypto/IEncapsulatedSecretExtractor.cs b/crypto/src/crypto/IEncapsulatedSecretExtractor.cs
index 9115c8e7d..ab909479a 100644
--- a/crypto/src/crypto/IEncapsulatedSecretExtractor.cs
+++ b/crypto/src/crypto/IEncapsulatedSecretExtractor.cs
@@ -7,5 +7,10 @@ namespace Org.BouncyCastle.Crypto
         /// </summary>
         /// <param name="encapsulation"> the encapsulated secret.</param>
         byte[] ExtractSecret(byte[] encapsulation);
+
+        /// <summary>
+        /// The length in bytes of the encapsulation.
+        /// </summary>
+        int EncapsulationLength { get;  }
     }
 }
\ No newline at end of file
diff --git a/crypto/src/crypto/IEntropySource.cs b/crypto/src/crypto/IEntropySource.cs
index 62e3bc76c..2e088ed27 100644
--- a/crypto/src/crypto/IEntropySource.cs
+++ b/crypto/src/crypto/IEntropySource.cs
@@ -19,6 +19,10 @@ namespace Org.BouncyCastle.Crypto
 		/// <returns>The entropy bytes.</returns>
 		byte[] GetEntropy();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int GetEntropy(Span<byte> output);
+#endif
+
 		/// <summary>
 		/// Return the number of bits of entropy this source can produce.
 		/// </summary>
diff --git a/crypto/src/crypto/IMac.cs b/crypto/src/crypto/IMac.cs
index 03a86e8b6..aa539b968 100644
--- a/crypto/src/crypto/IMac.cs
+++ b/crypto/src/crypto/IMac.cs
@@ -2,68 +2,52 @@ using System;
 
 namespace Org.BouncyCastle.Crypto
 {
-    /**
-     * The base interface for implementations of message authentication codes (MACs).
-     */
+    /// <summary>The base interface for implementations of message authentication codes (MACs).</summary>
     public interface IMac
     {
-        /**
-         * Initialise the MAC.
-         *
-         * @param param the key and other data required by the MAC.
-         * @exception ArgumentException if the parameters argument is
-         * inappropriate.
-         */
+        /// <summary>Initialise the MAC.</summary>
+        /// <param name="parameters">The key or other data required by the MAC.</param>
         void Init(ICipherParameters parameters);
 
-        /**
-         * Return the name of the algorithm the MAC implements.
-         *
-         * @return the name of the algorithm the MAC implements.
-         */
+        /// <summary>The algorithm name.</summary>
         string AlgorithmName { get; }
 
-		/**
-		 * Return the block size for this MAC (in bytes).
-		 *
-		 * @return the block size for this MAC in bytes.
-		 */
-		int GetMacSize();
+        /// <summary>Return the size, in bytes, of the MAC produced by this implementation.</summary>
+        /// <returns>the size, in bytes, of the MAC produced by this implementation.</returns>
+        int GetMacSize();
 
-        /**
-         * add a single byte to the mac for processing.
-         *
-         * @param in the byte to be processed.
-         * @exception InvalidOperationException if the MAC is not initialised.
-         */
+        /// <summary>Update the MAC with a single byte.</summary>
+        /// <param name="input">the input byte to be entered.</param>
         void Update(byte input);
 
-		/**
-         * @param in the array containing the input.
-         * @param inOff the index in the array the data begins at.
-         * @param len the length of the input starting at inOff.
-         * @exception InvalidOperationException if the MAC is not initialised.
-         * @exception DataLengthException if there isn't enough data in in.
-         */
-        void BlockUpdate(byte[] input, int inOff, int len);
+        /// <summary>Update the MAC with a block of bytes.</summary>
+        /// <param name="input">the byte array containing the data.</param>
+        /// <param name="inOff">the offset into the byte array where the data starts.</param>
+        /// <param name="inLen">the length of the data.</param>
+        void BlockUpdate(byte[] input, int inOff, int inLen);
 
-		/**
-         * Compute the final stage of the MAC writing the output to the out
-         * parameter.
-         * <p>
-         * doFinal leaves the MAC in the same state it was after the last init.
-         * </p>
-         * @param out the array the MAC is to be output to.
-         * @param outOff the offset into the out buffer the output is to start at.
-         * @exception DataLengthException if there isn't enough space in out.
-         * @exception InvalidOperationException if the MAC is not initialised.
-         */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Update the MAC with a span of bytes.</summary>
+        /// <param name="input">the span containing the data.</param>
+        void BlockUpdate(ReadOnlySpan<byte> input);
+#endif
+
+        /// <summary>Perform final calculations, producing the result MAC.</summary>
+        /// <remarks>This call leaves the MAC reset.</remarks>
+        /// <param name="output">the byte array the MAC is to be copied into.</param>
+        /// <param name="outOff">the offset into the byte array the MAC is to start at.</param>
+        /// <returns>the number of bytes written</returns>
         int DoFinal(byte[] output, int outOff);
 
-		/**
-         * Reset the MAC. At the end of resetting the MAC should be in the
-         * in the same state it was after the last init (if there was one).
-         */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Perform final calculations, producing the result MAC.</summary>
+        /// <remarks>This call leaves the MAC reset.</remarks>
+        /// <param name="output">the span the MAC is to be copied into.</param>
+        /// <returns>the number of bytes written</returns>
+        int DoFinal(Span<byte> output);
+#endif
+
+        /// <summary>Reset the MAC back to its initial state.</summary>
         void Reset();
     }
 }
diff --git a/crypto/src/crypto/IMacDerivationFunction.cs b/crypto/src/crypto/IMacDerivationFunction.cs
index 7297cd854..354524e5a 100644
--- a/crypto/src/crypto/IMacDerivationFunction.cs
+++ b/crypto/src/crypto/IMacDerivationFunction.cs
@@ -1,7 +1,8 @@
 namespace Org.BouncyCastle.Crypto
 {
-    public interface IMacDerivationFunction:IDerivationFunction
+    public interface IMacDerivationFunction
+        : IDerivationFunction
     {
-        IMac GetMac();
+        IMac Mac { get; }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/IMacFactory.cs b/crypto/src/crypto/IMacFactory.cs
index 9180ef1ea..49ace39d5 100644
--- a/crypto/src/crypto/IMacFactory.cs
+++ b/crypto/src/crypto/IMacFactory.cs
@@ -13,6 +13,6 @@ namespace Org.BouncyCastle.Crypto
         /// and producing the signature block.
         /// </summary>
         /// <returns>A calculator producing an IBlockResult with a signature in it.</returns>
-        IStreamCalculator CreateCalculator();
+        IStreamCalculator<IBlockResult> CreateCalculator();
     }
 }
diff --git a/crypto/src/crypto/IRawAgreement.cs b/crypto/src/crypto/IRawAgreement.cs
index 63e664888..f8b810ec7 100644
--- a/crypto/src/crypto/IRawAgreement.cs
+++ b/crypto/src/crypto/IRawAgreement.cs
@@ -9,5 +9,9 @@ namespace Org.BouncyCastle.Crypto
         int AgreementSize { get; }
 
         void CalculateAgreement(ICipherParameters publicKey, byte[] buf, int off);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void CalculateAgreement(ICipherParameters publicKey, Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/ISignatureFactory.cs b/crypto/src/crypto/ISignatureFactory.cs
index 1186d85a6..2cda6b2cf 100644
--- a/crypto/src/crypto/ISignatureFactory.cs
+++ b/crypto/src/crypto/ISignatureFactory.cs
@@ -1,5 +1,3 @@
-using System;
-
 namespace Org.BouncyCastle.Crypto
 {
     /// <summary>
@@ -8,7 +6,7 @@ namespace Org.BouncyCastle.Crypto
     public interface ISignatureFactory
 	{
         /// <summary>The algorithm details object for this calculator.</summary>
-        object AlgorithmDetails { get ; }
+        object AlgorithmDetails { get; }
 
         /// <summary>
         /// Create a stream calculator for this signature calculator. The stream
@@ -16,8 +14,6 @@ namespace Org.BouncyCastle.Crypto
         /// and producing the signature block.
         /// </summary>
         /// <returns>A calculator producing an IBlockResult with a signature in it.</returns>
-        IStreamCalculator CreateCalculator();
+        IStreamCalculator<IBlockResult> CreateCalculator();
     }
 }
-
-
diff --git a/crypto/src/crypto/ISigner.cs b/crypto/src/crypto/ISigner.cs
index e03bbf4d3..668e5e4cd 100644
--- a/crypto/src/crypto/ISigner.cs
+++ b/crypto/src/crypto/ISigner.cs
@@ -1,50 +1,45 @@
-
 using System;
-using System.Text;
 
 namespace Org.BouncyCastle.Crypto
 {
     public interface ISigner
     {
-        /**
-         * Return the name of the algorithm the signer implements.
-         *
-         * @return the name of the algorithm the signer implements.
-         */
+        /// <summary>The algorithm name.</summary>
         string AlgorithmName { get; }
 
-		/**
-         * Initialise the signer for signing or verification.
-         *
-         * @param forSigning true if for signing, false otherwise
-         * @param param necessary parameters.
-         */
-         void Init(bool forSigning, ICipherParameters parameters);
-
-        /**
-         * update the internal digest with the byte b
-         */
+        /// <summary>Initialise the signer for signing or verification.</summary>
+        /// <param name="forSigning">true if for signing, false otherwise.</param>
+        /// <param name="parameters">necessary parameters.</param>
+        void Init(bool forSigning, ICipherParameters parameters);
+
+        /// <summary>Update the signer with a single byte.</summary>
+        /// <param name="input">the input byte to be entered.</param>
         void Update(byte input);
 
-        /**
-         * update the internal digest with the byte array in
-         */
-        void BlockUpdate(byte[] input, int inOff, int length);
+        /// <summary>Update the signer with a block of bytes.</summary>
+        /// <param name="input">the byte array containing the data.</param>
+        /// <param name="inOff">the offset into the byte array where the data starts.</param>
+        /// <param name="inLen">the length of the data.</param>
+        void BlockUpdate(byte[] input, int inOff, int inLen);
 
-        /**
-         * Generate a signature for the message we've been loaded with using
-         * the key we were initialised with.
-         */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Update the signer with a span of bytes.</summary>
+        /// <param name="input">the span containing the data.</param>
+        void BlockUpdate(ReadOnlySpan<byte> input);
+#endif
+
+        /// <summary>Generate a signature for the message we've been loaded with using the key we were initialised with.
+        /// </summary>
+        /// <returns>A byte array containing the signature for the message.</returns>
         byte[] GenerateSignature();
-        /**
-         * return true if the internal state represents the signature described
-         * in the passed in array.
-         */
+
+        /// <summary>Return true if the internal state represents the signature described in the passed in array.
+        /// </summary>
+        /// <param name="signature">an array containing the candidate signature to verify.</param>
+        /// <returns>true if the internal state represents the signature described in the passed in array.</returns>
         bool VerifySignature(byte[] signature);
 
-        /**
-         * reset the internal state
-         */
+        /// <summary>Reset the signer back to its initial state.</summary>
         void Reset();
     }
 }
diff --git a/crypto/src/crypto/IStreamCalculator.cs b/crypto/src/crypto/IStreamCalculator.cs
index 502b29d2d..2161b5f4d 100644
--- a/crypto/src/crypto/IStreamCalculator.cs
+++ b/crypto/src/crypto/IStreamCalculator.cs
@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System.IO;
 
 namespace Org.BouncyCastle.Crypto
 {
@@ -7,7 +6,7 @@ namespace Org.BouncyCastle.Crypto
     /// Base interface for cryptographic operations such as Hashes, MACs, and Signatures which reduce a stream of data
     /// to a single value.
     /// </summary>
-    public interface IStreamCalculator
+    public interface IStreamCalculator<out TResult>
     {
         /// <summary>Return a "sink" stream which only exists to update the implementing object.</summary>
         /// <returns>A stream to write to in order to update the implementing object.</returns>
@@ -18,6 +17,6 @@ namespace Org.BouncyCastle.Crypto
         /// has been closed.
         /// </summary>
         /// <returns>The result of processing the stream.</returns>
-        object GetResult();
+        TResult GetResult();
     }
 }
diff --git a/crypto/src/crypto/IStreamCipher.cs b/crypto/src/crypto/IStreamCipher.cs
index 8e575a7e5..0408b33c9 100644
--- a/crypto/src/crypto/IStreamCipher.cs
+++ b/crypto/src/crypto/IStreamCipher.cs
@@ -22,24 +22,35 @@ namespace Org.BouncyCastle.Crypto
 		/// <returns>the result of processing the input byte.</returns>
         byte ReturnByte(byte input);
 
-		/// <summary>
-		/// Process a block of bytes from <c>input</c> putting the result into <c>output</c>.
-		/// </summary>
-		/// <param name="input">The input byte array.</param>
-		/// <param name="inOff">
-		/// The offset into <c>input</c> where the data to be processed starts.
-		/// </param>
-		/// <param name="length">The number of bytes to be processed.</param>
-		/// <param name="output">The output buffer the processed bytes go into.</param>
-		/// <param name="outOff">
-		/// The offset into <c>output</c> the processed data starts at.
-		/// </param>
-		/// <exception cref="DataLengthException">If the output buffer is too small.</exception>
+        /// <summary>
+        /// Process a block of bytes from <paramref name="input"/>, putting the result into <paramref name="output"/>.
+        /// </summary>
+        /// <param name="input">The input byte array.</param>
+        /// <param name="inOff">
+        /// The offset into <c>input</c> where the data to be processed starts.
+        /// </param>
+        /// <param name="length">The number of bytes to be processed.</param>
+        /// <param name="output">The output buffer the processed bytes go into.</param>
+        /// <param name="outOff">
+        /// The offset into <c>output</c> the processed data starts at.
+        /// </param>
+        /// <exception cref="DataLengthException">If the input buffer is too small.</exception>
+        /// <exception cref="OutputLengthException">If the output buffer is too small.</exception>
         void ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff);
 
-		/// <summary>
-		/// Reset the cipher to the same state as it was after the last init (if there was one).
-		/// </summary>
-		void Reset();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>
+        /// Process a block of bytes from <paramref name="input"/>, putting the result into <paramref name="output"/>.
+        /// </summary>
+		/// <param name="input">The input span.</param>
+		/// <param name="output">The output span.</param>
+        /// <exception cref="OutputLengthException">If the output span is too small.</exception>
+        void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
+
+        /// <summary>
+        /// Reset the cipher to the same state as it was after the last init (if there was one).
+        /// </summary>
+        void Reset();
     }
 }
diff --git a/crypto/src/crypto/IVerifierFactory.cs b/crypto/src/crypto/IVerifierFactory.cs
index 707c72111..8f32119de 100644
--- a/crypto/src/crypto/IVerifierFactory.cs
+++ b/crypto/src/crypto/IVerifierFactory.cs
@@ -1,5 +1,3 @@
-using System;
-
 namespace Org.BouncyCastle.Crypto
 {
     /// <summary>
@@ -8,7 +6,7 @@ namespace Org.BouncyCastle.Crypto
     public interface IVerifierFactory
 	{
         /// <summary>The algorithm details object for this verifier.</summary>
-        object AlgorithmDetails { get ; }
+        object AlgorithmDetails { get; }
 
         /// <summary>
         /// Create a stream calculator for this verifier. The stream
@@ -16,6 +14,6 @@ namespace Org.BouncyCastle.Crypto
         /// and producing a result which can be used to verify the original signature.
         /// </summary>
         /// <returns>A calculator producing an IVerifier which can verify the signature.</returns>
-        IStreamCalculator CreateCalculator();
+        IStreamCalculator<IVerifier> CreateCalculator();
     }
 }
diff --git a/crypto/src/crypto/IWrapper.cs b/crypto/src/crypto/IWrapper.cs
index 58202b302..ffeddf3a9 100644
--- a/crypto/src/crypto/IWrapper.cs
+++ b/crypto/src/crypto/IWrapper.cs
@@ -1,7 +1,3 @@
-using System;
-
-using Org.BouncyCastle.Security;
-
 namespace Org.BouncyCastle.Crypto
 {
     public interface IWrapper
diff --git a/crypto/src/crypto/IXof.cs b/crypto/src/crypto/IXof.cs
index f76304d48..c2d53ca87 100644
--- a/crypto/src/crypto/IXof.cs
+++ b/crypto/src/crypto/IXof.cs
@@ -4,28 +4,47 @@ namespace Org.BouncyCastle.Crypto
 {
     /// <remarks>
     /// With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes.
-    /// This interface provides the extra method required to support variable output on a digest implementation.
+    /// This interface provides the extra methods required to support variable output on a digest implementation.
     /// </remarks>
     public interface IXof
         : IDigest
     {
         /// <summary>
-        /// Output the results of the final calculation for this digest to outLen number of bytes.
+        /// Output the results of the final calculation for this XOF to outLen number of bytes.
         /// </summary>
         /// <param name="output">output array to write the output bytes to.</param>
         /// <param name="outOff">offset to start writing the bytes at.</param>
         /// <param name="outLen">the number of output bytes requested.</param>
         /// <returns>the number of bytes written</returns>
-        int DoFinal(byte[] output, int outOff, int outLen);
+        int OutputFinal(byte[] output, int outOff, int outLen);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         /// <summary>
-        /// Start outputting the results of the final calculation for this digest. Unlike DoFinal, this method
-        /// will continue producing output until the Xof is explicitly reset, or signals otherwise.
+        /// Output the results of the final calculation for this XOF to fill the output span.
+        /// </summary>
+        /// <param name="output">span to fill with the output bytes.</param>
+        /// <returns>the number of bytes written</returns>
+        int OutputFinal(Span<byte> output);
+#endif
+
+        /// <summary>
+        /// Start outputting the results of the final calculation for this XOF. Unlike DoFinal, this method
+        /// will continue producing output until the XOF is explicitly reset, or signals otherwise.
         /// </summary>
         /// <param name="output">output array to write the output bytes to.</param>
         /// <param name="outOff">offset to start writing the bytes at.</param>
         /// <param name="outLen">the number of output bytes requested.</param>
         /// <returns>the number of bytes written</returns>
-        int DoOutput(byte[] output, int outOff, int outLen);
+        int Output(byte[] output, int outOff, int outLen);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>
+        /// Start outputting the results of the final calculation for this XOF. Unlike OutputFinal, this method
+        /// will continue producing output until the XOF is explicitly reset, or signals otherwise.
+        /// </summary>
+        /// <param name="output">span to fill with the output bytes.</param>
+        /// <returns>the number of bytes written</returns>
+        int Output(Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/Security.cs b/crypto/src/crypto/Security.cs
deleted file mode 100644
index 1e82e75b0..000000000
--- a/crypto/src/crypto/Security.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System;
-using System.Text;
-
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Digests;
-using Org.BouncyCastle.Crypto.Engines;
-using Org.BouncyCastle.Crypto.Generators;
-using Org.BouncyCastle.Crypto.Modes;
-using Org.BouncyCastle.Crypto.Paddings;
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities.Encoders;
-
-namespace crypto
-{
-    public class Security
-    {
-        // USAGE
-        //var key = Security.GenerateText(32);
-        //var iv = Security.GenerateText(16);
-        //var encrypted = Security.Encrypt("MY SECRET", key, iv);
-        //var decrypted = Security.Decrypt(encrypted, key, iv);
-
-        /// <summary>
-        /// Return a salted hash based on PBKDF2 for the UTF-8 encoding of the argument text.
-        /// </summary>
-        /// <param name="text">Provided key text</param>
-        /// <param name="salt">Base64 encoded string representing the salt</param>
-        /// <returns></returns>
-        public static string ComputeHash(string text, string salt)
-        {
-            byte[] data = Encoding.UTF8.GetBytes(text);
-            Sha512Digest sha = new Sha512Digest();
-            Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator(sha);
-
-            gen.Init(data, Base64.Decode(salt), 2048);
-
-            return Base64.ToBase64String(((KeyParameter)gen.GenerateDerivedParameters("AES", sha.GetDigestSize() * 8)).GetKey());
-        }
-
-        public static string Decrypt(string cipherText, string key, string iv)
-        {
-            IBufferedCipher cipher = CreateCipher(false, key, iv);
-            byte[] textAsBytes = cipher.DoFinal(Base64.Decode(cipherText));
-
-            return Encoding.UTF8.GetString(textAsBytes, 0, textAsBytes.Length);
-        }
-
-        public static string Encrypt(string plainText, string key, string iv)
-        {
-            IBufferedCipher cipher = CreateCipher(true, key, iv);
-
-            return Base64.ToBase64String(cipher.DoFinal(Encoding.UTF8.GetBytes(plainText)));
-        }
-
-        public static string GenerateText(int size)
-        {
-            byte[] textAsBytes = new byte[size];
-            SecureRandom secureRandom = SecureRandom.GetInstance("SHA256PRNG", true);
-
-            secureRandom.NextBytes(textAsBytes);
-            return Base64.ToBase64String(textAsBytes);
-        }
-
-        private static IBufferedCipher CreateCipher(bool isEncryption, string key, string iv)
-        {
-            IBufferedCipher cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(new RijndaelEngine()), new ISO10126d2Padding());
-            KeyParameter keyParam = new KeyParameter(Base64.Decode(key));
-            ICipherParameters cipherParams = (null == iv || iv.Length < 1)
-                ? (ICipherParameters)keyParam
-                : new ParametersWithIV(keyParam, Base64.Decode(iv));
-            cipher.Init(isEncryption, cipherParams);
-            return cipher;
-        }
-    }
-}
diff --git a/crypto/src/crypto/SimpleBlockResult.cs b/crypto/src/crypto/SimpleBlockResult.cs
index 6cacda63f..35432c2b3 100644
--- a/crypto/src/crypto/SimpleBlockResult.cs
+++ b/crypto/src/crypto/SimpleBlockResult.cs
@@ -49,5 +49,14 @@ namespace Org.BouncyCastle.Crypto
 
             return result.Length;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Collect(Span<byte> destination)
+        {
+            result.CopyTo(destination);
+
+            return result.Length;
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/StreamBlockCipher.cs b/crypto/src/crypto/StreamBlockCipher.cs
index ef2a8b68a..f7e76e6cf 100644
--- a/crypto/src/crypto/StreamBlockCipher.cs
+++ b/crypto/src/crypto/StreamBlockCipher.cs
@@ -1,6 +1,6 @@
 using System;
 
-using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Modes;
 
 namespace Org.BouncyCastle.Crypto
 {
@@ -11,7 +11,7 @@ namespace Org.BouncyCastle.Crypto
 	public class StreamBlockCipher
 		: IStreamCipher
 	{
-		private readonly IBlockCipher cipher;
+		private readonly IBlockCipherMode m_cipherMode;
 		private readonly byte[] oneByte = new byte[1];
 
 		/**
@@ -21,28 +21,25 @@ namespace Org.BouncyCastle.Crypto
 		 * @exception ArgumentException if the cipher has a block size other than
 		 * one.
 		 */
-		public StreamBlockCipher(
-			IBlockCipher cipher)
+		public StreamBlockCipher(IBlockCipherMode cipherMode)
 		{
-			if (cipher == null)
-				throw new ArgumentNullException("cipher");
-			if (cipher.GetBlockSize() != 1)
-				throw new ArgumentException("block cipher block size != 1.", "cipher");
+			if (cipherMode == null)
+				throw new ArgumentNullException(nameof(cipherMode));
+			if (cipherMode.GetBlockSize() != 1)
+				throw new ArgumentException("block cipher block size != 1.", nameof(cipherMode));
 
-			this.cipher = cipher;
+            m_cipherMode = cipherMode;
 		}
 
-		/**
+        /**
 		 * initialise the underlying cipher.
 		 *
 		 * @param forEncryption true if we are setting up for encryption, false otherwise.
 		 * @param param the necessary parameters for the underlying cipher to be initialised.
 		 */
-		public void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
-		{
-			cipher.Init(forEncryption, parameters);
+        public void Init(bool forEncryption, ICipherParameters parameters)
+        {
+            m_cipherMode.Init(forEncryption, parameters);
 		}
 
 		/**
@@ -52,7 +49,7 @@ namespace Org.BouncyCastle.Crypto
 		*/
 		public string AlgorithmName
 		{
-			get { return cipher.AlgorithmName; }
+			get { return m_cipherMode.AlgorithmName; }
 		}
 
 		/**
@@ -61,17 +58,16 @@ namespace Org.BouncyCastle.Crypto
 		* @param in the byte to be processed.
 		* @return the result of processing the input byte.
 		*/
-		public byte ReturnByte(
-			byte input)
+		public byte ReturnByte(byte input)
 		{
 			oneByte[0] = input;
 
-			cipher.ProcessBlock(oneByte, 0, oneByte, 0);
+            m_cipherMode.ProcessBlock(oneByte, 0, oneByte, 0);
 
 			return oneByte[0];
 		}
 
-		/**
+        /**
 		* process a block of bytes from in putting the result into out.
 		*
 		* @param in the input byte array.
@@ -81,29 +77,36 @@ namespace Org.BouncyCastle.Crypto
 		* @param outOff the offset into the output byte array the processed data stars at.
 		* @exception DataLengthException if the output buffer is too small.
 		*/
-		public void ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length,
-			byte[]	output,
-			int		outOff)
-		{
-			if (outOff + length > output.Length)
-				throw new DataLengthException("output buffer too small in ProcessBytes()");
+        public void ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
+        {
+            Check.DataLength(input, inOff, length, "input buffer too short");
+            Check.OutputLength(output, outOff, length, "output buffer too short");
 
 			for (int i = 0; i != length; i++)
 			{
-				cipher.ProcessBlock(input, inOff + i, output, outOff + i);
+                m_cipherMode.ProcessBlock(input, inOff + i, output, outOff + i);
 			}
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i != input.Length; i++)
+            {
+                m_cipherMode.ProcessBlock(input[i..], output[i..]);
+            }
+        }
+#endif
+
+        /**
 		* reset the underlying cipher. This leaves it in the same state
 		* it was at after the last init (if there was one).
 		*/
-		public void Reset()
+        public void Reset()
 		{
-			cipher.Reset();
+            m_cipherMode.Reset();
 		}
 	}
 }
diff --git a/crypto/src/crypto/agreement/DHAgreement.cs b/crypto/src/crypto/agreement/DHAgreement.cs
index e988c0d53..6032c237a 100644
--- a/crypto/src/crypto/agreement/DHAgreement.cs
+++ b/crypto/src/crypto/agreement/DHAgreement.cs
@@ -26,30 +26,25 @@ namespace Org.BouncyCastle.Crypto.Agreement
 		private BigInteger				privateValue;
 		private SecureRandom			random;
 
-		public void Init(
-			ICipherParameters parameters)
+		public void Init(ICipherParameters parameters)
 		{
 			AsymmetricKeyParameter kParam;
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom rParam)
 			{
-				ParametersWithRandom rParam = (ParametersWithRandom)parameters;
-
 				this.random = rParam.Random;
 				kParam = (AsymmetricKeyParameter)rParam.Parameters;
 			}
 			else
 			{
-				this.random = new SecureRandom();
+				this.random = CryptoServicesRegistrar.GetSecureRandom();
 				kParam = (AsymmetricKeyParameter)parameters;
 			}
 
-			if (!(kParam is DHPrivateKeyParameters))
-			{
+			if (!(kParam is DHPrivateKeyParameters dhPrivateKeyParameters))
 				throw new ArgumentException("DHEngine expects DHPrivateKeyParameters");
-			}
 
-			this.key = (DHPrivateKeyParameters)kParam;
-			this.dhParams = key.Parameters;
+			this.key = dhPrivateKeyParameters;
+			this.dhParams = dhPrivateKeyParameters.Parameters;
 		}
 
 		/**
diff --git a/crypto/src/crypto/agreement/X25519Agreement.cs b/crypto/src/crypto/agreement/X25519Agreement.cs
index 7e5890c16..b4d2e0e87 100644
--- a/crypto/src/crypto/agreement/X25519Agreement.cs
+++ b/crypto/src/crypto/agreement/X25519Agreement.cs
@@ -7,11 +7,11 @@ namespace Org.BouncyCastle.Crypto.Agreement
     public sealed class X25519Agreement
         : IRawAgreement
     {
-        private X25519PrivateKeyParameters privateKey;
+        private X25519PrivateKeyParameters m_privateKey;
 
         public void Init(ICipherParameters parameters)
         {
-            this.privateKey = (X25519PrivateKeyParameters)parameters;
+            m_privateKey = (X25519PrivateKeyParameters)parameters;
         }
 
         public int AgreementSize
@@ -21,7 +21,18 @@ namespace Org.BouncyCastle.Crypto.Agreement
 
         public void CalculateAgreement(ICipherParameters publicKey, byte[] buf, int off)
         {
-            privateKey.GenerateSecret((X25519PublicKeyParameters)publicKey, buf, off);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            CalculateAgreement(publicKey, buf.AsSpan(off));
+#else
+            m_privateKey.GenerateSecret((X25519PublicKeyParameters)publicKey, buf, off);
+#endif
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void CalculateAgreement(ICipherParameters publicKey, Span<byte> buf)
+        {
+            m_privateKey.GenerateSecret((X25519PublicKeyParameters)publicKey, buf);
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/agreement/X448Agreement.cs b/crypto/src/crypto/agreement/X448Agreement.cs
index 26f608c26..6b91603b5 100644
--- a/crypto/src/crypto/agreement/X448Agreement.cs
+++ b/crypto/src/crypto/agreement/X448Agreement.cs
@@ -7,11 +7,11 @@ namespace Org.BouncyCastle.Crypto.Agreement
     public sealed class X448Agreement
         : IRawAgreement
     {
-        private X448PrivateKeyParameters privateKey;
+        private X448PrivateKeyParameters m_privateKey;
 
         public void Init(ICipherParameters parameters)
         {
-            this.privateKey = (X448PrivateKeyParameters)parameters;
+            m_privateKey = (X448PrivateKeyParameters)parameters;
         }
 
         public int AgreementSize
@@ -21,7 +21,18 @@ namespace Org.BouncyCastle.Crypto.Agreement
 
         public void CalculateAgreement(ICipherParameters publicKey, byte[] buf, int off)
         {
-            privateKey.GenerateSecret((X448PublicKeyParameters)publicKey, buf, off);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            CalculateAgreement(publicKey, buf.AsSpan(off));
+#else
+            m_privateKey.GenerateSecret((X448PublicKeyParameters)publicKey, buf, off);
+#endif
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void CalculateAgreement(ICipherParameters publicKey, Span<byte> buf)
+        {
+            m_privateKey.GenerateSecret((X448PublicKeyParameters)publicKey, buf);
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs
index 794284866..d6ed37032 100755
--- a/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs
+++ b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs
@@ -142,7 +142,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.JPake
         ///      Caller should clear the input password as soon as possible.</param>
         /// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
         public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group)
-            : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { }
+            : this(participantId, password, group, new Sha256Digest(), CryptoServicesRegistrar.GetSecureRandom()) { }
 
 
         /// <summary>
@@ -162,7 +162,8 @@ namespace Org.BouncyCastle.Crypto.Agreement.JPake
         /// <param name="digest">Digest to use during zero knowledge proofs and key confirmation
         ///     (SHA-256 or stronger preferred).</param>
         /// <param name="random">Source of secure random data for x1 and x2, and for the zero knowledge proofs.</param>
-        public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random)
+        public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest,
+            SecureRandom random)
         {
             JPakeUtilities.ValidateNotNull(participantId, "participantId");
             JPakeUtilities.ValidateNotNull(password, "password");
@@ -171,9 +172,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.JPake
             JPakeUtilities.ValidateNotNull(random, "random");
 
             if (password.Length == 0)
-            {
                 throw new ArgumentException("Password must not be empty.");
-            }
 
             this.participantId = participantId;
 
diff --git a/crypto/src/crypto/agreement/kdf/ConcatenationKdfGenerator.cs b/crypto/src/crypto/agreement/kdf/ConcatenationKdfGenerator.cs
index d88f4dfdb..207c795da 100644
--- a/crypto/src/crypto/agreement/kdf/ConcatenationKdfGenerator.cs
+++ b/crypto/src/crypto/agreement/kdf/ConcatenationKdfGenerator.cs
@@ -5,96 +5,110 @@ using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Agreement.Kdf
 {
-    /**
-     * Generator for Concatenation Key Derivation Function defined in NIST SP 800-56A, Sect 5.8.1
-     */
-    public class ConcatenationKdfGenerator
+    /// <summary>Generator for Concatenation Key Derivation Function defined in NIST SP 800-56A, Sect 5.8.1</summary>
+    public sealed class ConcatenationKdfGenerator
         :   IDerivationFunction
     {
-        private readonly IDigest mDigest;
+        private readonly IDigest m_digest;
+        private readonly int m_hLen;
 
-        private byte[] mShared;
-        private byte[] mOtherInfo;
-        private int mHLen;
+        private byte[] m_buffer;
 
-        /**
-         * @param digest the digest to be used as the source of generated bytes
-         */
+        /// <param name="digest">the digest to be used as the source of generated bytes</param>
         public ConcatenationKdfGenerator(IDigest digest)
         {
-            this.mDigest = digest;
-            this.mHLen = digest.GetDigestSize();
+            m_digest = digest;
+            m_hLen = digest.GetDigestSize();
         }
 
-        public virtual void Init(IDerivationParameters param)
+        public void Init(IDerivationParameters param)
         {
-            if (!(param is KdfParameters))
+            if (!(param is KdfParameters kdfParameters))
                 throw new ArgumentException("KDF parameters required for ConcatenationKdfGenerator");
 
-            KdfParameters p = (KdfParameters)param;
+            byte[] sharedSecret = kdfParameters.GetSharedSecret();
+            byte[] otherInfo = kdfParameters.GetIV();
 
-            mShared = p.GetSharedSecret();
-            mOtherInfo = p.GetIV();
+            m_buffer = new byte[4 + sharedSecret.Length + otherInfo.Length + m_hLen];
+            sharedSecret.CopyTo(m_buffer, 4);
+            otherInfo.CopyTo(m_buffer, 4 + sharedSecret.Length);
         }
 
-        /**
-         * return the underlying digest.
-         */
-        public virtual IDigest Digest
-        {
-            get { return mDigest; }
-        }
+        /// <summary>the underlying digest.</summary>
+        public IDigest Digest => m_digest;
 
-        /**
-         * fill len bytes of the output buffer with bytes generated from
-         * the derivation function.
-         *
-         * @throws DataLengthException if the out buffer is too small.
-         */
-        public virtual int GenerateBytes(byte[]	outBytes, int outOff, int len)
+        /// <summary>Fill <c>len</c> bytes of the output buffer with bytes generated from the derivation function.
+        /// </summary>
+        public int GenerateBytes(byte[]	output, int outOff, int length)
         {
-            if ((outBytes.Length - len) < outOff)
-                throw new DataLengthException("output buffer too small");
+            Check.OutputLength(output, outOff, length, "output buffer too small");
 
-            byte[] hashBuf = new byte[mHLen];
-            byte[] C = new byte[4];
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
+            int hashPos = m_buffer.Length - m_hLen;
             uint counter = 1;
-            int outputLen = 0;
 
-            mDigest.Reset();
+            m_digest.Reset();
+
+            int end = outOff + length;
+            int limit = end - m_hLen;
 
-            if (len > mHLen)
+            while (outOff <= limit)
             {
-                do
-                {
-                    Pack.UInt32_To_BE(counter, C);
+                Pack.UInt32_To_BE(counter++, m_buffer, 0);
 
-                    mDigest.BlockUpdate(C, 0, C.Length);
-                    mDigest.BlockUpdate(mShared, 0, mShared.Length);
-                    mDigest.BlockUpdate(mOtherInfo, 0, mOtherInfo.Length);
+                m_digest.BlockUpdate(m_buffer, 0, hashPos);
+                m_digest.DoFinal(output, outOff);
 
-                    mDigest.DoFinal(hashBuf, 0);
+                outOff += m_hLen;
+            }
 
-                    Array.Copy(hashBuf, 0, outBytes, outOff + outputLen, mHLen);
-                    outputLen += mHLen;
-                }
-                while ((counter++) < (len / mHLen));
+            if (outOff < end)
+            {
+                Pack.UInt32_To_BE(counter, m_buffer, 0);
+
+                m_digest.BlockUpdate(m_buffer, 0, hashPos);
+                m_digest.DoFinal(m_buffer, hashPos);
+
+                Array.Copy(m_buffer, hashPos, output, outOff, end - outOff);
             }
 
-            if (outputLen < len)
+            return length;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
+        {
+            int hashPos = m_buffer.Length - m_hLen;
+            uint counter = 1;
+
+            m_digest.Reset();
+
+            int pos = 0, length = output.Length, limit = length - m_hLen;
+
+            while (pos <= limit)
             {
-                Pack.UInt32_To_BE(counter, C);
+                Pack.UInt32_To_BE(counter++, m_buffer.AsSpan());
+
+                m_digest.BlockUpdate(m_buffer.AsSpan(0, hashPos));
+                m_digest.DoFinal(output[pos..]);
 
-                mDigest.BlockUpdate(C, 0, C.Length);
-                mDigest.BlockUpdate(mShared, 0, mShared.Length);
-                mDigest.BlockUpdate(mOtherInfo, 0, mOtherInfo.Length);
+                pos += m_hLen;
+            }
 
-                mDigest.DoFinal(hashBuf, 0);
+            if (pos < length)
+            {
+                Pack.UInt32_To_BE(counter, m_buffer.AsSpan());
 
-                Array.Copy(hashBuf, 0, outBytes, outOff + outputLen, len - outputLen);
+                m_digest.BlockUpdate(m_buffer.AsSpan(0, hashPos));
+                m_digest.DoFinal(m_buffer.AsSpan(hashPos));
+                m_buffer.AsSpan(hashPos, length - pos).CopyTo(output[pos..]);
             }
 
-            return len;
+            return length;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/agreement/kdf/DHKekGenerator.cs b/crypto/src/crypto/agreement/kdf/DHKekGenerator.cs
index 259e21e69..2dabe9d1f 100644
--- a/crypto/src/crypto/agreement/kdf/DHKekGenerator.cs
+++ b/crypto/src/crypto/agreement/kdf/DHKekGenerator.cs
@@ -1,17 +1,19 @@
 using System;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Crypto.Agreement.Kdf
 {
     /**
     * RFC 2631 Diffie-hellman KEK derivation function.
     */
-    public class DHKekGenerator
+    public sealed class DHKekGenerator
         : IDerivationFunction
     {
-        private readonly IDigest digest;
+        private readonly IDigest m_digest;
 
         private DerObjectIdentifier	algorithm;
         private int					keySize;
@@ -20,10 +22,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
 
         public DHKekGenerator(IDigest digest)
         {
-            this.digest = digest;
+            m_digest = digest;
         }
 
-        public virtual void Init(IDerivationParameters param)
+        public void Init(IDerivationParameters param)
         {
             DHKdfParameters parameters = (DHKdfParameters)param;
 
@@ -33,20 +35,79 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
             this.partyAInfo = parameters.GetExtraInfo(); // TODO Clone?
         }
 
-        public virtual IDigest Digest
-        {
-            get { return digest; }
-        }
+        public IDigest Digest => m_digest;
 
-        public virtual int GenerateBytes(byte[]	outBytes, int outOff, int len)
+        public int GenerateBytes(byte[]	outBytes, int outOff, int length)
         {
-            if ((outBytes.Length - len) < outOff)
+            Check.OutputLength(outBytes, outOff, length, "output buffer too small");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(outBytes.AsSpan(outOff, length));
+#else
+            long oBytes = length;
+            int digestSize = m_digest.GetDigestSize();
+
+            //
+            // this is at odds with the standard implementation, the
+            // maximum value should be hBits * (2^32 - 1) where hBits
+            // is the digest output size in bits. We can't have an
+            // array with a long index at the moment...
+            //
+            if (oBytes > ((2L << 32) - 1))
+                throw new ArgumentException("Output length too large");
+
+            int cThreshold = (int)((oBytes + digestSize - 1) / digestSize);
+
+            byte[] dig = new byte[digestSize];
+
+            uint counter = 1;
+
+            for (int i = 0; i < cThreshold; i++)
             {
-                throw new DataLengthException("output buffer too small");
+                // KeySpecificInfo
+                DerSequence keyInfo = new DerSequence(algorithm, new DerOctetString(Pack.UInt32_To_BE(counter)));
+
+                // OtherInfo
+                Asn1EncodableVector v1 = new Asn1EncodableVector(keyInfo);
+
+                if (partyAInfo != null)
+                {
+                    v1.Add(new DerTaggedObject(true, 0, new DerOctetString(partyAInfo)));
+                }
+
+                v1.Add(new DerTaggedObject(true, 2, new DerOctetString(Pack.UInt32_To_BE((uint)keySize))));
+
+                byte[] other = new DerSequence(v1).GetDerEncoded();
+
+                m_digest.BlockUpdate(z, 0, z.Length);
+                m_digest.BlockUpdate(other, 0, other.Length);
+                m_digest.DoFinal(dig, 0);
+
+                if (length > digestSize)
+                {
+                    Array.Copy(dig, 0, outBytes, outOff, digestSize);
+                    outOff += digestSize;
+                    length -= digestSize;
+                }
+                else
+                {
+                    Array.Copy(dig, 0, outBytes, outOff, length);
+                }
+
+                counter++;
             }
 
-            long oBytes = len;
-            int outLen = digest.GetDigestSize();
+            m_digest.Reset();
+
+            return (int)oBytes;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
+        {
+            long oBytes = output.Length;
+            int digestSize = m_digest.GetDigestSize();
 
             //
             // this is at odds with the standard implementation, the
@@ -55,24 +116,20 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
             // array with a long index at the moment...
             //
             if (oBytes > ((2L << 32) - 1))
-            {
                 throw new ArgumentException("Output length too large");
-            }
 
-            int cThreshold = (int)((oBytes + outLen - 1) / outLen);
+            int cThreshold = (int)((oBytes + digestSize - 1) / digestSize);
 
-            byte[] dig = new byte[digest.GetDigestSize()];
+            Span<byte> dig = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
 
             uint counter = 1;
 
             for (int i = 0; i < cThreshold; i++)
             {
-                digest.BlockUpdate(z, 0, z.Length);
-
                 // KeySpecificInfo
-                DerSequence keyInfo = new DerSequence(
-                    algorithm,
-                    new DerOctetString(Pack.UInt32_To_BE(counter)));
+                DerSequence keyInfo = new DerSequence(algorithm, new DerOctetString(Pack.UInt32_To_BE(counter)));
 
                 // OtherInfo
                 Asn1EncodableVector v1 = new Asn1EncodableVector(keyInfo);
@@ -86,27 +143,28 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
 
                 byte[] other = new DerSequence(v1).GetDerEncoded();
 
-                digest.BlockUpdate(other, 0, other.Length);
-
-                digest.DoFinal(dig, 0);
+                m_digest.BlockUpdate(z);
+                m_digest.BlockUpdate(other);
+                m_digest.DoFinal(dig);
 
-                if (len > outLen)
+                int remaining = output.Length;
+                if (remaining > digestSize)
                 {
-                    Array.Copy(dig, 0, outBytes, outOff, outLen);
-                    outOff += outLen;
-                    len -= outLen;
+                    dig.CopyTo(output);
+                    output = output[digestSize..];
                 }
                 else
                 {
-                    Array.Copy(dig, 0, outBytes, outOff, len);
+                    dig[..remaining].CopyTo(output);
                 }
 
                 counter++;
             }
 
-            digest.Reset();
+            m_digest.Reset();
 
             return (int)oBytes;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs b/crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs
index 74464574c..dbb7ad535 100644
--- a/crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs
+++ b/crypto/src/crypto/agreement/kdf/ECDHKekGenerator.cs
@@ -11,10 +11,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
     /**
     * X9.63 based key derivation function for ECDH CMS.
     */
-    public class ECDHKekGenerator
+    public sealed class ECDHKekGenerator
         : IDerivationFunction
     {
-        private readonly IDerivationFunction kdf;
+        private readonly IDerivationFunction m_kdf;
 
         private DerObjectIdentifier	algorithm;
         private int					keySize;
@@ -22,10 +22,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
 
         public ECDHKekGenerator(IDigest digest)
         {
-            this.kdf = new Kdf2BytesGenerator(digest);
+            m_kdf = new Kdf2BytesGenerator(digest);
         }
 
-        public virtual void Init(IDerivationParameters param)
+        public void Init(IDerivationParameters param)
         {
             DHKdfParameters parameters = (DHKdfParameters)param;
 
@@ -34,12 +34,29 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
             this.z = parameters.GetZ(); // TODO Clone?
         }
 
-        public virtual IDigest Digest
+        public IDigest Digest => m_kdf.Digest;
+
+        public int GenerateBytes(byte[]	outBytes, int outOff, int length)
         {
-            get { return kdf.Digest; }
+            Check.OutputLength(outBytes, outOff, length, "output buffer too small");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(outBytes.AsSpan(outOff, length));
+#else
+            // TODO Create an ASN.1 class for this (RFC3278)
+            // ECC-CMS-SharedInfo
+            DerSequence s = new DerSequence(
+                new AlgorithmIdentifier(algorithm, DerNull.Instance),
+                new DerTaggedObject(true, 2, new DerOctetString(Pack.UInt32_To_BE((uint)keySize))));
+
+            m_kdf.Init(new KdfParameters(z, s.GetDerEncoded()));
+
+            return m_kdf.GenerateBytes(outBytes, outOff, length);
+#endif
         }
 
-        public virtual int GenerateBytes(byte[]	outBytes, int outOff, int len)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
         {
             // TODO Create an ASN.1 class for this (RFC3278)
             // ECC-CMS-SharedInfo
@@ -47,9 +64,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Kdf
                 new AlgorithmIdentifier(algorithm, DerNull.Instance),
                 new DerTaggedObject(true, 2, new DerOctetString(Pack.UInt32_To_BE((uint)keySize))));
 
-            kdf.Init(new KdfParameters(z, s.GetDerEncoded()));
+            m_kdf.Init(new KdfParameters(z, s.GetDerEncoded()));
 
-            return kdf.GenerateBytes(outBytes, outOff, len);
+            return m_kdf.GenerateBytes(output);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/agreement/srp/SRP6Utilities.cs b/crypto/src/crypto/agreement/srp/SRP6Utilities.cs
index ef6d8f24c..73be57c5a 100644
--- a/crypto/src/crypto/agreement/srp/SRP6Utilities.cs
+++ b/crypto/src/crypto/agreement/srp/SRP6Utilities.cs
@@ -20,7 +20,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Srp
 
 		public static BigInteger CalculateX(IDigest digest, BigInteger N, byte[] salt, byte[] identity, byte[] password)
 	    {
-	        byte[] output = new byte[digest.GetDigestSize()];
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return CalculateX(digest, N, salt.AsSpan(), identity.AsSpan(), password.AsSpan());
+#else
+            byte[] output = new byte[digest.GetDigestSize()];
 
 	        digest.BlockUpdate(identity, 0, identity.Length);
 	        digest.Update((byte)':');
@@ -32,9 +35,32 @@ namespace Org.BouncyCastle.Crypto.Agreement.Srp
 	        digest.DoFinal(output, 0);
 
 	        return new BigInteger(1, output);
+#endif
 	    }
 
-		public static BigInteger GeneratePrivateValue(IDigest digest, BigInteger N, BigInteger g, SecureRandom random)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static BigInteger CalculateX(IDigest digest, BigInteger N, ReadOnlySpan<byte> salt,
+            ReadOnlySpan<byte> identity, ReadOnlySpan<byte> password)
+        {
+            int digestSize = digest.GetDigestSize();
+            Span<byte> output = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+
+            digest.BlockUpdate(identity);
+            digest.Update((byte)':');
+            digest.BlockUpdate(password);
+            digest.DoFinal(output);
+
+            digest.BlockUpdate(salt);
+            digest.BlockUpdate(output);
+            digest.DoFinal(output);
+
+            return new BigInteger(1, output);
+        }
+#endif
+
+        public static BigInteger GeneratePrivateValue(IDigest digest, BigInteger N, BigInteger g, SecureRandom random)
 	    {
 			int minBits = System.Math.Min(256, N.BitLength / 2);
 	        BigInteger min = BigInteger.One.ShiftLeft(minBits - 1);
@@ -95,59 +121,98 @@ namespace Org.BouncyCastle.Crypto.Agreement.Srp
          */
         public static BigInteger CalculateKey(IDigest digest, BigInteger N, BigInteger S)
         {
-            int padLength = (N.BitLength + 7) / 8;
-            byte[] _S = GetPadded(S, padLength);
-            digest.BlockUpdate(_S, 0, _S.Length);
-
-            byte[] output = new byte[digest.GetDigestSize()];
+            int paddedLength = (N.BitLength + 7) / 8;
+            int digestSize = digest.GetDigestSize();
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = paddedLength <= 512
+                ? stackalloc byte[paddedLength]
+                : new byte[paddedLength];
+            BigIntegers.AsUnsignedByteArray(S, bytes);
+            digest.BlockUpdate(bytes);
+
+            Span<byte> output = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+            digest.DoFinal(output);
+#else
+            byte[] bytes = new byte[paddedLength];
+            BigIntegers.AsUnsignedByteArray(S, bytes, 0, bytes.Length);
+	        digest.BlockUpdate(bytes, 0, bytes.Length);
+
+            byte[] output = new byte[digestSize];
             digest.DoFinal(output, 0);
+#endif
+
             return new BigInteger(1, output);
         }
 
         private static BigInteger HashPaddedTriplet(IDigest digest, BigInteger N, BigInteger n1, BigInteger n2, BigInteger n3)
         {
-            int padLength = (N.BitLength + 7) / 8;
-
-            byte[] n1_bytes = GetPadded(n1, padLength);
-            byte[] n2_bytes = GetPadded(n2, padLength);
-            byte[] n3_bytes = GetPadded(n3, padLength);
-
-            digest.BlockUpdate(n1_bytes, 0, n1_bytes.Length);
-            digest.BlockUpdate(n2_bytes, 0, n2_bytes.Length);
-            digest.BlockUpdate(n3_bytes, 0, n3_bytes.Length);
-
-            byte[] output = new byte[digest.GetDigestSize()];
+            int paddedLength = (N.BitLength + 7) / 8;
+            int digestSize = digest.GetDigestSize();
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = paddedLength <= 512
+                ? stackalloc byte[paddedLength]
+                : new byte[paddedLength];
+            BigIntegers.AsUnsignedByteArray(n1, bytes);
+            digest.BlockUpdate(bytes);
+            BigIntegers.AsUnsignedByteArray(n2, bytes);
+            digest.BlockUpdate(bytes);
+            BigIntegers.AsUnsignedByteArray(n3, bytes);
+            digest.BlockUpdate(bytes);
+
+            Span<byte> output = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+            digest.DoFinal(output);
+#else
+            byte[] bytes = new byte[paddedLength];
+            BigIntegers.AsUnsignedByteArray(n1, bytes, 0, bytes.Length);
+	        digest.BlockUpdate(bytes, 0, bytes.Length);
+            BigIntegers.AsUnsignedByteArray(n2, bytes, 0, bytes.Length);
+	        digest.BlockUpdate(bytes, 0, bytes.Length);
+            BigIntegers.AsUnsignedByteArray(n3, bytes, 0, bytes.Length);
+	        digest.BlockUpdate(bytes, 0, bytes.Length);
+
+            byte[] output = new byte[digestSize];
             digest.DoFinal(output, 0);
+#endif
 
             return new BigInteger(1, output);
         }
 
         private static BigInteger HashPaddedPair(IDigest digest, BigInteger N, BigInteger n1, BigInteger n2)
 		{
-	    	int padLength = (N.BitLength + 7) / 8;
-
-	    	byte[] n1_bytes = GetPadded(n1, padLength);
-	    	byte[] n2_bytes = GetPadded(n2, padLength);
-
-	        digest.BlockUpdate(n1_bytes, 0, n1_bytes.Length);
-	        digest.BlockUpdate(n2_bytes, 0, n2_bytes.Length);
-
-	        byte[] output = new byte[digest.GetDigestSize()];
+	    	int paddedLength = (N.BitLength + 7) / 8;
+            int digestSize = digest.GetDigestSize();
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = paddedLength <= 512
+                ? stackalloc byte[paddedLength]
+                : new byte[paddedLength];
+            BigIntegers.AsUnsignedByteArray(n1, bytes);
+            digest.BlockUpdate(bytes);
+            BigIntegers.AsUnsignedByteArray(n2, bytes);
+            digest.BlockUpdate(bytes);
+
+            Span<byte> output = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+            digest.DoFinal(output);
+#else
+            byte[] bytes = new byte[paddedLength];
+            BigIntegers.AsUnsignedByteArray(n1, bytes, 0, bytes.Length);
+	        digest.BlockUpdate(bytes, 0, bytes.Length);
+            BigIntegers.AsUnsignedByteArray(n2, bytes, 0, bytes.Length);
+	        digest.BlockUpdate(bytes, 0, bytes.Length);
+
+	        byte[] output = new byte[digestSize];
 	        digest.DoFinal(output, 0);
+#endif
 
-	        return new BigInteger(1, output);
-		}
-
-		private static byte[] GetPadded(BigInteger n, int length)
-		{
-			byte[] bs = BigIntegers.AsUnsignedByteArray(n);
-			if (bs.Length < length)
-			{
-				byte[] tmp = new byte[length];
-				Array.Copy(bs, 0, tmp, length - bs.Length, bs.Length);
-				bs = tmp;
-			}
-			return bs;
-		}
+            return new BigInteger(1, output);
+        }
 	}
 }
diff --git a/crypto/src/crypto/digests/Blake2bDigest.cs b/crypto/src/crypto/digests/Blake2bDigest.cs
index 1ac9cfa35..953ac0062 100644
--- a/crypto/src/crypto/digests/Blake2bDigest.cs
+++ b/crypto/src/crypto/digests/Blake2bDigest.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
 
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
@@ -39,7 +42,7 @@ namespace Org.BouncyCastle.Crypto.Digests
      * BLAKE2b is optimized for 64-bit platforms and produces digests of any size
      * between 1 and 64 bytes.
      */
-    public class Blake2bDigest
+    public sealed class Blake2bDigest
         : IDigest
     {
         // Blake2b Initialization Vector:
@@ -276,12 +279,10 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @param b the input byte to be entered.
          */
-        public virtual void Update(byte b)
+        public void Update(byte b)
         {
-            int remainingLength = 0; // left bytes of buffer
-
             // process the buffer if full else add to buffer:
-            remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+            int remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
             if (remainingLength == 0)
             { // full buffer
                 t0 += BLOCK_LENGTH_BYTES;
@@ -289,7 +290,11 @@ namespace Org.BouncyCastle.Crypto.Digests
                 { // if message > 2^64
                     t1++;
                 }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Compress(buffer);
+#else
                 Compress(buffer, 0);
+#endif
                 Array.Clear(buffer, 0, buffer.Length);// clear buffer
                 buffer[0] = b;
                 bufferPos = 1;
@@ -298,7 +303,6 @@ namespace Org.BouncyCastle.Crypto.Digests
             {
                 buffer[bufferPos] = b;
                 bufferPos++;
-                return;
             }
         }
 
@@ -309,11 +313,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param offset  the offset into the byte array where the data starts.
          * @param len     the length of the data.
          */
-        public virtual void BlockUpdate(byte[] message, int offset, int len)
+        public void BlockUpdate(byte[] message, int offset, int len)
         {
             if (message == null || len == 0)
                 return;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(message.AsSpan(offset, len));
+#else
             int remainingLength = 0; // left bytes of buffer
 
             if (bufferPos != 0)
@@ -323,8 +330,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                 remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
                 if (remainingLength < len)
                 { // full buffer + at least 1 byte
-                    Array.Copy(message, offset, buffer, bufferPos,
-                        remainingLength);
+                    Array.Copy(message, offset, buffer, bufferPos, remainingLength);
                     t0 += BLOCK_LENGTH_BYTES;
                     if (t0 == 0)
                     { // if message > 2^64
@@ -357,10 +363,63 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
 
             // fill the buffer with left bytes, this might be a full block
-            Array.Copy(message, messagePos, buffer, 0, offset + len
-                - messagePos);
+            Array.Copy(message, messagePos, buffer, 0, offset + len - messagePos);
             bufferPos += offset + len - messagePos;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (input.IsEmpty)
+                return;
+
+            int remainingLength = 0; // left bytes of buffer
+
+            if (bufferPos != 0)
+            { // commenced, incomplete buffer
+
+                // complete the buffer:
+                remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+                if (remainingLength < input.Length)
+                { // full buffer + at least 1 byte
+                    input[..remainingLength].CopyTo(buffer.AsSpan(bufferPos));
+                    t0 += BLOCK_LENGTH_BYTES;
+                    if (t0 == 0)
+                    { // if message > 2^64
+                        t1++;
+                    }
+                    Compress(buffer);
+                    bufferPos = 0;
+                    Array.Clear(buffer, 0, buffer.Length);// clear buffer
+                }
+                else
+                {
+                    input.CopyTo(buffer.AsSpan(bufferPos));
+                    bufferPos += input.Length;
+                    return;
+                }
+            }
+
+            // process blocks except last block (also if last block is full)
+            int messagePos;
+            int blockWiseLastPos = input.Length - BLOCK_LENGTH_BYTES;
+            for (messagePos = remainingLength; messagePos < blockWiseLastPos; messagePos += BLOCK_LENGTH_BYTES)
+            { // block wise 128 bytes
+                // without buffer:
+                t0 += BLOCK_LENGTH_BYTES;
+                if (t0 == 0)
+                {
+                    t1++;
+                }
+                Compress(input[messagePos..]);
+            }
+
+            // fill the buffer with left bytes, this might be a full block
+            input[messagePos..].CopyTo(buffer.AsSpan());
+            bufferPos += input.Length - messagePos;
         }
+#endif
 
         /**
          * close the digest, producing the final digest value. The doFinal
@@ -370,8 +429,11 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param out       the array the digest is to be copied into.
          * @param outOffset the offset into the out array the digest is to start at.
          */
-        public virtual int DoFinal(byte[] output, int outOffset)
+        public int DoFinal(byte[] output, int outOffset)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOffset));
+#else
             f0 = 0xFFFFFFFFFFFFFFFFUL;
             t0 += (ulong)bufferPos;
             if (bufferPos > 0 && t0 == 0)
@@ -382,19 +444,43 @@ namespace Org.BouncyCastle.Crypto.Digests
             Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
             Array.Clear(internalState, 0, internalState.Length);
 
-            byte[] bytes = new byte[8];
-            for (int i = 0; i < chainValue.Length && (i * 8 < digestLength); i++)
+            int full = digestLength >> 3, partial = digestLength & 7;
+            Pack.UInt64_To_LE(chainValue, 0, full, output, outOffset);
+            if (partial > 0)
             {
-                Pack.UInt64_To_LE(chainValue[i], bytes, 0);
+                byte[] bytes = new byte[8];
+                Pack.UInt64_To_LE(chainValue[full], bytes, 0);
+                Array.Copy(bytes, 0, output, outOffset + digestLength - partial, partial);
+            }
 
-                if (i * 8 < digestLength - 8)
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 8, 8);
-                }
-                else
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 8, digestLength - (i * 8));
-                }
+            Array.Clear(chainValue, 0, chainValue.Length);
+
+            Reset();
+
+            return digestLength;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            f0 = 0xFFFFFFFFFFFFFFFFUL;
+            t0 += (ulong)bufferPos;
+            if (bufferPos > 0 && t0 == 0)
+            {
+                t1++;
+            }
+            Compress(buffer);
+            Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
+            Array.Clear(internalState, 0, internalState.Length);
+
+            int full = digestLength >> 3, partial = digestLength & 7;
+            Pack.UInt64_To_LE(chainValue.AsSpan(0, full), output);
+            if (partial > 0)
+            {
+                Span<byte> bytes = stackalloc byte[8];
+                Pack.UInt64_To_LE(chainValue[full], bytes);
+                bytes[..partial].CopyTo(output[(digestLength - partial)..]);
             }
 
             Array.Clear(chainValue, 0, chainValue.Length);
@@ -403,13 +489,14 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             return digestLength;
         }
+#endif
 
         /**
          * Reset the digest back to it's initial state.
          * The key, the salt and the personal string will
          * remain for further computations.
          */
-        public virtual void Reset()
+        public void Reset()
         {
             bufferPos = 0;
             f0 = 0L;
@@ -425,6 +512,35 @@ namespace Org.BouncyCastle.Crypto.Digests
             Init();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> message)
+        {
+            InitializeInternalState();
+
+            Span<ulong> m = stackalloc ulong[16];
+            Pack.LE_To_UInt64(message, m);
+
+            for (int round = 0; round < ROUNDS; round++)
+            {
+                // G apply to columns of internalState:m[blake2b_sigma[round][2 * blockPos]] /+1
+                G(m[blake2b_sigma[round, 0]], m[blake2b_sigma[round, 1]], 0, 4, 8, 12);
+                G(m[blake2b_sigma[round, 2]], m[blake2b_sigma[round, 3]], 1, 5, 9, 13);
+                G(m[blake2b_sigma[round, 4]], m[blake2b_sigma[round, 5]], 2, 6, 10, 14);
+                G(m[blake2b_sigma[round, 6]], m[blake2b_sigma[round, 7]], 3, 7, 11, 15);
+                // G apply to diagonals of internalState:
+                G(m[blake2b_sigma[round, 8]], m[blake2b_sigma[round, 9]], 0, 5, 10, 15);
+                G(m[blake2b_sigma[round, 10]], m[blake2b_sigma[round, 11]], 1, 6, 11, 12);
+                G(m[blake2b_sigma[round, 12]], m[blake2b_sigma[round, 13]], 2, 7, 8, 13);
+                G(m[blake2b_sigma[round, 14]], m[blake2b_sigma[round, 15]], 3, 4, 9, 14);
+            }
+
+            // update chain values:
+            for (int offset = 0; offset < chainValue.Length; offset++)
+            {
+                chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
+            }
+        }
+#else
         private void Compress(byte[] message, int messagePos)
         {
             InitializeInternalState();
@@ -452,7 +568,11 @@ namespace Org.BouncyCastle.Crypto.Digests
                 chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
             }
         }
+#endif
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         private void G(ulong m1, ulong m2, int posA, int posB, int posC, int posD)
         {
             internalState[posA] = internalState[posA] + internalState[posB] + m1;
@@ -475,17 +595,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return the algorithm name
          */
-        public virtual string AlgorithmName
-        {
-            get { return "BLAKE2b"; }
-        }
+        public string AlgorithmName => "BLAKE2b";
 
         /**
          * return the size, in bytes, of the digest produced by this message digest.
          *
          * @return the size, in bytes, of the digest produced by this message digest.
          */
-        public virtual int GetDigestSize()
+        public int GetDigestSize()
         {
             return digestLength;
         }
@@ -496,7 +613,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return byte length of the digests internal buffer.
          */
-        public virtual int GetByteLength()
+        public int GetByteLength()
         {
             return BLOCK_LENGTH_BYTES;
         }
@@ -505,7 +622,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Overwrite the key
          * if it is no longer used (zeroization)
          */
-        public virtual void ClearKey()
+        public void ClearKey()
         {
             if (key != null)
             {
@@ -518,7 +635,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Overwrite the salt (pepper) if it
          * is secret and no longer used (zeroization)
          */
-        public virtual void ClearSalt()
+        public void ClearSalt()
         {
             if (salt != null)
             {
diff --git a/crypto/src/crypto/digests/Blake2sDigest.cs b/crypto/src/crypto/digests/Blake2sDigest.cs
index f50419126..a6ee75af5 100644
--- a/crypto/src/crypto/digests/Blake2sDigest.cs
+++ b/crypto/src/crypto/digests/Blake2sDigest.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
 
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
@@ -40,7 +43,7 @@ namespace Org.BouncyCastle.Crypto.Digests
      * BLAKE2s is optimized for 32-bit platforms and produces digests of any size
      * between 1 and 32 bytes.
      */
-    public class Blake2sDigest
+    public sealed class Blake2sDigest
         : IDigest
     {
         /**
@@ -83,16 +86,13 @@ namespace Org.BouncyCastle.Crypto.Digests
         private byte[] key = null;
 
         // Tree hashing parameters:
-        // Because this class does not implement the Tree Hashing Mode,
-        // these parameters can be treated as constants (see Init() function)
-	    /*
-	     * private int fanout = 1; // 0-255
-	     * private int depth = 1; // 1 - 255
-	     * private int leafLength= 0;
-	     * private long nodeOffset = 0L;
-	     * private int nodeDepth = 0;
-	     * private int innerHashLength = 0;
-	     */
+        // The Tree Hashing Mode is not supported but these are used for the XOF implementation
+        private int fanout = 1; // 0-255
+        private int depth = 1; // 0-255
+        private int leafLength = 0;
+        private long nodeOffset = 0L;
+        private int nodeDepth = 0;
+        private int innerHashLength = 0;
 
         /**
          * Whenever this buffer overflows, it will be processed in the Compress()
@@ -145,8 +145,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             this.keyLength = digest.keyLength;
             this.key = Arrays.Clone(digest.key);
             this.digestLength = digest.digestLength;
+            this.internalState = Arrays.Clone(digest.internalState);
             this.chainValue = Arrays.Clone(digest.chainValue);
+            this.t0 = digest.t0;
+            this.t1 = digest.t1;
+            this.f0 = digest.f0;
+            this.salt = Arrays.Clone(digest.salt);
             this.personalization = Arrays.Clone(digest.personalization);
+            this.fanout = digest.fanout;
+            this.depth = digest.depth;
+            this.leafLength = digest.leafLength;
+            this.nodeOffset = digest.nodeOffset;
+            this.nodeDepth = digest.nodeDepth;
+            this.innerHashLength = digest.innerHashLength;
         }
 
         /**
@@ -159,10 +170,9 @@ namespace Org.BouncyCastle.Crypto.Digests
             if (digestBits < 8 || digestBits > 256 || digestBits % 8 != 0)
                 throw new ArgumentException("BLAKE2s digest bit length must be a multiple of 8 and not greater than 256");
 
-            buffer = new byte[BLOCK_LENGTH_BYTES];
-            keyLength = 0;
             digestLength = digestBits / 8;
-            Init();
+
+            Init(null, null, null);
         }
 
         /**
@@ -176,21 +186,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          */
         public Blake2sDigest(byte[] key)
         {
-            buffer = new byte[BLOCK_LENGTH_BYTES];
-            if (key != null)
-            {
-                if (key.Length > 32)
-                    throw new ArgumentException("Keys > 32 are not supported");
-
-                this.key = new byte[key.Length];
-                Array.Copy(key, 0, this.key, 0, key.Length);
-
-                keyLength = key.Length;
-                Array.Copy(key, 0, buffer, 0, key.Length);
-                bufferPos = BLOCK_LENGTH_BYTES; // zero padding
-            }
-            digestLength = 32;
-            Init();
+            Init(null, null, key);
         }
 
         /**
@@ -206,65 +202,79 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param salt            8 bytes or null
          * @param personalization 8 bytes or null
          */
-        public Blake2sDigest(byte[] key, int digestBytes, byte[] salt,
-                             byte[] personalization)
+        public Blake2sDigest(byte[] key, int digestBytes, byte[] salt, byte[] personalization)
         {
             if (digestBytes < 1 || digestBytes > 32)
                 throw new ArgumentException("Invalid digest length (required: 1 - 32)");
 
             this.digestLength = digestBytes;
-            this.buffer = new byte[BLOCK_LENGTH_BYTES];
 
-            if (salt != null)
-            {
-                if (salt.Length != 8)
-                    throw new ArgumentException("Salt length must be exactly 8 bytes");
+            Init(salt, personalization, key);
+        }
 
-                this.salt = new byte[8];
-                Array.Copy(salt, 0, this.salt, 0, salt.Length);
-            }
-            if (personalization != null)
-            {
-                if (personalization.Length != 8)
-                    throw new ArgumentException("Personalization length must be exactly 8 bytes");
+        // XOF root hash parameters
+        internal Blake2sDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization, long offset)
+        {
+            digestLength = digestBytes;
+            nodeOffset = offset;
 
-                this.personalization = new byte[8];
-                Array.Copy(personalization, 0, this.personalization, 0, personalization.Length);
-            }
-            if (key != null)
-            {
-                if (key.Length > 32)
-                    throw new ArgumentException("Keys > 32 bytes are not supported");
+            Init(salt, personalization, key);
+        }
+
+        // XOF internal hash parameters
+        internal Blake2sDigest(int digestBytes, int hashLength, long offset)
+        {
+            digestLength = digestBytes;
+            nodeOffset = offset;
+            fanout = 0;
+            depth = 0;
+            leafLength = hashLength;
+            innerHashLength = hashLength;
+            nodeDepth = 0;
+
+            Init(null, null, null);
+        }
 
-                this.key = new byte[key.Length];
-                Array.Copy(key, 0, this.key, 0, key.Length);
+        // initialize the digest's parameters
+        private void Init(byte[] salt, byte[] personalization, byte[] key)
+        {
+            buffer = new byte[BLOCK_LENGTH_BYTES];
 
+            if (key != null && key.Length > 0)
+            {
                 keyLength = key.Length;
-                Array.Copy(key, 0, buffer, 0, key.Length);
+                if (keyLength > 32)
+                    throw new ArgumentException("Keys > 32 bytes are not supported");
+
+                this.key = new byte[keyLength];
+                Array.Copy(key, 0, this.key, 0, keyLength);
+                Array.Copy(key, 0, buffer, 0, keyLength);
                 bufferPos = BLOCK_LENGTH_BYTES; // zero padding
             }
-            Init();
-        }
 
-        // initialize chainValue
-        private void Init()
-        {
             if (chainValue == null)
             {
                 chainValue = new uint[8];
 
-                chainValue[0] = blake2s_IV[0] ^ (uint)(digestLength | (keyLength << 8) | 0x1010000);
-                // 0x1010000 = ((fanout << 16) | (depth << 24));
-                // with fanout = 1; depth = 0;
-                chainValue[1] = blake2s_IV[1];// ^ leafLength; with leafLength = 0;
-                chainValue[2] = blake2s_IV[2];// ^ nodeOffset; with nodeOffset = 0;
-                chainValue[3] = blake2s_IV[3];// ^ ( (nodeOffset << 32) | (nodeDepth << 16) | (innerHashLength << 24) );
-                // with nodeDepth = 0; innerHashLength = 0;
+                chainValue[0] = blake2s_IV[0]
+                    ^ (uint)(digestLength | (keyLength << 8) | ((fanout << 16) | (depth << 24)));
+                chainValue[1] = blake2s_IV[1] ^ (uint)leafLength;
+
+                int nofHi = (int)(nodeOffset >> 32);
+                int nofLo = (int)nodeOffset;
+                chainValue[2] = blake2s_IV[2] ^ (uint)nofLo;
+                chainValue[3] = blake2s_IV[3] ^ (uint)(nofHi | (nodeDepth << 16) | (innerHashLength << 24));
 
                 chainValue[4] = blake2s_IV[4];
                 chainValue[5] = blake2s_IV[5];
                 if (salt != null)
                 {
+                    if (salt.Length != 8)
+                        throw new ArgumentException("Salt length must be exactly 8 bytes");
+
+                    this.salt = new byte[8];
+                    Array.Copy(salt, 0, this.salt, 0, salt.Length);
+
                     chainValue[4] ^= Pack.LE_To_UInt32(salt, 0);
                     chainValue[5] ^= Pack.LE_To_UInt32(salt, 4);
                 }
@@ -273,6 +283,12 @@ namespace Org.BouncyCastle.Crypto.Digests
                 chainValue[7] = blake2s_IV[7];
                 if (personalization != null)
                 {
+                    if (personalization.Length != 8)
+                        throw new ArgumentException("Personalization length must be exactly 8 bytes");
+
+                    this.personalization = new byte[8];
+                    Array.Copy(personalization, 0, this.personalization, 0, personalization.Length);
+
                     chainValue[6] ^= Pack.LE_To_UInt32(personalization, 0);
                     chainValue[7] ^= Pack.LE_To_UInt32(personalization, 4);
                 }
@@ -295,12 +311,10 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @param b the input byte to be entered.
          */
-        public virtual void Update(byte b)
+        public void Update(byte b)
         {
-            int remainingLength; // left bytes of buffer
-
             // process the buffer if full else add to buffer:
-            remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+            int remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
             if (remainingLength == 0)
             { // full buffer
                 t0 += BLOCK_LENGTH_BYTES;
@@ -308,7 +322,11 @@ namespace Org.BouncyCastle.Crypto.Digests
                 { // if message > 2^32
                     t1++;
                 }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Compress(buffer);
+#else
                 Compress(buffer, 0);
+#endif
                 Array.Clear(buffer, 0, buffer.Length);// clear buffer
                 buffer[0] = b;
                 bufferPos = 1;
@@ -327,11 +345,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param offset  the offset into the byte array where the data starts.
          * @param len     the length of the data.
          */
-        public virtual void BlockUpdate(byte[] message, int offset, int len)
+        public void BlockUpdate(byte[] message, int offset, int len)
         {
             if (message == null || len == 0)
                 return;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(message.AsSpan(offset, len));
+#else
             int remainingLength = 0; // left bytes of buffer
 
             if (bufferPos != 0)
@@ -379,7 +400,63 @@ namespace Org.BouncyCastle.Crypto.Digests
             Array.Copy(message, messagePos, buffer, 0, offset + len
                 - messagePos);
             bufferPos += offset + len - messagePos;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (input.IsEmpty)
+                return;
+
+            int remainingLength = 0; // left bytes of buffer
+
+            if (bufferPos != 0)
+            { // commenced, incomplete buffer
+
+                // complete the buffer:
+                remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+                if (remainingLength < input.Length)
+                { // full buffer + at least 1 byte
+                    input[..remainingLength].CopyTo(buffer.AsSpan(bufferPos));
+                    t0 += BLOCK_LENGTH_BYTES;
+                    if (t0 == 0)
+                    { // if message > 2^32
+                        t1++;
+                    }
+                    Compress(buffer);
+                    bufferPos = 0;
+                    Array.Clear(buffer, 0, buffer.Length);// clear buffer
+                }
+                else
+                {
+                    input.CopyTo(buffer.AsSpan(bufferPos));
+                    bufferPos += input.Length;
+                    return;
+                }
+            }
+
+            // process blocks except last block (also if last block is full)
+            int messagePos;
+            int blockWiseLastPos = input.Length - BLOCK_LENGTH_BYTES;
+            for (messagePos = remainingLength;
+                 messagePos < blockWiseLastPos;
+                 messagePos += BLOCK_LENGTH_BYTES)
+            { // block wise 64 bytes
+                // without buffer:
+                t0 += BLOCK_LENGTH_BYTES;
+                if (t0 == 0)
+                {
+                    t1++;
+                }
+                Compress(input[messagePos..]);
+            }
+
+            // fill the buffer with left bytes, this might be a full block
+            input[messagePos..].CopyTo(buffer.AsSpan());
+            bufferPos += input.Length - messagePos;
         }
+#endif
 
         /**
          * Close the digest, producing the final digest value. The doFinal() call
@@ -388,8 +465,11 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param out       the array the digest is to be copied into.
          * @param outOffset the offset into the out array the digest is to start at.
          */
-        public virtual int DoFinal(byte[] output, int outOffset)
+        public int DoFinal(byte[] output, int outOffset)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOffset));
+#else
             f0 = 0xFFFFFFFFU;
             t0 += (uint)bufferPos;
             // bufferPos may be < 64, so (t0 == 0) does not work
@@ -402,19 +482,45 @@ namespace Org.BouncyCastle.Crypto.Digests
             Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
             Array.Clear(internalState, 0, internalState.Length);
 
-            byte[] bytes = new byte[4];
-            for (int i = 0; i < chainValue.Length && (i * 4 < digestLength); i++)
+            int full = digestLength >> 2, partial = digestLength & 3;
+            Pack.UInt32_To_LE(chainValue, 0, full, output, outOffset);
+            if (partial > 0)
             {
-                Pack.UInt32_To_LE(chainValue[i], bytes, 0);
+                byte[] bytes = new byte[4];
+                Pack.UInt32_To_LE(chainValue[full], bytes, 0);
+                Array.Copy(bytes, 0, output, outOffset + digestLength - partial, partial);
+            }
 
-                if (i * 4 < digestLength - 4)
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 4, 4);
-                }
-                else
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 4, digestLength - (i * 4));
-                }
+            Array.Clear(chainValue, 0, chainValue.Length);
+
+            Reset();
+
+            return digestLength;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            f0 = 0xFFFFFFFFU;
+            t0 += (uint)bufferPos;
+            // bufferPos may be < 64, so (t0 == 0) does not work
+            // for 2^32 < message length > 2^32 - 63
+            if ((t0 < 0) && (bufferPos > -t0))
+            {
+                t1++;
+            }
+            Compress(buffer);
+            Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
+            Array.Clear(internalState, 0, internalState.Length);
+
+            int full = digestLength >> 2, partial = digestLength & 3;
+            Pack.UInt32_To_LE(chainValue.AsSpan(0, full), output);
+            if (partial > 0)
+            {
+                Span<byte> bytes = stackalloc byte[4];
+                Pack.UInt32_To_LE(chainValue[full], bytes);
+                bytes[..partial].CopyTo(output[(digestLength - partial)..]);
             }
 
             Array.Clear(chainValue, 0, chainValue.Length);
@@ -423,12 +529,13 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             return digestLength;
         }
+#endif
 
         /**
          * Reset the digest back to its initial state. The key, the salt and the
          * personal string will remain for further computations.
          */
-        public virtual void Reset()
+        public void Reset()
         {
             bufferPos = 0;
             f0 = 0;
@@ -441,9 +548,39 @@ namespace Org.BouncyCastle.Crypto.Digests
                 Array.Copy(key, 0, buffer, 0, key.Length);
                 bufferPos = BLOCK_LENGTH_BYTES; // zero padding
             }
-            Init();
+
+            Init(this.salt, this.personalization, this.key);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> message)
+        {
+            InitializeInternalState();
+
+            Span<uint> m = stackalloc uint[16];
+            Pack.LE_To_UInt32(message, m);
+
+            for (int round = 0; round < ROUNDS; round++)
+            {
+                // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1
+                G(m[blake2s_sigma[round, 0]], m[blake2s_sigma[round, 1]], 0, 4, 8, 12);
+                G(m[blake2s_sigma[round, 2]], m[blake2s_sigma[round, 3]], 1, 5, 9, 13);
+                G(m[blake2s_sigma[round, 4]], m[blake2s_sigma[round, 5]], 2, 6, 10, 14);
+                G(m[blake2s_sigma[round, 6]], m[blake2s_sigma[round, 7]], 3, 7, 11, 15);
+                // G apply to diagonals of internalState:
+                G(m[blake2s_sigma[round, 8]], m[blake2s_sigma[round, 9]], 0, 5, 10, 15);
+                G(m[blake2s_sigma[round, 10]], m[blake2s_sigma[round, 11]], 1, 6, 11, 12);
+                G(m[blake2s_sigma[round, 12]], m[blake2s_sigma[round, 13]], 2, 7, 8, 13);
+                G(m[blake2s_sigma[round, 14]], m[blake2s_sigma[round, 15]], 3, 4, 9, 14);
+            }
+
+            // update chain values:
+            for (int offset = 0; offset < chainValue.Length; offset++)
+            {
+                chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
+            }
+        }
+#else
         private void Compress(byte[] message, int messagePos)
         {
             InitializeInternalState();
@@ -453,9 +590,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             for (int round = 0; round < ROUNDS; round++)
             {
-
-                // G apply to columns of internalState:m[blake2s_sigma[round][2 *
-                // blockPos]] /+1
+                // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1
                 G(m[blake2s_sigma[round,0]], m[blake2s_sigma[round,1]], 0, 4, 8, 12);
                 G(m[blake2s_sigma[round,2]], m[blake2s_sigma[round,3]], 1, 5, 9, 13);
                 G(m[blake2s_sigma[round,4]], m[blake2s_sigma[round,5]], 2, 6, 10, 14);
@@ -473,22 +608,21 @@ namespace Org.BouncyCastle.Crypto.Digests
                 chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
             }
         }
+#endif
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         private void G(uint m1, uint m2, int posA, int posB, int posC, int posD)
         {
             internalState[posA] = internalState[posA] + internalState[posB] + m1;
-            internalState[posD] = rotr32(internalState[posD] ^ internalState[posA], 16);
+            internalState[posD] = Integers.RotateRight(internalState[posD] ^ internalState[posA], 16);
             internalState[posC] = internalState[posC] + internalState[posD];
-            internalState[posB] = rotr32(internalState[posB] ^ internalState[posC], 12);
+            internalState[posB] = Integers.RotateRight(internalState[posB] ^ internalState[posC], 12);
             internalState[posA] = internalState[posA] + internalState[posB] + m2;
-            internalState[posD] = rotr32(internalState[posD] ^ internalState[posA], 8);
+            internalState[posD] = Integers.RotateRight(internalState[posD] ^ internalState[posA], 8);
             internalState[posC] = internalState[posC] + internalState[posD];
-            internalState[posB] = rotr32(internalState[posB] ^ internalState[posC], 7);
-        }
-
-        private uint rotr32(uint x, int rot)
-        {
-            return x >> rot | x << -rot;
+            internalState[posB] = Integers.RotateRight(internalState[posB] ^ internalState[posC], 7);
         }
 
         /**
@@ -496,17 +630,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return the algorithm name
          */
-        public virtual string AlgorithmName
-        {
-            get { return "BLAKE2s"; }
-        }
+        public string AlgorithmName => "BLAKE2s";
 
         /**
          * Return the size in bytes of the digest produced by this message digest.
          *
          * @return the size in bytes of the digest produced by this message digest.
          */
-        public virtual int GetDigestSize()
+        public int GetDigestSize()
         {
             return digestLength;
         }
@@ -517,7 +648,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return byte length of the digest's internal buffer.
          */
-        public virtual int GetByteLength()
+        public int GetByteLength()
         {
             return BLOCK_LENGTH_BYTES;
         }
@@ -525,7 +656,7 @@ namespace Org.BouncyCastle.Crypto.Digests
         /**
          * Overwrite the key if it is no longer used (zeroization).
          */
-        public virtual void ClearKey()
+        public void ClearKey()
         {
             if (key != null)
             {
@@ -538,7 +669,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Overwrite the salt (pepper) if it is secret and no longer used
          * (zeroization).
          */
-        public virtual void ClearSalt()
+        public void ClearSalt()
         {
             if (salt != null)
             {
diff --git a/crypto/src/crypto/digests/Blake2xsDigest.cs b/crypto/src/crypto/digests/Blake2xsDigest.cs
new file mode 100644
index 000000000..ac7e8f611
--- /dev/null
+++ b/crypto/src/crypto/digests/Blake2xsDigest.cs
@@ -0,0 +1,368 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+    /*
+      The BLAKE2 cryptographic hash function was designed by Jean-
+      Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn, and Christian
+      Winnerlein.
+
+      Reference Implementation and Description can be found at: https://blake2.net/blake2x.pdf
+     */
+
+    /**
+     * Implementation of the eXtendable Output Function (XOF) BLAKE2xs.
+     * <p/>
+     * BLAKE2xs offers a built-in keying mechanism to be used directly
+     * for authentication ("Prefix-MAC") rather than a HMAC construction.
+     * <p/>
+     * BLAKE2xs offers a built-in support for a salt for randomized hashing
+     * and a personal string for defining a unique hash function for each application.
+     * <p/>
+     * BLAKE2xs is optimized for 32-bit platforms and produces digests of any size
+     * between 1 and 2^16-2 bytes. The length can also be unknown and then the maximum
+     * length will be 2^32 blocks of 32 bytes.
+     */
+    public sealed class Blake2xsDigest
+        : IXof
+    {
+        /**
+         * Magic number to indicate an unknown length of digest
+         */
+        public const int UnknownDigestLength = 65535;
+
+        private const int DigestLength = 32;
+        private const long MaxNumberBlocks = 1L << 32;
+
+        /**
+         * Expected digest length for the xof. It can be unknown.
+         */
+        private int digestLength;
+
+        /**
+         * Root hash that will take the updates
+         */
+        private Blake2sDigest hash;
+
+        /**
+         * Digest of the root hash
+         */
+        private byte[] h0 = null;
+
+        /**
+         * Digest of each round of the XOF
+         */
+        private byte[] buf = new byte[32];
+
+        /**
+         * Current position for a round
+         */
+        private int bufPos = 32;
+
+        /**
+         * Overall position of the digest. It is useful when the length is known
+         * in advance to get last block length.
+         */
+        private int digestPos = 0;
+
+        /**
+         * Keep track of the round number to detect the end of the digest after
+         * 2^32 blocks of 32 bytes.
+         */
+        private long blockPos = 0;
+
+        /**
+         * Current node offset incremented by 1 every round.
+         */
+        private long nodeOffset;
+
+        /**
+         * BLAKE2xs for hashing with unknown digest length
+         */
+        public Blake2xsDigest()
+            : this(UnknownDigestLength)
+        {
+        }
+
+        /**
+         * BLAKE2xs for hashing
+         *
+         * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1
+         */
+        public Blake2xsDigest(int digestBytes)
+            : this(digestBytes, null, null, null)
+        {
+        }
+
+        /**
+         * BLAKE2xs with key
+         *
+         * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1
+         * @param key         A key up to 32 bytes or null
+         */
+        public Blake2xsDigest(int digestBytes, byte[] key)
+            : this(digestBytes, key, null, null)
+        {
+        }
+
+        /**
+         * BLAKE2xs with key, salt and personalization
+         *
+         * @param digestBytes     The desired digest length in bytes. Must be above 1 and less than 2^16-1
+         * @param key             A key up to 32 bytes or null
+         * @param salt            8 bytes or null
+         * @param personalization 8 bytes or null
+         */
+        public Blake2xsDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization)
+        {
+            if (digestBytes < 1 || digestBytes > UnknownDigestLength)
+                throw new ArgumentException("BLAKE2xs digest length must be between 1 and 2^16-1");
+
+            digestLength = digestBytes;
+            nodeOffset = ComputeNodeOffset();
+            hash = new Blake2sDigest(DigestLength, key, salt, personalization, nodeOffset);
+        }
+
+        public Blake2xsDigest(Blake2xsDigest digest)
+        {
+            digestLength = digest.digestLength;
+            hash = new Blake2sDigest(digest.hash);
+            h0 = Arrays.Clone(digest.h0);
+            buf = Arrays.Clone(digest.buf);
+            bufPos = digest.bufPos;
+            digestPos = digest.digestPos;
+            blockPos = digest.blockPos;
+            nodeOffset = digest.nodeOffset;
+        }
+
+        /**
+         * Return the algorithm name.
+         *
+         * @return the algorithm name
+         */
+        public string AlgorithmName => "BLAKE2xs";
+
+        /**
+         * Return the size in bytes of the digest produced by this message digest.
+         *
+         * @return the size in bytes of the digest produced by this message digest.
+         */
+        public int GetDigestSize() => digestLength;
+
+        /**
+         * Return the size in bytes of the internal buffer the digest applies its
+         * compression function to.
+         *
+         * @return byte length of the digest's internal buffer.
+         */
+        public int GetByteLength() => hash.GetByteLength();
+
+        /**
+         * Return the maximum size in bytes the digest can produce when the length
+         * is unknown
+         *
+         * @return byte length of the largest digest with unknown length
+         */
+        public long GetUnknownMaxLength()
+        {
+            return MaxNumberBlocks * DigestLength;
+        }
+
+        /**
+         * Update the message digest with a single byte.
+         *
+         * @param in the input byte to be entered.
+         */
+        public void Update(byte b)
+        {
+            hash.Update(b);
+        }
+
+        /**
+         * Update the message digest with a block of bytes.
+         *
+         * @param in    the byte array containing the data.
+         * @param inOff the offset into the byte array where the data starts.
+         * @param len   the length of the data.
+         */
+        public void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            hash.BlockUpdate(input, inOff, inLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            hash.BlockUpdate(input);
+        }
+#endif
+
+        /**
+         * Reset the digest back to its initial state. The key, the salt and the
+         * personal string will remain for further computations.
+         */
+        public void Reset()
+        {
+            hash.Reset();
+
+            h0 = null;
+            bufPos = DigestLength;
+            digestPos = 0;
+            blockPos = 0;
+            nodeOffset = ComputeNodeOffset();
+        }
+
+        /**
+         * Close the digest, producing the final digest value. The doFinal() call
+         * leaves the digest reset. Key, salt and personal string remain.
+         *
+         * @param out       the array the digest is to be copied into.
+         * @param outOffset the offset into the out array the digest is to start at.
+         */
+        public int DoFinal(byte[] output, int outOff)
+        {
+            return OutputFinal(output, outOff, digestLength);
+        }
+
+        /**
+         * Close the digest, producing the final digest value. The doFinal() call
+         * leaves the digest reset. Key, salt, personal string remain.
+         *
+         * @param out    output array to write the output bytes to.
+         * @param outOff offset to start writing the bytes at.
+         * @param outLen the number of output bytes requested.
+         */
+        public int OutputFinal(byte[] output, int outOff, int outLen)
+        {
+            int ret = Output(output, outOff, outLen);
+
+            Reset();
+
+            return ret;
+        }
+
+        /**
+         * Start outputting the results of the final calculation for this digest. Unlike doFinal, this method
+         * will continue producing output until the Xof is explicitly reset, or signals otherwise.
+         *
+         * @param out    output array to write the output bytes to.
+         * @param outOff offset to start writing the bytes at.
+         * @param outLen the number of output bytes requested.
+         * @return the number of bytes written
+         */
+        public int Output(byte[] output, int outOff, int outLen)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Output(output.AsSpan(outOff, outLen));
+#else
+            if (h0 == null)
+            {
+                h0 = new byte[hash.GetDigestSize()];
+                hash.DoFinal(h0, 0);
+            }
+
+            if (digestLength != UnknownDigestLength)
+            {
+                if (digestPos + outLen > digestLength)
+                    throw new ArgumentException("Output length is above the digest length");
+            }
+            else if (blockPos << 5 >= GetUnknownMaxLength())
+            {
+                throw new ArgumentException("Maximum length is 2^32 blocks of 32 bytes");
+            }
+
+            for (int i = 0; i < outLen; i++)
+            {
+                if (bufPos >= DigestLength)
+                {
+                    Blake2sDigest h = new Blake2sDigest(ComputeStepLength(), DigestLength, nodeOffset);
+                    h.BlockUpdate(h0, 0, h0.Length);
+
+                    Arrays.Fill(buf, 0);
+                    h.DoFinal(buf, 0);
+                    bufPos = 0;
+                    nodeOffset++;
+                    blockPos++;
+                }
+                output[outOff + i] = buf[bufPos];
+                bufPos++;
+                digestPos++;
+            }
+
+            return outLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..digestLength]);
+        }
+
+        public int OutputFinal(Span<byte> output)
+        {
+            int ret = Output(output);
+
+            Reset();
+
+            return ret;
+        }
+
+        public int Output(Span<byte> output)
+        {
+            int outLen = output.Length;
+            if (h0 == null)
+            {
+                h0 = new byte[hash.GetDigestSize()];
+                hash.DoFinal(h0);
+            }
+
+            if (digestLength != UnknownDigestLength)
+            {
+                if (digestPos + outLen > digestLength)
+                    throw new ArgumentException("Output length is above the digest length");
+            }
+            else if (blockPos << 5 >= GetUnknownMaxLength())
+            {
+                throw new ArgumentException("Maximum length is 2^32 blocks of 32 bytes");
+            }
+
+            for (int i = 0; i < outLen; i++)
+            {
+                if (bufPos >= DigestLength)
+                {
+                    Blake2sDigest h = new Blake2sDigest(ComputeStepLength(), DigestLength, nodeOffset);
+                    h.BlockUpdate(h0);
+
+                    Arrays.Fill(buf, 0);
+                    h.DoFinal(buf);
+                    bufPos = 0;
+                    nodeOffset++;
+                    blockPos++;
+                }
+                output[i] = buf[bufPos];
+                bufPos++;
+                digestPos++;
+            }
+
+            return outLen;
+        }
+#endif
+
+        // get the next round length. If the length is unknown, the digest length is always the maximum.
+        private int ComputeStepLength()
+        {
+            if (digestLength == UnknownDigestLength)
+                return DigestLength;
+
+            return System.Math.Min(DigestLength, digestLength - digestPos);
+        }
+
+        private long ComputeNodeOffset()
+        {
+            return digestLength * 0x100000000L;
+        }
+    }
+}
diff --git a/crypto/src/crypto/digests/Blake3Digest.cs b/crypto/src/crypto/digests/Blake3Digest.cs
new file mode 100644
index 000000000..720af805d
--- /dev/null
+++ b/crypto/src/crypto/digests/Blake3Digest.cs
@@ -0,0 +1,1047 @@
+using System;
+using System.Collections.Generic;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+    public sealed class Blake3Digest
+        : IDigest, IMemoable, IXof
+    {
+        /**
+         * Already outputting error.
+         */
+        private const string ERR_OUTPUTTING = "Already outputting";
+
+        /**
+         * Number of Words.
+         */
+        private const int NUMWORDS = 8;
+
+        /**
+         * Number of Rounds.
+         */
+        private const int ROUNDS = 7;
+
+        /**
+         * Buffer length.
+         */
+        private const int BLOCKLEN = NUMWORDS * Integers.NumBytes * 2;
+
+        /**
+         * Chunk length.
+         */
+        private const int CHUNKLEN = 1024;
+
+        /**
+         * ChunkStart Flag.
+         */
+        private const int CHUNKSTART = 1;
+
+        /**
+         * ChunkEnd Flag.
+         */
+        private const int CHUNKEND = 2;
+
+        /**
+         * Parent Flag.
+         */
+        private const int PARENT = 4;
+
+        /**
+         * Root Flag.
+         */
+        private const int ROOT = 8;
+
+        /**
+         * KeyedHash Flag.
+         */
+        private const int KEYEDHASH = 16;
+
+        /**
+         * DeriveContext Flag.
+         */
+        private const int DERIVECONTEXT = 32;
+
+        /**
+         * DeriveKey Flag.
+         */
+        private const int DERIVEKEY = 64;
+
+        /**
+         * Chaining0 State Locations.
+         */
+        private const int CHAINING0 = 0;
+
+        /**
+         * Chaining1 State Location.
+         */
+        private const int CHAINING1 = 1;
+
+        /**
+         * Chaining2 State Location.
+         */
+        private const int CHAINING2 = 2;
+
+        /**
+         * Chaining3 State Location.
+         */
+        private const int CHAINING3 = 3;
+
+        /**
+         * Chaining4 State Location.
+         */
+        private const int CHAINING4 = 4;
+
+        /**
+         * Chaining5 State Location.
+         */
+        private const int CHAINING5 = 5;
+
+        /**
+         * Chaining6 State Location.
+         */
+        private const int CHAINING6 = 6;
+
+        /**
+         * Chaining7 State Location.
+         */
+        private const int CHAINING7 = 7;
+
+        /**
+         * IV0 State Locations.
+         */
+        private const int IV0 = 8;
+
+        /**
+         * IV1 State Location.
+         */
+        private const int IV1 = 9;
+
+        /**
+         * IV2 State Location.
+         */
+        private const int IV2 = 10;
+
+        /**
+         * IV3 State Location.
+         */
+        private const int IV3 = 11;
+
+        /**
+         * Count0 State Location.
+         */
+        private const int COUNT0 = 12;
+
+        /**
+         * Count1 State Location.
+         */
+        private const int COUNT1 = 13;
+
+        /**
+         * DataLen State Location.
+         */
+        private const int DATALEN = 14;
+
+        /**
+         * Flags State Location.
+         */
+        private const int FLAGS = 15;
+
+        /**
+         * Message word permutations.
+         */
+        private static readonly byte[] SIGMA = { 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 };
+
+        /**
+         * Blake3 Initialization Vector.
+         */
+        private static readonly uint[] IV = {
+            0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+        };
+
+        /**
+         * The byte input/output buffer.
+         */
+        private readonly byte[] m_theBuffer = new byte[BLOCKLEN];
+
+        /**
+         * The key.
+         */
+        private readonly uint[] m_theK = new uint[NUMWORDS];
+
+        /**
+         * The chaining value.
+         */
+        private readonly uint[] m_theChaining = new uint[NUMWORDS];
+
+        /**
+         * The state.
+         */
+        private readonly uint[] m_theV = new uint[NUMWORDS << 1];
+
+        /**
+         * The message Buffer.
+         */
+        private readonly uint[] m_theM = new uint[NUMWORDS << 1];
+
+        /**
+         * The indices.
+         */
+        private readonly byte[] m_theIndices = new byte[NUMWORDS << 1];
+
+        /**
+         * The chainingStack.
+         */
+        private readonly List<uint[]> m_theStack = new List<uint[]>();
+
+        /**
+         * The default digestLength.
+         */
+        private readonly int m_theDigestLen;
+
+        /**
+         * Are we outputting?
+         */
+        private bool m_outputting;
+
+        /**
+         * How many more bytes can we output?
+         */
+        private long m_outputAvailable;
+
+        /**
+         * The current mode.
+         */
+        private int m_theMode;
+
+        /**
+         * The output mode.
+         */
+        private int m_theOutputMode;
+
+        /**
+         * The output dataLen.
+         */
+        private int m_theOutputDataLen;
+
+        /**
+         * The block counter.
+         */
+        private long m_theCounter;
+
+        /**
+         * The # of bytes in the current block.
+         */
+        private int m_theCurrBytes;
+
+        /**
+         * The position of the next byte in the buffer.
+         */
+        private int m_thePos;
+
+        public Blake3Digest()
+            : this((BLOCKLEN >> 1) * 8)
+        {
+        }
+
+        /// <param name="pDigestSize">the default digest size (in bits)</param>
+        public Blake3Digest(int pDigestSize)
+        {
+            m_theDigestLen = pDigestSize / 8;
+
+            Init(null);
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param pSource the source digest.
+         */
+        public Blake3Digest(Blake3Digest pSource)
+        {
+            /* Copy default digest length */
+            m_theDigestLen = pSource.m_theDigestLen;
+
+            /* Initialise from source */
+            Reset(pSource);
+        }
+
+        public int GetByteLength() => BLOCKLEN;
+
+        public string AlgorithmName => "BLAKE3";
+
+        public int GetDigestSize() => m_theDigestLen;
+
+        /**
+         * Initialise.
+         *
+         * @param pParams the parameters.
+         */
+        public void Init(Blake3Parameters pParams)
+        {
+            /* Access key/context */
+            byte[] myKey = pParams?.GetKey();
+            byte[] myContext = pParams?.GetContext();
+
+            /* Reset the digest */
+            Reset();
+
+            /* If we have a key  */
+            if (myKey != null)
+            {
+                /* Initialise with the key */
+                InitKey(myKey);
+                Arrays.Fill(myKey, 0);
+
+                /* else if we have a context */
+            }
+            else if (myContext != null)
+            {
+                /* Initialise for deriving context */
+                InitNullKey();
+                m_theMode = DERIVECONTEXT;
+
+                /* Derive key from context */
+                BlockUpdate(myContext, 0, myContext.Length);
+                DoFinal(m_theBuffer, 0);
+                InitKeyFromContext();
+                Reset();
+
+                /* Else init null key and reset mode */
+            }
+            else
+            {
+                InitNullKey();
+                m_theMode = 0;
+            }
+        }
+
+        public void Update(byte b)
+        {
+            /* Check that we are not outputting */
+            if (m_outputting)
+                throw new InvalidOperationException(ERR_OUTPUTTING);
+
+            /* If the buffer is full */
+            int blockLen = m_theBuffer.Length;
+            int remainingLength = blockLen - m_thePos;
+            if (remainingLength == 0)
+            {
+                /* Process the buffer */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                CompressBlock(m_theBuffer);
+#else
+                CompressBlock(m_theBuffer, 0);
+#endif
+
+                /* Reset the buffer */
+                Arrays.Fill(m_theBuffer, 0);
+                m_thePos = 0;
+            }
+
+            /* Store the byte */
+            m_theBuffer[m_thePos] = b;
+            m_thePos++;
+        }
+
+        public void BlockUpdate(byte[] pMessage, int pOffset, int pLen)
+        {
+            /* Ignore null operation */
+            if (pMessage == null)
+                return;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(pMessage.AsSpan(pOffset, pLen));
+#else
+            if (pLen == 0)
+                return;
+
+            /* Check that we are not outputting */
+            if (m_outputting)
+                throw new InvalidOperationException(ERR_OUTPUTTING);
+
+            /* Process any bytes currently in the buffer */
+            int remainingLen = 0; // left bytes of buffer
+            if (m_thePos != 0)
+            {
+                /* Calculate space remaining in the buffer */
+                remainingLen = BLOCKLEN - m_thePos;
+
+                /* If there is sufficient space in the buffer */
+                if (remainingLen >= pLen)
+                {
+                    /* Copy data into buffer and return */
+                    Array.Copy(pMessage, pOffset, m_theBuffer, m_thePos, pLen);
+                    m_thePos += pLen;
+                    return;
+                }
+
+                /* Fill the buffer */
+                Array.Copy(pMessage, pOffset, m_theBuffer, m_thePos, remainingLen);
+
+                /* Process the buffer */
+                CompressBlock(m_theBuffer, 0);
+
+                /* Reset the buffer */
+                m_thePos = 0;
+                Arrays.Fill(m_theBuffer, 0);
+            }
+
+            /* process all blocks except the last one */
+            int messagePos;
+            int blockWiseLastPos = pOffset + pLen - BLOCKLEN;
+            for (messagePos = pOffset + remainingLen; messagePos < blockWiseLastPos; messagePos += BLOCKLEN)
+            {
+                /* Process the buffer */
+                CompressBlock(pMessage, messagePos);
+            }
+
+            /* Fill the buffer with the remaining bytes of the message */
+            int len = pLen - messagePos;
+            Array.Copy(pMessage, messagePos, m_theBuffer, 0, pOffset + len);
+            m_thePos += pOffset + len;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (input.IsEmpty)
+                return;
+
+            /* Check that we are not outputting */
+            if (m_outputting)
+                throw new InvalidOperationException(ERR_OUTPUTTING);
+
+            int pLen = input.Length;
+
+            /* Process any bytes currently in the buffer */
+            int remainingLen = 0; // left bytes of buffer
+            if (m_thePos != 0)
+            {
+                /* Calculate space remaining in the buffer */
+                remainingLen = BLOCKLEN - m_thePos;
+
+                /* If there is sufficient space in the buffer */
+                if (remainingLen >= pLen)
+                {
+                    /* Copy data into buffer and return */
+                    input.CopyTo(m_theBuffer.AsSpan(m_thePos));
+                    m_thePos += pLen;
+                    return;
+                }
+
+                /* Fill the buffer */
+                input[..remainingLen].CopyTo(m_theBuffer.AsSpan(m_thePos));
+
+                /* Process the buffer */
+                CompressBlock(m_theBuffer);
+
+                /* Reset the buffer */
+                m_thePos = 0;
+                Arrays.Fill(m_theBuffer, 0);
+            }
+
+            /* process all blocks except the last one */
+            int messagePos;
+            int blockWiseLastPos = pLen - BLOCKLEN;
+            for (messagePos = remainingLen; messagePos < blockWiseLastPos; messagePos += BLOCKLEN)
+            {
+                /* Process the buffer */
+                CompressBlock(input[messagePos..]);
+            }
+
+            /* Fill the buffer with the remaining bytes of the message */
+            input[messagePos..].CopyTo(m_theBuffer);
+            m_thePos += pLen - messagePos;
+        }
+#endif
+
+        public int DoFinal(byte[] pOutput, int pOutOffset)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return OutputFinal(pOutput.AsSpan(pOutOffset, GetDigestSize()));
+#else
+            return OutputFinal(pOutput, pOutOffset, GetDigestSize());
+#endif
+        }
+
+        public int OutputFinal(byte[] pOut, int pOutOffset, int pOutLen)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return OutputFinal(pOut.AsSpan(pOutOffset, pOutLen));
+#else
+            /* Reject if we are already outputting */
+            if (m_outputting)
+                throw new InvalidOperationException(ERR_OUTPUTTING);
+
+            /* Build the required output */
+            int length = Output(pOut, pOutOffset, pOutLen);
+
+            /* reset the underlying digest and return the length */
+            Reset();
+            return length;
+#endif
+        }
+
+        public int Output(byte[] pOut, int pOutOffset, int pOutLen)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Output(pOut.AsSpan(pOutOffset, pOutLen));
+#else
+            /* If we have not started outputting yet */
+            if (!m_outputting)
+            {
+                /* Process the buffer */
+                CompressFinalBlock(m_thePos);
+            }
+
+            /* Reject if there is insufficient Xof remaining */
+            if (pOutLen < 0 || (m_outputAvailable >= 0 && pOutLen > m_outputAvailable))
+                throw new ArgumentException("Insufficient bytes remaining");
+
+            /* If we have some remaining data in the current buffer */
+            int dataLeft = pOutLen;
+            int outPos = pOutOffset;
+            if (m_thePos < BLOCKLEN)
+            {
+                /* Copy data from current hash */
+                int dataToCopy = System.Math.Min(dataLeft, BLOCKLEN - m_thePos);
+                Array.Copy(m_theBuffer, m_thePos, pOut, outPos, dataToCopy);
+
+                /* Adjust counters */
+                m_thePos += dataToCopy;
+                outPos += dataToCopy;
+                dataLeft -= dataToCopy;
+            }
+
+            /* Loop until we have completed the request */
+            while (dataLeft > 0)
+            {
+                /* Calculate the next block */
+                NextOutputBlock();
+
+                /* Copy data from current hash */
+                int dataToCopy = System.Math.Min(dataLeft, BLOCKLEN);
+                Array.Copy(m_theBuffer, 0, pOut, outPos, dataToCopy);
+
+                /* Adjust counters */
+                m_thePos += dataToCopy;
+                outPos += dataToCopy;
+                dataLeft -= dataToCopy;
+            }
+
+            /* Adjust outputAvailable */
+            m_outputAvailable -= pOutLen;
+
+            /* Return the number of bytes transferred */
+            return pOutLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..GetDigestSize()]);
+        }
+
+        public int OutputFinal(Span<byte> output)
+        {
+            /* Reject if we are already outputting */
+            if (m_outputting)
+                throw new InvalidOperationException(ERR_OUTPUTTING);
+
+            /* Build the required output */
+            int length = Output(output);
+
+            /* reset the underlying digest and return the length */
+            Reset();
+            return length;
+        }
+
+        public int Output(Span<byte> output)
+        {
+            /* If we have not started outputting yet */
+            if (!m_outputting)
+            {
+                /* Process the buffer */
+                CompressFinalBlock(m_thePos);
+            }
+
+            int pOutOffset = 0, pOutLen = output.Length;
+            /* Reject if there is insufficient Xof remaining */
+            if (pOutLen < 0 || (m_outputAvailable >= 0 && pOutLen > m_outputAvailable))
+                throw new ArgumentException("Insufficient bytes remaining");
+
+            /* If we have some remaining data in the current buffer */
+            int dataLeft = pOutLen;
+            int outPos = pOutOffset;
+            if (m_thePos < BLOCKLEN)
+            {
+                /* Copy data from current hash */
+                int dataToCopy = System.Math.Min(dataLeft, BLOCKLEN - m_thePos);
+                m_theBuffer.AsSpan(m_thePos, dataToCopy).CopyTo(output[outPos..]);
+
+                /* Adjust counters */
+                m_thePos += dataToCopy;
+                outPos += dataToCopy;
+                dataLeft -= dataToCopy;
+            }
+
+            /* Loop until we have completed the request */
+            while (dataLeft > 0)
+            {
+                /* Calculate the next block */
+                NextOutputBlock();
+
+                /* Copy data from current hash */
+                int dataToCopy = System.Math.Min(dataLeft, BLOCKLEN);
+                m_theBuffer.AsSpan(0, dataToCopy).CopyTo(output[outPos..]);
+
+                /* Adjust counters */
+                m_thePos += dataToCopy;
+                outPos += dataToCopy;
+                dataLeft -= dataToCopy;
+            }
+
+            /* Adjust outputAvailable */
+            m_outputAvailable -= pOutLen;
+
+            /* Return the number of bytes transferred */
+            return pOutLen;
+        }
+#endif
+
+        public void Reset()
+        {
+            ResetBlockCount();
+            m_thePos = 0;
+            m_outputting = false;
+            Arrays.Fill(m_theBuffer, 0);
+        }
+
+        public void Reset(IMemoable pSource)
+        {
+            /* Access source */
+            Blake3Digest mySource = (Blake3Digest)pSource;
+
+            /*  Reset counter */
+            m_theCounter = mySource.m_theCounter;
+            m_theCurrBytes = mySource.m_theCurrBytes;
+            m_theMode = mySource.m_theMode;
+
+            /* Reset output state */
+            m_outputting = mySource.m_outputting;
+            m_outputAvailable = mySource.m_outputAvailable;
+            m_theOutputMode = mySource.m_theOutputMode;
+            m_theOutputDataLen = mySource.m_theOutputDataLen;
+
+            /* Copy state */
+            Array.Copy(mySource.m_theChaining, 0, m_theChaining, 0, m_theChaining.Length);
+            Array.Copy(mySource.m_theK, 0, m_theK, 0, m_theK.Length);
+            Array.Copy(mySource.m_theM, 0, m_theM, 0, m_theM.Length);
+
+            /* Copy stack */
+            m_theStack.Clear();
+            foreach (var element in mySource.m_theStack)
+            {
+                m_theStack.Add(Arrays.Clone(element));
+            }
+
+            /* Copy buffer */
+            Array.Copy(mySource.m_theBuffer, 0, m_theBuffer, 0, m_theBuffer.Length);
+            m_thePos = mySource.m_thePos;
+        }
+
+        public IMemoable Copy()
+        {
+            return new Blake3Digest(this);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void CompressBlock(ReadOnlySpan<byte> block)
+        {
+            /* Initialise state and compress message */
+            InitChunkBlock(BLOCKLEN, false);
+            InitM(block);
+            Compress();
+
+            /* Adjust stack if we have completed a block */
+            if (m_theCurrBytes == 0)
+            {
+                AdjustStack();
+            }
+        }
+
+        private void InitM(ReadOnlySpan<byte> block)
+        {
+            /* Copy message bytes into word array */
+            Pack.LE_To_UInt32(block, m_theM);
+        }
+#else
+        /**
+         * Compress next block of the message.
+         *
+         * @param pMessage the message buffer
+         * @param pMsgPos  the position within the message buffer
+         */
+        private void CompressBlock(byte[] pMessage, int pMsgPos)
+        {
+            /* Initialise state and compress message */
+            InitChunkBlock(BLOCKLEN, false);
+            InitM(pMessage, pMsgPos);
+            Compress();
+
+            /* Adjust stack if we have completed a block */
+            if (m_theCurrBytes == 0)
+            {
+                AdjustStack();
+            }
+        }
+
+        /**
+         * Initialise M from message.
+         *
+         * @param pMessage the source message
+         * @param pMsgPos  the message position
+         */
+        private void InitM(byte[] pMessage, int pMsgPos)
+        {
+            /* Copy message bytes into word array */
+            Pack.LE_To_UInt32(pMessage, pMsgPos, m_theM);
+        }
+#endif
+
+        /**
+         * Adjust the stack.
+         */
+        private void AdjustStack()
+        {
+            /* Loop to combine blocks */
+            long myCount = m_theCounter;
+            while (myCount > 0)
+            {
+                /* Break loop if we are not combining */
+                if ((myCount & 1) == 1)
+                    break;
+
+                /* Build the message to be hashed */
+                uint[] myLeft = m_theStack[m_theStack.Count - 1];
+                m_theStack.RemoveAt(m_theStack.Count - 1);
+
+                Array.Copy(myLeft, 0, m_theM, 0, NUMWORDS);
+                Array.Copy(m_theChaining, 0, m_theM, NUMWORDS, NUMWORDS);
+
+                /* Create parent block */
+                InitParentBlock();
+                Compress();
+
+                /* Next block */
+                myCount >>= 1;
+            }
+
+            /* Add back to the stack */
+            m_theStack.Add(Arrays.CopyOf(m_theChaining, NUMWORDS));
+        }
+
+        /**
+         * Compress final block.
+         *
+         * @param pDataLen the data length
+         */
+        private void CompressFinalBlock(int pDataLen)
+        {
+            /* Initialise state and compress message */
+            InitChunkBlock(pDataLen, true);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            InitM(m_theBuffer);
+#else
+            InitM(m_theBuffer, 0);
+#endif
+            Compress();
+
+            /* Finalise stack */
+            ProcessStack();
+        }
+
+        /**
+         * Process the stack.
+         */
+        private void ProcessStack()
+        {
+            /* Finalise stack */
+            while (m_theStack.Count > 0)
+            {
+                /* Build the message to be hashed */
+                uint[] myLeft = m_theStack[m_theStack.Count - 1];
+                m_theStack.RemoveAt(m_theStack.Count - 1);
+
+                Array.Copy(myLeft, 0, m_theM, 0, NUMWORDS);
+                Array.Copy(m_theChaining, 0, m_theM, NUMWORDS, NUMWORDS);
+
+                /* Create parent block */
+                InitParentBlock();
+                if (m_theStack.Count < 1)
+                {
+                    SetRoot();
+                }
+                Compress();
+            }
+        }
+
+        /**
+         * Perform compression.
+         */
+        private void Compress()
+        {
+            /* Initialise the buffers */
+            InitIndices();
+
+            /* Loop through the rounds */
+            for (int round = 0; round < ROUNDS - 1; round++)
+            {
+                /* Perform the round and permuteM */
+                PerformRound();
+                PermuteIndices();
+            }
+            PerformRound();
+            AdjustChaining();
+        }
+
+        /**
+         * Perform a round.
+         */
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+        private void PerformRound()
+        {
+            /* Apply to columns of V */
+            MixG(0, CHAINING0, CHAINING4, IV0, COUNT0);
+            MixG(1, CHAINING1, CHAINING5, IV1, COUNT1);
+            MixG(2, CHAINING2, CHAINING6, IV2, DATALEN);
+            MixG(3, CHAINING3, CHAINING7, IV3, FLAGS);
+
+            /* Apply to diagonals of V */
+            MixG(4, CHAINING0, CHAINING5, IV2, FLAGS);
+            MixG(5, CHAINING1, CHAINING6, IV3, COUNT0);
+            MixG(6, CHAINING2, CHAINING7, IV0, COUNT1);
+            MixG(7, CHAINING3, CHAINING4, IV1, DATALEN);
+        }
+
+        /**
+         * Adjust Chaining after compression.
+         */
+        private void AdjustChaining()
+        {
+            /* If we are outputting */
+            if (m_outputting)
+            {
+                /* Adjust full state */
+                for (int i = 0; i < NUMWORDS; i++)
+                {
+                    m_theV[i] ^= m_theV[i + NUMWORDS];
+                    m_theV[i + NUMWORDS] ^= m_theChaining[i];
+                }
+
+                /* Output state to buffer */
+                Pack.UInt32_To_LE(m_theV, m_theBuffer, 0);
+                m_thePos = 0;
+
+                /* Else just build chain value */
+            }
+            else
+            {
+                /* Combine V into Chaining */
+                for (int i = 0; i < NUMWORDS; i++)
+                {
+                    m_theChaining[i] = m_theV[i] ^ m_theV[i + NUMWORDS];
+                }
+            }
+        }
+
+        /**
+         * Mix function G.
+         *
+         * @param msgIdx the message index
+         * @param posA   position A in V
+         * @param posB   position B in V
+         * @param posC   position C in V
+         * @param posD   poistion D in V
+         */
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+        private void MixG(int msgIdx, int posA, int posB, int posC, int posD)
+        {
+            /* Determine indices */
+            int msg = msgIdx << 1;
+
+            /* Perform the Round */
+            m_theV[posA] += m_theV[posB] + m_theM[m_theIndices[msg++]];
+            m_theV[posD] = Integers.RotateRight(m_theV[posD] ^ m_theV[posA], 16);
+            m_theV[posC] += m_theV[posD];
+            m_theV[posB] = Integers.RotateRight(m_theV[posB] ^ m_theV[posC], 12);
+            m_theV[posA] += m_theV[posB] + m_theM[m_theIndices[msg]];
+            m_theV[posD] = Integers.RotateRight(m_theV[posD] ^ m_theV[posA], 8);
+            m_theV[posC] += m_theV[posD];
+            m_theV[posB] = Integers.RotateRight(m_theV[posB] ^ m_theV[posC], 7);
+        }
+
+        /**
+         * initialise the indices.
+         */
+        private void InitIndices()
+        {
+            for (byte i = 0; i < m_theIndices.Length; i++)
+            {
+                m_theIndices[i] = i;
+            }
+        }
+
+        /**
+         * PermuteIndices.
+         */
+        private void PermuteIndices()
+        {
+            for (byte i = 0; i < m_theIndices.Length; i++)
+            {
+                m_theIndices[i] = SIGMA[m_theIndices[i]];
+            }
+        }
+
+        /**
+         * Initialise null key.
+         */
+        private void InitNullKey()
+        {
+            Array.Copy(IV, 0, m_theK, 0, NUMWORDS);
+        }
+
+        /**
+         * Initialise key.
+         *
+         * @param pKey the keyBytes
+         */
+        private void InitKey(byte[] pKey)
+        {
+            /* Copy message bytes into word array */
+            Pack.LE_To_UInt32(pKey, 0, m_theK);
+            m_theMode = KEYEDHASH;
+        }
+
+        /**
+         * Initialise key from context.
+         */
+        private void InitKeyFromContext()
+        {
+            Array.Copy(m_theV, 0, m_theK, 0, NUMWORDS);
+            m_theMode = DERIVEKEY;
+        }
+
+        /**
+         * Initialise chunk block.
+         *
+         * @param pDataLen the dataLength
+         * @param pFinal   is this the final chunk?
+         */
+        private void InitChunkBlock(int pDataLen, bool pFinal)
+        {
+            /* Initialise the block */
+            Array.Copy(m_theCurrBytes == 0 ? m_theK : m_theChaining, 0, m_theV, 0, NUMWORDS);
+            Array.Copy(IV, 0, m_theV, NUMWORDS, NUMWORDS >> 1);
+            m_theV[COUNT0] = (uint)m_theCounter;
+            m_theV[COUNT1] = (uint)(m_theCounter >> Integers.NumBits);
+            m_theV[DATALEN] = (uint)pDataLen;
+            m_theV[FLAGS] = (uint)(m_theMode
+                + (m_theCurrBytes == 0 ? CHUNKSTART : 0)
+                + (pFinal ? CHUNKEND : 0));
+
+            /* * Adjust block count */
+            m_theCurrBytes += pDataLen;
+            if (m_theCurrBytes >= CHUNKLEN)
+            {
+                IncrementBlockCount();
+                m_theV[FLAGS] |= CHUNKEND;
+            }
+
+            /* If we are single chunk */
+            if (pFinal && m_theStack.Count < 1)
+            {
+                SetRoot();
+            }
+        }
+
+        /**
+         * Initialise parent block.
+         */
+        private void InitParentBlock()
+        {
+            /* Initialise the block */
+            Array.Copy(m_theK, 0, m_theV, 0, NUMWORDS);
+            Array.Copy(IV, 0, m_theV, NUMWORDS, NUMWORDS >> 1);
+            m_theV[COUNT0] = 0;
+            m_theV[COUNT1] = 0;
+            m_theV[DATALEN] = BLOCKLEN;
+            m_theV[FLAGS] = (uint)(m_theMode | PARENT);
+        }
+
+        /**
+         * Initialise output block.
+         */
+        private void NextOutputBlock()
+        {
+            /* Increment the counter */
+            m_theCounter++;
+
+            /* Initialise the block */
+            Array.Copy(m_theChaining, 0, m_theV, 0, NUMWORDS);
+            Array.Copy(IV, 0, m_theV, NUMWORDS, NUMWORDS >> 1);
+            m_theV[COUNT0] = (uint)m_theCounter;
+            m_theV[COUNT1] = (uint)(m_theCounter >> Integers.NumBits);
+            m_theV[DATALEN] = (uint)m_theOutputDataLen;
+            m_theV[FLAGS] = (uint)m_theOutputMode;
+
+            /* Generate output */
+            Compress();
+        }
+
+        /**
+         * IncrementBlockCount.
+         */
+        private void IncrementBlockCount()
+        {
+            m_theCounter++;
+            m_theCurrBytes = 0;
+        }
+
+        /**
+         * ResetBlockCount.
+         */
+        private void ResetBlockCount()
+        {
+            m_theCounter = 0;
+            m_theCurrBytes = 0;
+        }
+
+        /**
+         * Set root indication.
+         */
+        private void SetRoot()
+        {
+            m_theV[FLAGS] |= ROOT;
+            m_theOutputMode = (int)m_theV[FLAGS];
+            m_theOutputDataLen = (int)m_theV[DATALEN];
+            m_theCounter = 0;
+            m_outputting = true;
+            m_outputAvailable = -1;
+            Array.Copy(m_theV, 0, m_theChaining, 0, NUMWORDS);
+        }
+    }
+}
diff --git a/crypto/src/crypto/digests/CSHAKEDigest.cs b/crypto/src/crypto/digests/CSHAKEDigest.cs
index c3b0b7068..15a6c6da0 100644
--- a/crypto/src/crypto/digests/CSHAKEDigest.cs
+++ b/crypto/src/crypto/digests/CSHAKEDigest.cs
@@ -78,11 +78,11 @@ namespace Org.BouncyCastle.Crypto.Digests
             get { return "CSHAKE" + fixedOutputLength; }
         }
 
-        public override int DoOutput(byte[] output, int outOff, int outLen)
+        public override int Output(byte[] output, int outOff, int outLen)
         {
             if (diff == null)
             {
-                return base.DoOutput(output, outOff, outLen);
+                return base.Output(output, outOff, outLen);
             }
 
             if (!squeezing)
@@ -95,6 +95,25 @@ namespace Org.BouncyCastle.Crypto.Digests
             return outLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Output(Span<byte> output)
+        {
+            if (diff == null)
+            {
+                return base.Output(output);
+            }
+
+            if (!squeezing)
+            {
+                AbsorbBits(0x00, 2);
+            }
+
+            Squeeze(output);
+
+            return output.Length;
+        }
+#endif
+
         public override void Reset()
         {
             base.Reset();
diff --git a/crypto/src/crypto/digests/DSTU7564Digest.cs b/crypto/src/crypto/digests/DSTU7564Digest.cs
index b2d90799d..901e549be 100644
--- a/crypto/src/crypto/digests/DSTU7564Digest.cs
+++ b/crypto/src/crypto/digests/DSTU7564Digest.cs
@@ -1,11 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto.Engines;
-using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
-
 using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.Utilities.Encoders;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
@@ -132,25 +128,103 @@ namespace Org.BouncyCastle.Crypto.Digests
                 --length;
             }
 
-            if (length > 0)
+            while (length >= blockSize)
+            {
+                ProcessBlock(input, inOff);
+                inOff += blockSize;
+                length -= blockSize;
+                ++inputBlocks;
+            }
+
+            while (length > 0)
+            {
+                Update(input[inOff++]);
+                --length;
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            while (bufOff != 0 && input.Length > 0)
             {
-                while (length >= blockSize)
+                Update(input[0]);
+                input = input[1..];
+            }
+
+            while (input.Length >= blockSize)
+            {
+                ProcessBlock(input);
+                input = input[blockSize..];
+                ++inputBlocks;
+            }
+
+            while (input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+        }
+#endif
+
+        public virtual int DoFinal(byte[] output, int outOff)
+        {
+            // Apply padding: terminator byte and 96-bit length field
+            {
+                int inputBytes = bufOff;
+                buf[bufOff++] = (byte)0x80;
+
+                int lenPos = blockSize - 12;
+                if (bufOff > lenPos)
                 {
-                    ProcessBlock(input, inOff);
-                    inOff += blockSize;
-                    length -= blockSize;
-                    ++inputBlocks;
+                    while (bufOff < blockSize)
+                    {
+                        buf[bufOff++] = 0;
+                    }
+                    bufOff = 0;
+                    ProcessBlock(buf, 0);
                 }
 
-                while (length > 0)
+                while (bufOff < lenPos)
                 {
-                    Update(input[inOff++]);
-                    --length;
+                    buf[bufOff++] = 0;
+                }
+
+                ulong c = ((inputBlocks & 0xFFFFFFFFUL) * (ulong)blockSize + (uint)inputBytes) << 3;
+                Pack.UInt32_To_LE((uint)c, buf, bufOff);
+                bufOff += 4;
+                c >>= 32;
+                c += ((inputBlocks >> 32) * (ulong)blockSize) << 3;
+                Pack.UInt64_To_LE(c, buf, bufOff);
+                //bufOff += 8;
+                ProcessBlock(buf, 0);
+            }
+
+            {
+                Array.Copy(state, 0, tempState1, 0, columns);
+
+                P(tempState1);
+
+                for (int col = 0; col < columns; ++col)
+                {
+                    state[col] ^= tempState1[col];
                 }
             }
+
+            int neededColumns = hashSize / 8;
+            for (int col = columns - neededColumns; col < columns; ++col)
+            {
+                Pack.UInt64_To_LE(state[col], output, outOff);
+                outOff += 8;
+            }
+
+            Reset();
+
+            return hashSize;
         }
 
-        public virtual int DoFinal(byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
         {
             // Apply padding: terminator byte and 96-bit length field
             {
@@ -179,7 +253,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                 c >>= 32;
                 c += ((inputBlocks >> 32) * (ulong)blockSize) << 3;
                 Pack.UInt64_To_LE(c, buf, bufOff);
-    //            bufOff += 8;
+                //bufOff += 8;
                 ProcessBlock(buf, 0);
             }
 
@@ -197,14 +271,15 @@ namespace Org.BouncyCastle.Crypto.Digests
             int neededColumns = hashSize / 8;
             for (int col = columns - neededColumns; col < columns; ++col)
             {
-                Pack.UInt64_To_LE(state[col], output, outOff);
-                outOff += 8;
+                Pack.UInt64_To_LE(state[col], output);
+                output = output[8..];
             }
 
             Reset();
 
             return hashSize;
         }
+#endif
 
         public virtual void Reset()
         {
@@ -236,6 +311,28 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected virtual void ProcessBlock(ReadOnlySpan<byte> input)
+        {
+            for (int col = 0; col < columns; ++col)
+            {
+                ulong word = Pack.LE_To_UInt64(input);
+                input = input[8..];
+
+                tempState1[col] = state[col] ^ word;
+                tempState2[col] = word;
+            }
+
+            P(tempState1);
+            Q(tempState2);
+
+            for (int col = 0; col < columns; ++col)
+            {
+                state[col] ^= tempState1[col] ^ tempState2[col];
+            }
+        }
+#endif
+
         private void P(ulong[] s)
         {
             for (int round = 0; round < rounds; ++round)
diff --git a/crypto/src/crypto/digests/GOST3411Digest.cs b/crypto/src/crypto/digests/GOST3411Digest.cs
index 123751baa..dc1d376c3 100644
--- a/crypto/src/crypto/digests/GOST3411Digest.cs
+++ b/crypto/src/crypto/digests/GOST3411Digest.cs
@@ -91,10 +91,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			byteCount++;
 		}
 
-		public void BlockUpdate(
-			byte[]	input,
-			int		inOff,
-			int		length)
+		public void BlockUpdate(byte[] input, int inOff, int length)
 		{
 			while ((xBufOff != 0) && (length > 0))
 			{
@@ -123,6 +120,34 @@ namespace Org.BouncyCastle.Crypto.Digests
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
+		{
+			while ((xBufOff != 0) && (input.Length > 0))
+			{
+				Update(input[0]);
+				input = input[1..];
+			}
+
+			while (input.Length >= xBuf.Length)
+			{
+				input[..xBuf.Length].CopyTo(xBuf.AsSpan());
+
+				sumByteArray(xBuf); // calc sum M
+				processBlock(xBuf, 0);
+				input = input[xBuf.Length..];
+				byteCount += (uint)xBuf.Length;
+			}
+
+			// load in the remainder.
+			while (input.Length > 0)
+			{
+				Update(input[0]);
+				input = input[1..];
+			}
+		}
+#endif
+
 		// (i + 1 + 4(k - 1)) = 8i + k      i = 0-3, k = 1-8
 		private byte[] K = new byte[32];
 
@@ -234,7 +259,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			Array.Copy(S, 0, H, 0, H.Length);
 		}
 
-		private void finish()
+		private void Finish()
 		{
 			ulong bitCount = byteCount * 8;
 			Pack.UInt64_To_LE(bitCount, L);
@@ -248,11 +273,9 @@ namespace Org.BouncyCastle.Crypto.Digests
 			processBlock(Sum, 0);
 		}
 
-		public int DoFinal(
-			byte[]  output,
-			int     outOff)
+		public int DoFinal(byte[] output, int outOff)
 		{
-			finish();
+			Finish();
 
 			H.CopyTo(output, outOff);
 
@@ -261,6 +284,19 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DIGEST_LENGTH;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			Finish();
+
+			H.CopyTo(output);
+
+			Reset();
+
+			return DIGEST_LENGTH;
+		}
+#endif
+
 		/**
 		* reset the chaining variables to the IV values.
 		*/
diff --git a/crypto/src/crypto/digests/GOST3411_2012Digest.cs b/crypto/src/crypto/digests/GOST3411_2012Digest.cs
index 68cb6c035..259f4bcae 100644
--- a/crypto/src/crypto/digests/GOST3411_2012Digest.cs
+++ b/crypto/src/crypto/digests/GOST3411_2012Digest.cs
@@ -1,10 +1,11 @@
 using System;
-using Org.BouncyCastle.Crypto;
+
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
-    public abstract class Gost3411_2012Digest:IDigest,IMemoable
+    public abstract class Gost3411_2012Digest
+        : IDigest, IMemoable
     {
         private readonly byte[] IV = new byte[64];
         private readonly byte[] N = new byte[64];
@@ -21,8 +22,8 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         protected Gost3411_2012Digest(byte[] IV)
         {
-            System.Array.Copy(IV,this.IV,64);
-            System.Array.Copy(IV, h, 64);
+            Array.Copy(IV,this.IV,64);
+            Array.Copy(IV, h, 64);
         }
 
         public abstract string AlgorithmName { get; }
@@ -43,7 +44,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             if (bOff != 64)
             {
-                System.Array.Copy(block, bOff, m, 64 - lenM, lenM);
+                Array.Copy(block, bOff, m, 64 - lenM, lenM);
             }
 
             g_N(h, N, m);
@@ -60,6 +61,39 @@ namespace Org.BouncyCastle.Crypto.Digests
             return 64;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            int lenM = 64 - bOff;
+
+            // At this point it is certain that lenM is smaller than 64
+            for (int i = 0; i != 64 - lenM; i++)
+            {
+                m[i] = 0;
+            }
+
+            m[63 - lenM] = 1;
+
+            if (bOff != 64)
+            {
+                Array.Copy(block, bOff, m, 64 - lenM, lenM);
+            }
+
+            g_N(h, N, m);
+            addMod512(N, lenM * 8);
+            addMod512(Sigma, m);
+            g_N(h, Zero, N);
+            g_N(h, Zero, Sigma);
+
+            reverse(h, tmp);
+
+            tmp.CopyTo(output);
+
+            Reset();
+            return 64;
+        }
+#endif
+
         public int GetByteLength()
         {
             return 64;
@@ -73,7 +107,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             bOff = 64;
             Arrays.Fill(N, (byte)0);
             Arrays.Fill(Sigma, (byte)0);
-            System.Array.Copy(IV, 0, h, 0, 64);
+            Array.Copy(IV, 0, h, 0, 64);
             Arrays.Fill(block, (byte)0);
         }
 
@@ -81,14 +115,14 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             Gost3411_2012Digest o = (Gost3411_2012Digest)other;
 
-            System.Array.Copy(o.IV, 0, this.IV, 0, 64);
-            System.Array.Copy(o.N, 0, this.N, 0, 64);
-            System.Array.Copy(o.Sigma, 0, this.Sigma, 0, 64);
-            System.Array.Copy(o.Ki, 0, this.Ki, 0, 64);
-            System.Array.Copy(o.m, 0, this.m, 0, 64);
-            System.Array.Copy(o.h, 0, this.h, 0, 64);
+            Array.Copy(o.IV, 0, this.IV, 0, 64);
+            Array.Copy(o.N, 0, this.N, 0, 64);
+            Array.Copy(o.Sigma, 0, this.Sigma, 0, 64);
+            Array.Copy(o.Ki, 0, this.Ki, 0, 64);
+            Array.Copy(o.m, 0, this.m, 0, 64);
+            Array.Copy(o.h, 0, this.h, 0, 64);
 
-            System.Array.Copy(o.block, 0, this.block, 0, 64);
+            Array.Copy(o.block, 0, this.block, 0, 64);
             this.bOff = o.bOff;
         }
 
@@ -104,7 +138,6 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-
         public void BlockUpdate(byte[] input, int inOff, int len)
         {
             while (bOff != 64 && len > 0)
@@ -114,7 +147,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
             while (len >= 64)
             {
-                System.Array.Copy(input, inOff, tmp, 0, 64);
+                Array.Copy(input, inOff, tmp, 0, 64);
                 reverse(tmp, block);
                 g_N(h, N, block);
                 addMod512(N, 512);
@@ -130,8 +163,31 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-    
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            while (bOff != 64 && input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+            while (input.Length >= 64)
+            {
+                input[..64].CopyTo(tmp.AsSpan());
+                reverse(tmp, block);
+                g_N(h, N, block);
+                addMod512(N, 512);
+                addMod512(Sigma, block);
 
+                input = input[64..];
+            }
+            while (input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+        }
+#endif
 
         private void F(byte[] V)
         {
@@ -317,7 +373,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         private void E(byte[] K, byte[] m)
         {
-            System.Array.Copy(K, 0, Ki, 0, 64);
+            Array.Copy(K, 0, Ki, 0, 64);
             xor512(K, m);
             F(K);
             for (int i = 0; i < 11; ++i)
@@ -334,7 +390,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         private void g_N(byte[] h, byte[] N, byte[] m)
         {
-            System.Array.Copy(h, 0, tmp, 0, 64);
+            Array.Copy(h, 0, tmp, 0, 64);
 
             xor512(h, N);
             F(h);
diff --git a/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs b/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs
index 77cf6c50f..47877b2b5 100644
--- a/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs
+++ b/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs
@@ -46,6 +46,18 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return 32;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Span<byte> result = stackalloc byte[64];
+            base.DoFinal(result);
+
+            result[32..].CopyTo(output);
+
+            return 32;
+        }
+#endif
+
         public override IMemoable Copy()
         {
 			return new Gost3411_2012_256Digest(this);
diff --git a/crypto/src/crypto/digests/GeneralDigest.cs b/crypto/src/crypto/digests/GeneralDigest.cs
index c38b35fd3..02bad2cfb 100644
--- a/crypto/src/crypto/digests/GeneralDigest.cs
+++ b/crypto/src/crypto/digests/GeneralDigest.cs
@@ -95,6 +95,50 @@ namespace Org.BouncyCastle.Crypto.Digests
             byteCount += length;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int length = input.Length;
+
+            //
+            // fill the current word
+            //
+            int i = 0;
+            if (xBufOff != 0)
+            {
+                while (i < length)
+                {
+                    xBuf[xBufOff++] = input[i++];
+                    if (xBufOff == 4)
+                    {
+                        ProcessWord(xBuf, 0);
+                        xBufOff = 0;
+                        break;
+                    }
+                }
+            }
+
+            //
+            // process whole words.
+            //
+            int limit = length - 3;
+            for (; i < limit; i += 4)
+            {
+                ProcessWord(input.Slice(i, 4));
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (i < length)
+            {
+                xBuf[xBufOff++] = input[i++];
+            }
+
+            byteCount += length;
+        }
+#endif
+
         public void Finish()
         {
             long    bitLength = (byteCount << 3);
@@ -122,6 +166,9 @@ namespace Org.BouncyCastle.Crypto.Digests
 		}
 
 		internal abstract void ProcessWord(byte[] input, int inOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal abstract void ProcessWord(ReadOnlySpan<byte> word);
+#endif
         internal abstract void ProcessLength(long bitLength);
         internal abstract void ProcessBlock();
         public abstract string AlgorithmName { get; }
@@ -129,5 +176,8 @@ namespace Org.BouncyCastle.Crypto.Digests
         public abstract int DoFinal(byte[] output, int outOff);
 		public abstract IMemoable Copy();
 		public abstract void Reset(IMemoable t);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract int DoFinal(Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/Haraka256Digest.cs b/crypto/src/crypto/digests/Haraka256Digest.cs
index f7af4bf69..efbaa81e2 100644
--- a/crypto/src/crypto/digests/Haraka256Digest.cs
+++ b/crypto/src/crypto/digests/Haraka256Digest.cs
@@ -1,160 +1,214 @@
 using System;
 
-
 namespace Org.BouncyCastle.Crypto.Digests
 {
-    public class Haraka256Digest : HarakaBase
+    public sealed class Haraka256Digest
+        : HarakaBase
     {
-        private static readonly byte[][] RC = new byte[][]{
-        new byte[]{(byte)0x06, (byte)0x84, (byte)0x70, (byte)0x4c, (byte)0xe6, (byte)0x20, (byte)0xc0, (byte)0x0a, (byte)0xb2, (byte)0xc5, (byte)0xfe, (byte)0xf0, (byte)0x75, (byte)0x81, (byte)0x7b, (byte)0x9d},
-        new byte[]{(byte)0x8b, (byte)0x66, (byte)0xb4, (byte)0xe1, (byte)0x88, (byte)0xf3, (byte)0xa0, (byte)0x6b, (byte)0x64, (byte)0x0f, (byte)0x6b, (byte)0xa4, (byte)0x2f, (byte)0x08, (byte)0xf7, (byte)0x17},
-        new byte[]{(byte)0x34, (byte)0x02, (byte)0xde, (byte)0x2d, (byte)0x53, (byte)0xf2, (byte)0x84, (byte)0x98, (byte)0xcf, (byte)0x02, (byte)0x9d, (byte)0x60, (byte)0x9f, (byte)0x02, (byte)0x91, (byte)0x14},
-        new byte[]{(byte)0x0e, (byte)0xd6, (byte)0xea, (byte)0xe6, (byte)0x2e, (byte)0x7b, (byte)0x4f, (byte)0x08, (byte)0xbb, (byte)0xf3, (byte)0xbc, (byte)0xaf, (byte)0xfd, (byte)0x5b, (byte)0x4f, (byte)0x79},
-        new byte[]{(byte)0xcb, (byte)0xcf, (byte)0xb0, (byte)0xcb, (byte)0x48, (byte)0x72, (byte)0x44, (byte)0x8b, (byte)0x79, (byte)0xee, (byte)0xcd, (byte)0x1c, (byte)0xbe, (byte)0x39, (byte)0x70, (byte)0x44},
-        new byte[]{(byte)0x7e, (byte)0xea, (byte)0xcd, (byte)0xee, (byte)0x6e, (byte)0x90, (byte)0x32, (byte)0xb7, (byte)0x8d, (byte)0x53, (byte)0x35, (byte)0xed, (byte)0x2b, (byte)0x8a, (byte)0x05, (byte)0x7b},
-        new byte[]{(byte)0x67, (byte)0xc2, (byte)0x8f, (byte)0x43, (byte)0x5e, (byte)0x2e, (byte)0x7c, (byte)0xd0, (byte)0xe2, (byte)0x41, (byte)0x27, (byte)0x61, (byte)0xda, (byte)0x4f, (byte)0xef, (byte)0x1b},
-        new byte[]{(byte)0x29, (byte)0x24, (byte)0xd9, (byte)0xb0, (byte)0xaf, (byte)0xca, (byte)0xcc, (byte)0x07, (byte)0x67, (byte)0x5f, (byte)0xfd, (byte)0xe2, (byte)0x1f, (byte)0xc7, (byte)0x0b, (byte)0x3b},
-        new byte[]{(byte)0xab, (byte)0x4d, (byte)0x63, (byte)0xf1, (byte)0xe6, (byte)0x86, (byte)0x7f, (byte)0xe9, (byte)0xec, (byte)0xdb, (byte)0x8f, (byte)0xca, (byte)0xb9, (byte)0xd4, (byte)0x65, (byte)0xee},
-        new byte[]{(byte)0x1c, (byte)0x30, (byte)0xbf, (byte)0x84, (byte)0xd4, (byte)0xb7, (byte)0xcd, (byte)0x64, (byte)0x5b, (byte)0x2a, (byte)0x40, (byte)0x4f, (byte)0xad, (byte)0x03, (byte)0x7e, (byte)0x33},
-        new byte[]{(byte)0xb2, (byte)0xcc, (byte)0x0b, (byte)0xb9, (byte)0x94, (byte)0x17, (byte)0x23, (byte)0xbf, (byte)0x69, (byte)0x02, (byte)0x8b, (byte)0x2e, (byte)0x8d, (byte)0xf6, (byte)0x98, (byte)0x00},
-        new byte[]{(byte)0xfa, (byte)0x04, (byte)0x78, (byte)0xa6, (byte)0xde, (byte)0x6f, (byte)0x55, (byte)0x72, (byte)0x4a, (byte)0xaa, (byte)0x9e, (byte)0xc8, (byte)0x5c, (byte)0x9d, (byte)0x2d, (byte)0x8a},
-        new byte[]{(byte)0xdf, (byte)0xb4, (byte)0x9f, (byte)0x2b, (byte)0x6b, (byte)0x77, (byte)0x2a, (byte)0x12, (byte)0x0e, (byte)0xfa, (byte)0x4f, (byte)0x2e, (byte)0x29, (byte)0x12, (byte)0x9f, (byte)0xd4},
-        new byte[]{(byte)0x1e, (byte)0xa1, (byte)0x03, (byte)0x44, (byte)0xf4, (byte)0x49, (byte)0xa2, (byte)0x36, (byte)0x32, (byte)0xd6, (byte)0x11, (byte)0xae, (byte)0xbb, (byte)0x6a, (byte)0x12, (byte)0xee},
-        new byte[]{(byte)0xaf, (byte)0x04, (byte)0x49, (byte)0x88, (byte)0x4b, (byte)0x05, (byte)0x00, (byte)0x84, (byte)0x5f, (byte)0x96, (byte)0x00, (byte)0xc9, (byte)0x9c, (byte)0xa8, (byte)0xec, (byte)0xa6},
-        new byte[]{(byte)0x21, (byte)0x02, (byte)0x5e, (byte)0xd8, (byte)0x9d, (byte)0x19, (byte)0x9c, (byte)0x4f, (byte)0x78, (byte)0xa2, (byte)0xc7, (byte)0xe3, (byte)0x27, (byte)0xe5, (byte)0x93, (byte)0xec},
-        new byte[]{(byte)0xbf, (byte)0x3a, (byte)0xaa, (byte)0xf8, (byte)0xa7, (byte)0x59, (byte)0xc9, (byte)0xb7, (byte)0xb9, (byte)0x28, (byte)0x2e, (byte)0xcd, (byte)0x82, (byte)0xd4, (byte)0x01, (byte)0x73},
-        new byte[]{(byte)0x62, (byte)0x60, (byte)0x70, (byte)0x0d, (byte)0x61, (byte)0x86, (byte)0xb0, (byte)0x17, (byte)0x37, (byte)0xf2, (byte)0xef, (byte)0xd9, (byte)0x10, (byte)0x30, (byte)0x7d, (byte)0x6b},
-        new byte[]{(byte)0x5a, (byte)0xca, (byte)0x45, (byte)0xc2, (byte)0x21, (byte)0x30, (byte)0x04, (byte)0x43, (byte)0x81, (byte)0xc2, (byte)0x91, (byte)0x53, (byte)0xf6, (byte)0xfc, (byte)0x9a, (byte)0xc6},
-        new byte[]{(byte)0x92, (byte)0x23, (byte)0x97, (byte)0x3c, (byte)0x22, (byte)0x6b, (byte)0x68, (byte)0xbb, (byte)0x2c, (byte)0xaf, (byte)0x92, (byte)0xe8, (byte)0x36, (byte)0xd1, (byte)0x94, (byte)0x3a}
-    };
-        private readonly byte[] buffer;
-        private int off;
-        private void mix256(byte[][] s1, byte[][] s2)
-        {
-            Array.Copy(s1[0], 0, s2[0], 0, 4);
-            Array.Copy(s1[1], 0, s2[0], 4, 4);
-            Array.Copy(s1[0], 4, s2[0], 8, 4);
-            Array.Copy(s1[1], 4, s2[0], 12, 4);
+        private readonly byte[] m_buf;
+        private int m_bufPos;
 
-            Array.Copy(s1[0], 8, s2[1], 0, 4);
-            Array.Copy(s1[1], 8, s2[1], 4, 4);
-            Array.Copy(s1[0], 12, s2[1], 8, 4);
-            Array.Copy(s1[1], 12, s2[1], 12, 4);
-        }
-
-        private int haraka256256(byte[] msg, byte[] output, int outOff)
+        public Haraka256Digest()
         {
-            byte[][] s1 = new byte[2][];
-            s1[0] = new byte[16];
-            s1[1] = new byte[16];
-            byte[][] s2 = new byte[2][];
-            s2[0] = new byte[16];
-            s2[1] = new byte[16];
-
-            Array.Copy(msg, 0, s1[0], 0, 16);
-            Array.Copy(msg, 16, s1[1], 0, 16);
-
-            s1[0] = aesEnc(s1[0], RC[0]);
-            s1[1] = aesEnc(s1[1], RC[1]);
-            s1[0] = aesEnc(s1[0], RC[2]);
-            s1[1] = aesEnc(s1[1], RC[3]);
-            mix256(s1, s2);
-
-            s1[0] = aesEnc(s2[0], RC[4]);
-            s1[1] = aesEnc(s2[1], RC[5]);
-            s1[0] = aesEnc(s1[0], RC[6]);
-            s1[1] = aesEnc(s1[1], RC[7]);
-            mix256(s1, s2);
-
-            s1[0] = aesEnc(s2[0], RC[8]);
-            s1[1] = aesEnc(s2[1], RC[9]);
-            s1[0] = aesEnc(s1[0], RC[10]);
-            s1[1] = aesEnc(s1[1], RC[11]);
-            mix256(s1, s2);
-
-            s1[0] = aesEnc(s2[0], RC[12]);
-            s1[1] = aesEnc(s2[1], RC[13]);
-            s1[0] = aesEnc(s1[0], RC[14]);
-            s1[1] = aesEnc(s1[1], RC[15]);
-            mix256(s1, s2);
-
-            s1[0] = aesEnc(s2[0], RC[16]);
-            s1[1] = aesEnc(s2[1], RC[17]);
-            s1[0] = aesEnc(s1[0], RC[18]);
-            s1[1] = aesEnc(s1[1], RC[19]);
-            mix256(s1, s2);
-
-            s1[0] = Xor(s2[0], msg, 0);
-            s1[1] = Xor(s2[1], msg, 16);
-
-            Array.Copy(s1[0], 0, output, outOff, 16);
-            Array.Copy(s1[1], 0, output, outOff + 16, 16);
-
-            return DIGEST_SIZE;
+            m_buf = new byte[32];
+            m_bufPos = 0;
         }
 
+        public override string AlgorithmName => "Haraka-256";
 
+        public override int GetByteLength() => 32;
 
-        public Haraka256Digest()
-        {
-            this.buffer = new byte[32];
-        }
-
-        public Haraka256Digest(Haraka256Digest digest)
+        public override void Update(byte input)
         {
-            this.buffer = (byte[]) digest.buffer.Clone();
-            this.off = digest.off;
-        }
+            if (m_bufPos > 32 - 1)
+                throw new ArgumentException("total input cannot be more than 32 bytes");
 
-        public string getAlgorithmName()
-        {
-            return "Haraka-256";
+            m_buf[m_bufPos++] = input;
         }
 
-        public override void Update(byte input)
+        public override void BlockUpdate(byte[] input, int inOff, int len)
         {
-            if (off + 1 > 32)
-            {
+            if (m_bufPos > 32 - len)
                 throw new ArgumentException("total input cannot be more than 32 bytes");
-            }
 
-            buffer[off++] = input;
+            Array.Copy(input, inOff, m_buf, m_bufPos, len);
+            m_bufPos += len;
         }
 
-        public override void BlockUpdate(byte[] input, int inOff, int len)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            if (off + len > 32)
-            {
+            if (m_bufPos > 32 - input.Length)
                 throw new ArgumentException("total input cannot be more than 32 bytes");
-            }
 
-            Array.Copy(input, inOff, buffer, off, len);
-            off += len;
+            input.CopyTo(m_buf.AsSpan(m_bufPos));
+            m_bufPos += input.Length;
         }
+#endif
 
         public override int DoFinal(byte[] output, int outOff)
         {
-            if (off != 32)
-            {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
+            if (m_bufPos != 32)
                 throw new ArgumentException("input must be exactly 32 bytes");
-            }
 
             if (output.Length - outOff < 32)
-            {
                 throw new ArgumentException("output too short to receive digest");
+
+            int rv = Haraka256256(m_buf, output, outOff);
+
+            Reset();
+
+            return rv;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            if (m_bufPos != 32)
+                throw new ArgumentException("input must be exactly 32 bytes");
+
+            if (output.Length < 32)
+                throw new ArgumentException("output too short to receive digest");
+
+#if NETCOREAPP3_0_OR_GREATER
+            if (Haraka256_X86.IsSupported)
+            {
+                Haraka256_X86.Hash(m_buf, output);
+                Reset();
+                return 32;
             }
+#endif
 
-            int rv = haraka256256(buffer, output, outOff);
+            int rv = Haraka256256(m_buf, output);
 
             Reset();
 
             return rv;
         }
+#endif
 
         public override void Reset()
         {
-            off = 0;
-            Array.Clear(buffer, 0, 32);
+            m_bufPos = 0;
+            Array.Clear(m_buf, 0, 32);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int Haraka256256(ReadOnlySpan<byte> msg, Span<byte> output)
+        {
+            byte[][] s1 = new byte[2][];
+            s1[0] = new byte[16];
+            s1[1] = new byte[16];
+            byte[][] s2 = new byte[2][];
+            s2[0] = new byte[16];
+            s2[1] = new byte[16];
+
+            msg[  ..16].CopyTo(s1[0]);
+            msg[16..32].CopyTo(s1[1]);
+
+            s1[0] = AesEnc(s1[0], RC[0]);
+            s1[1] = AesEnc(s1[1], RC[1]);
+            s1[0] = AesEnc(s1[0], RC[2]);
+            s1[1] = AesEnc(s1[1], RC[3]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[4]);
+            s1[1] = AesEnc(s2[1], RC[5]);
+            s1[0] = AesEnc(s1[0], RC[6]);
+            s1[1] = AesEnc(s1[1], RC[7]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[8]);
+            s1[1] = AesEnc(s2[1], RC[9]);
+            s1[0] = AesEnc(s1[0], RC[10]);
+            s1[1] = AesEnc(s1[1], RC[11]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[12]);
+            s1[1] = AesEnc(s2[1], RC[13]);
+            s1[0] = AesEnc(s1[0], RC[14]);
+            s1[1] = AesEnc(s1[1], RC[15]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[16]);
+            s1[1] = AesEnc(s2[1], RC[17]);
+            s1[0] = AesEnc(s1[0], RC[18]);
+            s1[1] = AesEnc(s1[1], RC[19]);
+            Mix256(s1, s2);
+
+            Xor(s2[0], msg      , output[  ..16]);
+            Xor(s2[1], msg[16..], output[16..32]);
+
+            return DIGEST_SIZE;
+        }
+#else
+        private static int Haraka256256(byte[] msg, byte[] output, int outOff)
+        {
+            byte[][] s1 = new byte[2][];
+            s1[0] = new byte[16];
+            s1[1] = new byte[16];
+            byte[][] s2 = new byte[2][];
+            s2[0] = new byte[16];
+            s2[1] = new byte[16];
+
+            Array.Copy(msg,  0, s1[0], 0, 16);
+            Array.Copy(msg, 16, s1[1], 0, 16);
+
+            s1[0] = AesEnc(s1[0], RC[0]);
+            s1[1] = AesEnc(s1[1], RC[1]);
+            s1[0] = AesEnc(s1[0], RC[2]);
+            s1[1] = AesEnc(s1[1], RC[3]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[4]);
+            s1[1] = AesEnc(s2[1], RC[5]);
+            s1[0] = AesEnc(s1[0], RC[6]);
+            s1[1] = AesEnc(s1[1], RC[7]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[8]);
+            s1[1] = AesEnc(s2[1], RC[9]);
+            s1[0] = AesEnc(s1[0], RC[10]);
+            s1[1] = AesEnc(s1[1], RC[11]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[12]);
+            s1[1] = AesEnc(s2[1], RC[13]);
+            s1[0] = AesEnc(s1[0], RC[14]);
+            s1[1] = AesEnc(s1[1], RC[15]);
+            Mix256(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[16]);
+            s1[1] = AesEnc(s2[1], RC[17]);
+            s1[0] = AesEnc(s1[0], RC[18]);
+            s1[1] = AesEnc(s1[1], RC[19]);
+            Mix256(s1, s2);
+
+            s1[0] = Xor(s2[0], msg,  0);
+            s1[1] = Xor(s2[1], msg, 16);
+
+            Array.Copy(s1[0], 0, output, outOff     , 16);
+            Array.Copy(s1[1], 0, output, outOff + 16, 16);
+
+            return DIGEST_SIZE;
+        }
+#endif
+
+        private static void Mix256(byte[][] s1, byte[][] s2)
+        {
+            Array.Copy(s1[0], 0, s2[0], 0, 4);
+            Array.Copy(s1[1], 0, s2[0], 4, 4);
+            Array.Copy(s1[0], 4, s2[0], 8, 4);
+            Array.Copy(s1[1], 4, s2[0], 12, 4);
+
+            Array.Copy(s1[0], 8, s2[1], 0, 4);
+            Array.Copy(s1[1], 8, s2[1], 4, 4);
+            Array.Copy(s1[0], 12, s2[1], 8, 4);
+            Array.Copy(s1[1], 12, s2[1], 12, 4);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/digests/Haraka256_X86.cs b/crypto/src/crypto/digests/Haraka256_X86.cs
new file mode 100644
index 000000000..4c9a798c0
--- /dev/null
+++ b/crypto/src/crypto/digests/Haraka256_X86.cs
@@ -0,0 +1,144 @@
+#if NETCOREAPP3_0_OR_GREATER
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+    using Aes = System.Runtime.Intrinsics.X86.Aes;
+    using Sse2 = System.Runtime.Intrinsics.X86.Sse2;
+
+    public static class Haraka256_X86
+    {
+        public static bool IsSupported => Aes.IsSupported;
+
+        public static void Hash(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka256_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+
+            ImplRounds(ref s0, ref s1, Haraka512_X86.DefaultRoundConstants.AsSpan(0, 20));
+
+            s0 = Sse2.Xor(s0, Load128(input[  ..16]));
+            s1 = Sse2.Xor(s1, Load128(input[16..32]));
+
+            Store128(s0, output[  ..16]);
+            Store128(s1, output[16..32]);
+        }
+
+        public static void Hash(ReadOnlySpan<byte> input, Span<byte> output,
+            ReadOnlySpan<Vector128<byte>> roundConstants)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka256_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+
+            ImplRounds(ref s0, ref s1, roundConstants[..20]);
+
+            s0 = Sse2.Xor(s0, Load128(input[  ..16]));
+            s1 = Sse2.Xor(s1, Load128(input[16..32]));
+
+            Store128(s0, output[  ..16]);
+            Store128(s1, output[16..32]);
+        }
+
+        public static void Permute(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka256_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+
+            ImplRounds(ref s0, ref s1, Haraka512_X86.DefaultRoundConstants.AsSpan(0, 20));
+
+            Store128(s0, output[  ..16]);
+            Store128(s1, output[16..32]);
+        }
+
+        public static void Permute(ReadOnlySpan<byte> input, Span<byte> output,
+            ReadOnlySpan<Vector128<byte>> roundConstants)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka256_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+
+            ImplRounds(ref s0, ref s1, roundConstants[..20]);
+
+            Store128(s0, output[  ..16]);
+            Store128(s1, output[16..32]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplRounds(ref Vector128<byte> s0, ref Vector128<byte> s1, ReadOnlySpan<Vector128<byte>> rc)
+        {
+            ImplRound(ref s0, ref s1, rc[  .. 4]);
+            ImplRound(ref s0, ref s1, rc[ 4.. 8]);
+            ImplRound(ref s0, ref s1, rc[ 8..12]);
+            ImplRound(ref s0, ref s1, rc[12..16]);
+            ImplRound(ref s0, ref s1, rc[16..20]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplRound(ref Vector128<byte> s0, ref Vector128<byte> s1, ReadOnlySpan<Vector128<byte>> rc)
+        {
+            ImplAes(ref s0, ref s1, rc[..4]);
+            ImplMix(ref s0, ref s1);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplAes(ref Vector128<byte> s0, ref Vector128<byte> s1, ReadOnlySpan<Vector128<byte>> rc)
+        {
+            s0 = Aes.Encrypt(s0, rc[0]);
+            s1 = Aes.Encrypt(s1, rc[1]);
+
+            s0 = Aes.Encrypt(s0, rc[2]);
+            s1 = Aes.Encrypt(s1, rc[3]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplMix(ref Vector128<byte> s0, ref Vector128<byte> s1)
+        {
+            Vector128<uint> t0 = s0.AsUInt32();
+            Vector128<uint> t1 = s1.AsUInt32();
+            s0 = Sse2.UnpackLow(t0, t1).AsByte();
+            s1 = Sse2.UnpackHigh(t0, t1).AsByte();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Vector128<byte> Load128(ReadOnlySpan<byte> t)
+        {
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+                return MemoryMarshal.Read<Vector128<byte>>(t);
+
+            return Vector128.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[..8]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[8..])
+            ).AsByte();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Store128(Vector128<byte> s, Span<byte> t)
+        {
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                MemoryMarshal.Write(t, ref s);
+                return;
+            }
+
+            var u = s.AsUInt64();
+            BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
+        }
+    }
+}
+#endif
diff --git a/crypto/src/crypto/digests/Haraka512Digest.cs b/crypto/src/crypto/digests/Haraka512Digest.cs
index 36bdcbabe..4c9794bac 100644
--- a/crypto/src/crypto/digests/Haraka512Digest.cs
+++ b/crypto/src/crypto/digests/Haraka512Digest.cs
@@ -2,89 +2,103 @@
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
-    public class Haraka512Digest : HarakaBase
+    public sealed class Haraka512Digest
+        : HarakaBase
     {
-        private static readonly byte[][] RC = new byte[][]{
-            new byte[]{(byte)0x06, (byte)0x84, (byte)0x70, (byte)0x4c, (byte)0xe6, (byte)0x20, (byte)0xc0, (byte)0x0a, (byte)0xb2, (byte)0xc5, (byte)0xfe, (byte)0xf0, (byte)0x75, (byte)0x81, (byte)0x7b, (byte)0x9d},
-            new byte[]{(byte)0x8b, (byte)0x66, (byte)0xb4, (byte)0xe1, (byte)0x88, (byte)0xf3, (byte)0xa0, (byte)0x6b, (byte)0x64, (byte)0x0f, (byte)0x6b, (byte)0xa4, (byte)0x2f, (byte)0x08, (byte)0xf7, (byte)0x17},
-            new byte[]{(byte)0x34, (byte)0x02, (byte)0xde, (byte)0x2d, (byte)0x53, (byte)0xf2, (byte)0x84, (byte)0x98, (byte)0xcf, (byte)0x02, (byte)0x9d, (byte)0x60, (byte)0x9f, (byte)0x02, (byte)0x91, (byte)0x14},
-            new byte[]{(byte)0x0e, (byte)0xd6, (byte)0xea, (byte)0xe6, (byte)0x2e, (byte)0x7b, (byte)0x4f, (byte)0x08, (byte)0xbb, (byte)0xf3, (byte)0xbc, (byte)0xaf, (byte)0xfd, (byte)0x5b, (byte)0x4f, (byte)0x79},
-            new byte[]{(byte)0xcb, (byte)0xcf, (byte)0xb0, (byte)0xcb, (byte)0x48, (byte)0x72, (byte)0x44, (byte)0x8b, (byte)0x79, (byte)0xee, (byte)0xcd, (byte)0x1c, (byte)0xbe, (byte)0x39, (byte)0x70, (byte)0x44},
-            new byte[]{(byte)0x7e, (byte)0xea, (byte)0xcd, (byte)0xee, (byte)0x6e, (byte)0x90, (byte)0x32, (byte)0xb7, (byte)0x8d, (byte)0x53, (byte)0x35, (byte)0xed, (byte)0x2b, (byte)0x8a, (byte)0x05, (byte)0x7b},
-            new byte[]{(byte)0x67, (byte)0xc2, (byte)0x8f, (byte)0x43, (byte)0x5e, (byte)0x2e, (byte)0x7c, (byte)0xd0, (byte)0xe2, (byte)0x41, (byte)0x27, (byte)0x61, (byte)0xda, (byte)0x4f, (byte)0xef, (byte)0x1b},
-            new byte[]{(byte)0x29, (byte)0x24, (byte)0xd9, (byte)0xb0, (byte)0xaf, (byte)0xca, (byte)0xcc, (byte)0x07, (byte)0x67, (byte)0x5f, (byte)0xfd, (byte)0xe2, (byte)0x1f, (byte)0xc7, (byte)0x0b, (byte)0x3b},
-            new byte[]{(byte)0xab, (byte)0x4d, (byte)0x63, (byte)0xf1, (byte)0xe6, (byte)0x86, (byte)0x7f, (byte)0xe9, (byte)0xec, (byte)0xdb, (byte)0x8f, (byte)0xca, (byte)0xb9, (byte)0xd4, (byte)0x65, (byte)0xee},
-            new byte[]{(byte)0x1c, (byte)0x30, (byte)0xbf, (byte)0x84, (byte)0xd4, (byte)0xb7, (byte)0xcd, (byte)0x64, (byte)0x5b, (byte)0x2a, (byte)0x40, (byte)0x4f, (byte)0xad, (byte)0x03, (byte)0x7e, (byte)0x33},
-            new byte[]{(byte)0xb2, (byte)0xcc, (byte)0x0b, (byte)0xb9, (byte)0x94, (byte)0x17, (byte)0x23, (byte)0xbf, (byte)0x69, (byte)0x02, (byte)0x8b, (byte)0x2e, (byte)0x8d, (byte)0xf6, (byte)0x98, (byte)0x00},
-            new byte[]{(byte)0xfa, (byte)0x04, (byte)0x78, (byte)0xa6, (byte)0xde, (byte)0x6f, (byte)0x55, (byte)0x72, (byte)0x4a, (byte)0xaa, (byte)0x9e, (byte)0xc8, (byte)0x5c, (byte)0x9d, (byte)0x2d, (byte)0x8a},
-            new byte[]{(byte)0xdf, (byte)0xb4, (byte)0x9f, (byte)0x2b, (byte)0x6b, (byte)0x77, (byte)0x2a, (byte)0x12, (byte)0x0e, (byte)0xfa, (byte)0x4f, (byte)0x2e, (byte)0x29, (byte)0x12, (byte)0x9f, (byte)0xd4},
-            new byte[]{(byte)0x1e, (byte)0xa1, (byte)0x03, (byte)0x44, (byte)0xf4, (byte)0x49, (byte)0xa2, (byte)0x36, (byte)0x32, (byte)0xd6, (byte)0x11, (byte)0xae, (byte)0xbb, (byte)0x6a, (byte)0x12, (byte)0xee},
-            new byte[]{(byte)0xaf, (byte)0x04, (byte)0x49, (byte)0x88, (byte)0x4b, (byte)0x05, (byte)0x00, (byte)0x84, (byte)0x5f, (byte)0x96, (byte)0x00, (byte)0xc9, (byte)0x9c, (byte)0xa8, (byte)0xec, (byte)0xa6},
-            new byte[]{(byte)0x21, (byte)0x02, (byte)0x5e, (byte)0xd8, (byte)0x9d, (byte)0x19, (byte)0x9c, (byte)0x4f, (byte)0x78, (byte)0xa2, (byte)0xc7, (byte)0xe3, (byte)0x27, (byte)0xe5, (byte)0x93, (byte)0xec},
-            new byte[]{(byte)0xbf, (byte)0x3a, (byte)0xaa, (byte)0xf8, (byte)0xa7, (byte)0x59, (byte)0xc9, (byte)0xb7, (byte)0xb9, (byte)0x28, (byte)0x2e, (byte)0xcd, (byte)0x82, (byte)0xd4, (byte)0x01, (byte)0x73},
-            new byte[]{(byte)0x62, (byte)0x60, (byte)0x70, (byte)0x0d, (byte)0x61, (byte)0x86, (byte)0xb0, (byte)0x17, (byte)0x37, (byte)0xf2, (byte)0xef, (byte)0xd9, (byte)0x10, (byte)0x30, (byte)0x7d, (byte)0x6b},
-            new byte[]{(byte)0x5a, (byte)0xca, (byte)0x45, (byte)0xc2, (byte)0x21, (byte)0x30, (byte)0x04, (byte)0x43, (byte)0x81, (byte)0xc2, (byte)0x91, (byte)0x53, (byte)0xf6, (byte)0xfc, (byte)0x9a, (byte)0xc6},
-            new byte[]{(byte)0x92, (byte)0x23, (byte)0x97, (byte)0x3c, (byte)0x22, (byte)0x6b, (byte)0x68, (byte)0xbb, (byte)0x2c, (byte)0xaf, (byte)0x92, (byte)0xe8, (byte)0x36, (byte)0xd1, (byte)0x94, (byte)0x3a},
-            new byte[]{(byte)0xd3, (byte)0xbf, (byte)0x92, (byte)0x38, (byte)0x22, (byte)0x58, (byte)0x86, (byte)0xeb, (byte)0x6c, (byte)0xba, (byte)0xb9, (byte)0x58, (byte)0xe5, (byte)0x10, (byte)0x71, (byte)0xb4},
-            new byte[]{(byte)0xdb, (byte)0x86, (byte)0x3c, (byte)0xe5, (byte)0xae, (byte)0xf0, (byte)0xc6, (byte)0x77, (byte)0x93, (byte)0x3d, (byte)0xfd, (byte)0xdd, (byte)0x24, (byte)0xe1, (byte)0x12, (byte)0x8d},
-            new byte[]{(byte)0xbb, (byte)0x60, (byte)0x62, (byte)0x68, (byte)0xff, (byte)0xeb, (byte)0xa0, (byte)0x9c, (byte)0x83, (byte)0xe4, (byte)0x8d, (byte)0xe3, (byte)0xcb, (byte)0x22, (byte)0x12, (byte)0xb1},
-            new byte[]{(byte)0x73, (byte)0x4b, (byte)0xd3, (byte)0xdc, (byte)0xe2, (byte)0xe4, (byte)0xd1, (byte)0x9c, (byte)0x2d, (byte)0xb9, (byte)0x1a, (byte)0x4e, (byte)0xc7, (byte)0x2b, (byte)0xf7, (byte)0x7d},
-            new byte[]{(byte)0x43, (byte)0xbb, (byte)0x47, (byte)0xc3, (byte)0x61, (byte)0x30, (byte)0x1b, (byte)0x43, (byte)0x4b, (byte)0x14, (byte)0x15, (byte)0xc4, (byte)0x2c, (byte)0xb3, (byte)0x92, (byte)0x4e},
-            new byte[]{(byte)0xdb, (byte)0xa7, (byte)0x75, (byte)0xa8, (byte)0xe7, (byte)0x07, (byte)0xef, (byte)0xf6, (byte)0x03, (byte)0xb2, (byte)0x31, (byte)0xdd, (byte)0x16, (byte)0xeb, (byte)0x68, (byte)0x99},
-            new byte[]{(byte)0x6d, (byte)0xf3, (byte)0x61, (byte)0x4b, (byte)0x3c, (byte)0x75, (byte)0x59, (byte)0x77, (byte)0x8e, (byte)0x5e, (byte)0x23, (byte)0x02, (byte)0x7e, (byte)0xca, (byte)0x47, (byte)0x2c},
-            new byte[]{(byte)0xcd, (byte)0xa7, (byte)0x5a, (byte)0x17, (byte)0xd6, (byte)0xde, (byte)0x7d, (byte)0x77, (byte)0x6d, (byte)0x1b, (byte)0xe5, (byte)0xb9, (byte)0xb8, (byte)0x86, (byte)0x17, (byte)0xf9},
-            new byte[]{(byte)0xec, (byte)0x6b, (byte)0x43, (byte)0xf0, (byte)0x6b, (byte)0xa8, (byte)0xe9, (byte)0xaa, (byte)0x9d, (byte)0x6c, (byte)0x06, (byte)0x9d, (byte)0xa9, (byte)0x46, (byte)0xee, (byte)0x5d},
-            new byte[]{(byte)0xcb, (byte)0x1e, (byte)0x69, (byte)0x50, (byte)0xf9, (byte)0x57, (byte)0x33, (byte)0x2b, (byte)0xa2, (byte)0x53, (byte)0x11, (byte)0x59, (byte)0x3b, (byte)0xf3, (byte)0x27, (byte)0xc1},
-            new byte[]{(byte)0x2c, (byte)0xee, (byte)0x0c, (byte)0x75, (byte)0x00, (byte)0xda, (byte)0x61, (byte)0x9c, (byte)0xe4, (byte)0xed, (byte)0x03, (byte)0x53, (byte)0x60, (byte)0x0e, (byte)0xd0, (byte)0xd9},
-            new byte[]{(byte)0xf0, (byte)0xb1, (byte)0xa5, (byte)0xa1, (byte)0x96, (byte)0xe9, (byte)0x0c, (byte)0xab, (byte)0x80, (byte)0xbb, (byte)0xba, (byte)0xbc, (byte)0x63, (byte)0xa4, (byte)0xa3, (byte)0x50},
-            new byte[]{(byte)0xae, (byte)0x3d, (byte)0xb1, (byte)0x02, (byte)0x5e, (byte)0x96, (byte)0x29, (byte)0x88, (byte)0xab, (byte)0x0d, (byte)0xde, (byte)0x30, (byte)0x93, (byte)0x8d, (byte)0xca, (byte)0x39},
-            new byte[]{(byte)0x17, (byte)0xbb, (byte)0x8f, (byte)0x38, (byte)0xd5, (byte)0x54, (byte)0xa4, (byte)0x0b, (byte)0x88, (byte)0x14, (byte)0xf3, (byte)0xa8, (byte)0x2e, (byte)0x75, (byte)0xb4, (byte)0x42},
-            new byte[]{(byte)0x34, (byte)0xbb, (byte)0x8a, (byte)0x5b, (byte)0x5f, (byte)0x42, (byte)0x7f, (byte)0xd7, (byte)0xae, (byte)0xb6, (byte)0xb7, (byte)0x79, (byte)0x36, (byte)0x0a, (byte)0x16, (byte)0xf6},
-            new byte[]{(byte)0x26, (byte)0xf6, (byte)0x52, (byte)0x41, (byte)0xcb, (byte)0xe5, (byte)0x54, (byte)0x38, (byte)0x43, (byte)0xce, (byte)0x59, (byte)0x18, (byte)0xff, (byte)0xba, (byte)0xaf, (byte)0xde},
-            new byte[]{(byte)0x4c, (byte)0xe9, (byte)0x9a, (byte)0x54, (byte)0xb9, (byte)0xf3, (byte)0x02, (byte)0x6a, (byte)0xa2, (byte)0xca, (byte)0x9c, (byte)0xf7, (byte)0x83, (byte)0x9e, (byte)0xc9, (byte)0x78},
-            new byte[]{(byte)0xae, (byte)0x51, (byte)0xa5, (byte)0x1a, (byte)0x1b, (byte)0xdf, (byte)0xf7, (byte)0xbe, (byte)0x40, (byte)0xc0, (byte)0x6e, (byte)0x28, (byte)0x22, (byte)0x90, (byte)0x12, (byte)0x35},
-            new byte[]{(byte)0xa0, (byte)0xc1, (byte)0x61, (byte)0x3c, (byte)0xba, (byte)0x7e, (byte)0xd2, (byte)0x2b, (byte)0xc1, (byte)0x73, (byte)0xbc, (byte)0x0f, (byte)0x48, (byte)0xa6, (byte)0x59, (byte)0xcf},
-            new byte[]{(byte)0x75, (byte)0x6a, (byte)0xcc, (byte)0x03, (byte)0x02, (byte)0x28, (byte)0x82, (byte)0x88, (byte)0x4a, (byte)0xd6, (byte)0xbd, (byte)0xfd, (byte)0xe9, (byte)0xc5, (byte)0x9d, (byte)0xa1}
-        };
-
-        private readonly byte[] buffer;
-        private int off;
+        private readonly byte[] m_buf;
+        private int m_bufPos;
 
         public Haraka512Digest()
         {
-            this.buffer = new byte[64];
+            m_buf = new byte[64];
+            m_bufPos = 0;
         }
 
-        public Haraka512Digest(Haraka512Digest digest)
+        public override string AlgorithmName => "Haraka-512";
+
+        public override int GetByteLength() => 64;
+
+        public override void Update(byte input)
         {
-            this.buffer = (byte[])digest.buffer.Clone();
-            this.off = digest.off;
+            if (m_bufPos > 64 - 1)
+                throw new ArgumentException("total input cannot be more than 64 bytes");
+
+            m_buf[m_bufPos++] = input;
         }
 
-        private void Mix512(byte[][] s1, byte[][] s2)
+        public override void BlockUpdate(byte[] input, int inOff, int len)
         {
-            Array.Copy(s1[0], 12, s2[0], 0, 4);
-            Array.Copy(s1[2], 12, s2[0], 4, 4);
-            Array.Copy(s1[1], 12, s2[0], 8, 4);
-            Array.Copy(s1[3], 12, s2[0], 12, 4);
+            if (m_bufPos > 64 - len)
+                throw new ArgumentException("total input cannot be more than 64 bytes");
 
-            Array.Copy(s1[2], 0, s2[1], 0, 4);
-            Array.Copy(s1[0], 0, s2[1], 4, 4);
-            Array.Copy(s1[3], 0, s2[1], 8, 4);
-            Array.Copy(s1[1], 0, s2[1], 12, 4);
+            Array.Copy(input, inOff, m_buf, m_bufPos, len);
+            m_bufPos += len;
+        }
 
-            Array.Copy(s1[2], 4, s2[2], 0, 4);
-            Array.Copy(s1[0], 4, s2[2], 4, 4);
-            Array.Copy(s1[3], 4, s2[2], 8, 4);
-            Array.Copy(s1[1], 4, s2[2], 12, 4);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (m_bufPos > 64 - input.Length)
+                throw new ArgumentException("total input cannot be more than 64 bytes");
 
-            Array.Copy(s1[0], 8, s2[3], 0, 4);
-            Array.Copy(s1[2], 8, s2[3], 4, 4);
-            Array.Copy(s1[1], 8, s2[3], 8, 4);
-            Array.Copy(s1[3], 8, s2[3], 12, 4);
+            input.CopyTo(m_buf.AsSpan(m_bufPos));
+            m_bufPos += input.Length;
         }
+#endif
 
-        private int Haraka512256(byte[] msg, byte[] output, int outOff)
+        public override int DoFinal(byte[] output, int outOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
+            if (m_bufPos != 64)
+                throw new ArgumentException("input must be exactly 64 bytes");
+
+            if (output.Length - outOff < 32)
+                throw new ArgumentException("output too short to receive digest");
+
+            int rv = Haraka512256(m_buf, output, outOff);
+
+            Reset();
+
+            return rv;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            if (m_bufPos != 64)
+                throw new ArgumentException("input must be exactly 64 bytes");
+
+            if (output.Length < 32)
+                throw new ArgumentException("output too short to receive digest");
+
+#if NETCOREAPP3_0_OR_GREATER
+            if (Haraka512_X86.IsSupported)
+            {
+                Haraka512_X86.Hash(m_buf, output);
+                Reset();
+                return 32;
+            }
+#endif
+
+            int rv = Haraka512256(m_buf, output);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
+        public override void Reset()
+        {
+            m_bufPos = 0;
+            Array.Clear(m_buf, 0, 64);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int Haraka512256(ReadOnlySpan<byte> msg, Span<byte> output)
         {
             byte[][] s1 = new byte[4][];
             s1[0] = new byte[16];
@@ -97,61 +111,140 @@ namespace Org.BouncyCastle.Crypto.Digests
             s2[2] = new byte[16];
             s2[3] = new byte[16];
 
-            //-- Unrolled version of above.
+            msg[  ..16].CopyTo(s1[0]);
+            msg[16..32].CopyTo(s1[1]);
+            msg[32..48].CopyTo(s1[2]);
+            msg[48..64].CopyTo(s1[3]);
+
+            s1[0] = AesEnc(s1[0], RC[0]);
+            s1[1] = AesEnc(s1[1], RC[1]);
+            s1[2] = AesEnc(s1[2], RC[2]);
+            s1[3] = AesEnc(s1[3], RC[3]);
+            s1[0] = AesEnc(s1[0], RC[4]);
+            s1[1] = AesEnc(s1[1], RC[5]);
+            s1[2] = AesEnc(s1[2], RC[6]);
+            s1[3] = AesEnc(s1[3], RC[7]);
+            Mix512(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[8]);
+            s1[1] = AesEnc(s2[1], RC[9]);
+            s1[2] = AesEnc(s2[2], RC[10]);
+            s1[3] = AesEnc(s2[3], RC[11]);
+            s1[0] = AesEnc(s1[0], RC[12]);
+            s1[1] = AesEnc(s1[1], RC[13]);
+            s1[2] = AesEnc(s1[2], RC[14]);
+            s1[3] = AesEnc(s1[3], RC[15]);
+            Mix512(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[16]);
+            s1[1] = AesEnc(s2[1], RC[17]);
+            s1[2] = AesEnc(s2[2], RC[18]);
+            s1[3] = AesEnc(s2[3], RC[19]);
+            s1[0] = AesEnc(s1[0], RC[20]);
+            s1[1] = AesEnc(s1[1], RC[21]);
+            s1[2] = AesEnc(s1[2], RC[22]);
+            s1[3] = AesEnc(s1[3], RC[23]);
+            Mix512(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[24]);
+            s1[1] = AesEnc(s2[1], RC[25]);
+            s1[2] = AesEnc(s2[2], RC[26]);
+            s1[3] = AesEnc(s2[3], RC[27]);
+            s1[0] = AesEnc(s1[0], RC[28]);
+            s1[1] = AesEnc(s1[1], RC[29]);
+            s1[2] = AesEnc(s1[2], RC[30]);
+            s1[3] = AesEnc(s1[3], RC[31]);
+            Mix512(s1, s2);
+
+            s1[0] = AesEnc(s2[0], RC[32]);
+            s1[1] = AesEnc(s2[1], RC[33]);
+            s1[2] = AesEnc(s2[2], RC[34]);
+            s1[3] = AesEnc(s2[3], RC[35]);
+            s1[0] = AesEnc(s1[0], RC[36]);
+            s1[1] = AesEnc(s1[1], RC[37]);
+            s1[2] = AesEnc(s1[2], RC[38]);
+            s1[3] = AesEnc(s1[3], RC[39]);
+            Mix512(s1, s2);
+
+            Xor(s2[0], msg, s1[0]);
+            Xor(s2[1], msg[16..], s1[1]);
+            Xor(s2[2], msg[32..], s1[2]);
+            Xor(s2[3], msg[48..], s1[3]);
+
+            s1[0].AsSpan(8, 8).CopyTo(output);
+            s1[1].AsSpan(8, 8).CopyTo(output[8..]);
+            s1[2].AsSpan(0, 8).CopyTo(output[16..]);
+            s1[3].AsSpan(0, 8).CopyTo(output[24..]);
+
+            return DIGEST_SIZE;
+        }
+#else
+        private static int Haraka512256(byte[] msg, byte[] output, int outOff)
+        {
+            byte[][] s1 = new byte[4][];
+            s1[0] = new byte[16];
+            s1[1] = new byte[16];
+            s1[2] = new byte[16];
+            s1[3] = new byte[16];
+            byte[][] s2 = new byte[4][];
+            s2[0] = new byte[16];
+            s2[1] = new byte[16];
+            s2[2] = new byte[16];
+            s2[3] = new byte[16];
 
             Array.Copy(msg, 0, s1[0], 0, 16);
             Array.Copy(msg, 16, s1[1], 0, 16);
             Array.Copy(msg, 32, s1[2], 0, 16);
             Array.Copy(msg, 48, s1[3], 0, 16);
 
-            s1[0] = aesEnc(s1[0], RC[0]);
-            s1[1] = aesEnc(s1[1], RC[1]);
-            s1[2] = aesEnc(s1[2], RC[2]);
-            s1[3] = aesEnc(s1[3], RC[3]);
-            s1[0] = aesEnc(s1[0], RC[4]);
-            s1[1] = aesEnc(s1[1], RC[5]);
-            s1[2] = aesEnc(s1[2], RC[6]);
-            s1[3] = aesEnc(s1[3], RC[7]);
+            s1[0] = AesEnc(s1[0], RC[0]);
+            s1[1] = AesEnc(s1[1], RC[1]);
+            s1[2] = AesEnc(s1[2], RC[2]);
+            s1[3] = AesEnc(s1[3], RC[3]);
+            s1[0] = AesEnc(s1[0], RC[4]);
+            s1[1] = AesEnc(s1[1], RC[5]);
+            s1[2] = AesEnc(s1[2], RC[6]);
+            s1[3] = AesEnc(s1[3], RC[7]);
             Mix512(s1, s2);
 
-            s1[0] = aesEnc(s2[0], RC[8]);
-            s1[1] = aesEnc(s2[1], RC[9]);
-            s1[2] = aesEnc(s2[2], RC[10]);
-            s1[3] = aesEnc(s2[3], RC[11]);
-            s1[0] = aesEnc(s1[0], RC[12]);
-            s1[1] = aesEnc(s1[1], RC[13]);
-            s1[2] = aesEnc(s1[2], RC[14]);
-            s1[3] = aesEnc(s1[3], RC[15]);
+            s1[0] = AesEnc(s2[0], RC[8]);
+            s1[1] = AesEnc(s2[1], RC[9]);
+            s1[2] = AesEnc(s2[2], RC[10]);
+            s1[3] = AesEnc(s2[3], RC[11]);
+            s1[0] = AesEnc(s1[0], RC[12]);
+            s1[1] = AesEnc(s1[1], RC[13]);
+            s1[2] = AesEnc(s1[2], RC[14]);
+            s1[3] = AesEnc(s1[3], RC[15]);
             Mix512(s1, s2);
 
-            s1[0] = aesEnc(s2[0], RC[16]);
-            s1[1] = aesEnc(s2[1], RC[17]);
-            s1[2] = aesEnc(s2[2], RC[18]);
-            s1[3] = aesEnc(s2[3], RC[19]);
-            s1[0] = aesEnc(s1[0], RC[20]);
-            s1[1] = aesEnc(s1[1], RC[21]);
-            s1[2] = aesEnc(s1[2], RC[22]);
-            s1[3] = aesEnc(s1[3], RC[23]);
+            s1[0] = AesEnc(s2[0], RC[16]);
+            s1[1] = AesEnc(s2[1], RC[17]);
+            s1[2] = AesEnc(s2[2], RC[18]);
+            s1[3] = AesEnc(s2[3], RC[19]);
+            s1[0] = AesEnc(s1[0], RC[20]);
+            s1[1] = AesEnc(s1[1], RC[21]);
+            s1[2] = AesEnc(s1[2], RC[22]);
+            s1[3] = AesEnc(s1[3], RC[23]);
             Mix512(s1, s2);
 
-            s1[0] = aesEnc(s2[0], RC[24]);
-            s1[1] = aesEnc(s2[1], RC[25]);
-            s1[2] = aesEnc(s2[2], RC[26]);
-            s1[3] = aesEnc(s2[3], RC[27]);
-            s1[0] = aesEnc(s1[0], RC[28]);
-            s1[1] = aesEnc(s1[1], RC[29]);
-            s1[2] = aesEnc(s1[2], RC[30]);
-            s1[3] = aesEnc(s1[3], RC[31]);
+            s1[0] = AesEnc(s2[0], RC[24]);
+            s1[1] = AesEnc(s2[1], RC[25]);
+            s1[2] = AesEnc(s2[2], RC[26]);
+            s1[3] = AesEnc(s2[3], RC[27]);
+            s1[0] = AesEnc(s1[0], RC[28]);
+            s1[1] = AesEnc(s1[1], RC[29]);
+            s1[2] = AesEnc(s1[2], RC[30]);
+            s1[3] = AesEnc(s1[3], RC[31]);
             Mix512(s1, s2);
 
-            s1[0] = aesEnc(s2[0], RC[32]);
-            s1[1] = aesEnc(s2[1], RC[33]);
-            s1[2] = aesEnc(s2[2], RC[34]);
-            s1[3] = aesEnc(s2[3], RC[35]);
-            s1[0] = aesEnc(s1[0], RC[36]);
-            s1[1] = aesEnc(s1[1], RC[37]);
-            s1[2] = aesEnc(s1[2], RC[38]);
-            s1[3] = aesEnc(s1[3], RC[39]);
+            s1[0] = AesEnc(s2[0], RC[32]);
+            s1[1] = AesEnc(s2[1], RC[33]);
+            s1[2] = AesEnc(s2[2], RC[34]);
+            s1[3] = AesEnc(s2[3], RC[35]);
+            s1[0] = AesEnc(s1[0], RC[36]);
+            s1[1] = AesEnc(s1[1], RC[37]);
+            s1[2] = AesEnc(s1[2], RC[38]);
+            s1[3] = AesEnc(s1[3], RC[39]);
             Mix512(s1, s2);
 
             s1[0] = Xor(s2[0], msg, 0);
@@ -166,56 +259,29 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             return DIGEST_SIZE;
         }
+#endif
 
-        public string GetAlgorithmName()
-        {
-            return "Haraka-512";
-        }
-
-        public override void Update(byte input)
-        {
-            if (off + 1 > 64)
-            {
-                throw new ArgumentException("total input cannot be more than 64 bytes");
-            }
-
-            buffer[off++] = input;
-        }
-
-        public override void BlockUpdate(byte[] input, int inOff, int len)
-        {
-            if (off + len > 64)
-            {
-                throw new ArgumentException("total input cannot be more than 64 bytes");
-            }
-
-            Array.Copy(input, inOff, buffer, off, len);
-            off += len;
-        }
-
-        public override int DoFinal(byte[] output, int outOff)
+        private static void Mix512(byte[][] s1, byte[][] s2)
         {
-            if (off != 64)
-            {
-                throw new ArgumentException("input must be exactly 64 bytes");
-            }
-
-            if (output.Length - outOff < 32)
-            {
-                throw new ArgumentException("output too short to receive digest");
-            }
-
-            int rv = Haraka512256(buffer, output, outOff);
+            Array.Copy(s1[0], 12, s2[0], 0, 4);
+            Array.Copy(s1[2], 12, s2[0], 4, 4);
+            Array.Copy(s1[1], 12, s2[0], 8, 4);
+            Array.Copy(s1[3], 12, s2[0], 12, 4);
 
-            Reset();
+            Array.Copy(s1[2], 0, s2[1], 0, 4);
+            Array.Copy(s1[0], 0, s2[1], 4, 4);
+            Array.Copy(s1[3], 0, s2[1], 8, 4);
+            Array.Copy(s1[1], 0, s2[1], 12, 4);
 
-            return rv;
-        }
+            Array.Copy(s1[2], 4, s2[2], 0, 4);
+            Array.Copy(s1[0], 4, s2[2], 4, 4);
+            Array.Copy(s1[3], 4, s2[2], 8, 4);
+            Array.Copy(s1[1], 4, s2[2], 12, 4);
 
-        public override void Reset()
-        {
-            off = 0;
-            Array.Clear(buffer, 0, 64);
+            Array.Copy(s1[0], 8, s2[3], 0, 4);
+            Array.Copy(s1[2], 8, s2[3], 4, 4);
+            Array.Copy(s1[1], 8, s2[3], 8, 4);
+            Array.Copy(s1[3], 8, s2[3], 12, 4);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/digests/Haraka512_X86.cs b/crypto/src/crypto/digests/Haraka512_X86.cs
new file mode 100644
index 000000000..6dcd3e782
--- /dev/null
+++ b/crypto/src/crypto/digests/Haraka512_X86.cs
@@ -0,0 +1,240 @@
+#if NETCOREAPP3_0_OR_GREATER
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+    using Aes = System.Runtime.Intrinsics.X86.Aes;
+    using Sse2 = System.Runtime.Intrinsics.X86.Sse2;
+
+    public static class Haraka512_X86
+    {
+        public static bool IsSupported => Aes.IsSupported;
+
+        // Haraka round constants
+        internal static readonly Vector128<byte>[] DefaultRoundConstants = new Vector128<byte>[]
+        {
+            Vector128.Create(0x9D, 0x7B, 0x81, 0x75, 0xF0, 0xFE, 0xC5, 0xB2, 0x0A, 0xC0, 0x20, 0xE6, 0x4C, 0x70, 0x84, 0x06),
+            Vector128.Create(0x17, 0xF7, 0x08, 0x2F, 0xA4, 0x6B, 0x0F, 0x64, 0x6B, 0xA0, 0xF3, 0x88, 0xE1, 0xB4, 0x66, 0x8B),
+            Vector128.Create(0x14, 0x91, 0x02, 0x9F, 0x60, 0x9D, 0x02, 0xCF, 0x98, 0x84, 0xF2, 0x53, 0x2D, 0xDE, 0x02, 0x34),
+            Vector128.Create(0x79, 0x4F, 0x5B, 0xFD, 0xAF, 0xBC, 0xF3, 0xBB, 0x08, 0x4F, 0x7B, 0x2E, 0xE6, 0xEA, 0xD6, 0x0E),
+            Vector128.Create(0x44, 0x70, 0x39, 0xBE, 0x1C, 0xCD, 0xEE, 0x79, 0x8B, 0x44, 0x72, 0x48, 0xCB, 0xB0, 0xCF, 0xCB),
+            Vector128.Create(0x7B, 0x05, 0x8A, 0x2B, 0xED, 0x35, 0x53, 0x8D, 0xB7, 0x32, 0x90, 0x6E, 0xEE, 0xCD, 0xEA, 0x7E),
+            Vector128.Create(0x1B, 0xEF, 0x4F, 0xDA, 0x61, 0x27, 0x41, 0xE2, 0xD0, 0x7C, 0x2E, 0x5E, 0x43, 0x8F, 0xC2, 0x67),
+            Vector128.Create(0x3B, 0x0B, 0xC7, 0x1F, 0xE2, 0xFD, 0x5F, 0x67, 0x07, 0xCC, 0xCA, 0xAF, 0xB0, 0xD9, 0x24, 0x29),
+            Vector128.Create(0xEE, 0x65, 0xD4, 0xB9, 0xCA, 0x8F, 0xDB, 0xEC, 0xE9, 0x7F, 0x86, 0xE6, 0xF1, 0x63, 0x4D, 0xAB),
+            Vector128.Create(0x33, 0x7E, 0x03, 0xAD, 0x4F, 0x40, 0x2A, 0x5B, 0x64, 0xCD, 0xB7, 0xD4, 0x84, 0xBF, 0x30, 0x1C),
+            Vector128.Create(0x00, 0x98, 0xF6, 0x8D, 0x2E, 0x8B, 0x02, 0x69, 0xBF, 0x23, 0x17, 0x94, 0xB9, 0x0B, 0xCC, 0xB2),
+            Vector128.Create(0x8A, 0x2D, 0x9D, 0x5C, 0xC8, 0x9E, 0xAA, 0x4A, 0x72, 0x55, 0x6F, 0xDE, 0xA6, 0x78, 0x04, 0xFA),
+            Vector128.Create(0xD4, 0x9F, 0x12, 0x29, 0x2E, 0x4F, 0xFA, 0x0E, 0x12, 0x2A, 0x77, 0x6B, 0x2B, 0x9F, 0xB4, 0xDF),
+            Vector128.Create(0xEE, 0x12, 0x6A, 0xBB, 0xAE, 0x11, 0xD6, 0x32, 0x36, 0xA2, 0x49, 0xF4, 0x44, 0x03, 0xA1, 0x1E),
+            Vector128.Create(0xA6, 0xEC, 0xA8, 0x9C, 0xC9, 0x00, 0x96, 0x5F, 0x84, 0x00, 0x05, 0x4B, 0x88, 0x49, 0x04, 0xAF),
+            Vector128.Create(0xEC, 0x93, 0xE5, 0x27, 0xE3, 0xC7, 0xA2, 0x78, 0x4F, 0x9C, 0x19, 0x9D, 0xD8, 0x5E, 0x02, 0x21),
+            Vector128.Create(0x73, 0x01, 0xD4, 0x82, 0xCD, 0x2E, 0x28, 0xB9, 0xB7, 0xC9, 0x59, 0xA7, 0xF8, 0xAA, 0x3A, 0xBF),
+            Vector128.Create(0x6B, 0x7D, 0x30, 0x10, 0xD9, 0xEF, 0xF2, 0x37, 0x17, 0xB0, 0x86, 0x61, 0x0D, 0x70, 0x60, 0x62),
+            Vector128.Create(0xC6, 0x9A, 0xFC, 0xF6, 0x53, 0x91, 0xC2, 0x81, 0x43, 0x04, 0x30, 0x21, 0xC2, 0x45, 0xCA, 0x5A),
+            Vector128.Create(0x3A, 0x94, 0xD1, 0x36, 0xE8, 0x92, 0xAF, 0x2C, 0xBB, 0x68, 0x6B, 0x22, 0x3C, 0x97, 0x23, 0x92),
+            Vector128.Create(0xB4, 0x71, 0x10, 0xE5, 0x58, 0xB9, 0xBA, 0x6C, 0xEB, 0x86, 0x58, 0x22, 0x38, 0x92, 0xBF, 0xD3),
+            Vector128.Create(0x8D, 0x12, 0xE1, 0x24, 0xDD, 0xFD, 0x3D, 0x93, 0x77, 0xC6, 0xF0, 0xAE, 0xE5, 0x3C, 0x86, 0xDB),
+            Vector128.Create(0xB1, 0x12, 0x22, 0xCB, 0xE3, 0x8D, 0xE4, 0x83, 0x9C, 0xA0, 0xEB, 0xFF, 0x68, 0x62, 0x60, 0xBB),
+            Vector128.Create(0x7D, 0xF7, 0x2B, 0xC7, 0x4E, 0x1A, 0xB9, 0x2D, 0x9C, 0xD1, 0xE4, 0xE2, 0xDC, 0xD3, 0x4B, 0x73),
+            Vector128.Create(0x4E, 0x92, 0xB3, 0x2C, 0xC4, 0x15, 0x14, 0x4B, 0x43, 0x1B, 0x30, 0x61, 0xC3, 0x47, 0xBB, 0x43),
+            Vector128.Create(0x99, 0x68, 0xEB, 0x16, 0xDD, 0x31, 0xB2, 0x03, 0xF6, 0xEF, 0x07, 0xE7, 0xA8, 0x75, 0xA7, 0xDB),
+            Vector128.Create(0x2C, 0x47, 0xCA, 0x7E, 0x02, 0x23, 0x5E, 0x8E, 0x77, 0x59, 0x75, 0x3C, 0x4B, 0x61, 0xF3, 0x6D),
+            Vector128.Create(0xF9, 0x17, 0x86, 0xB8, 0xB9, 0xE5, 0x1B, 0x6D, 0x77, 0x7D, 0xDE, 0xD6, 0x17, 0x5A, 0xA7, 0xCD),
+            Vector128.Create(0x5D, 0xEE, 0x46, 0xA9, 0x9D, 0x06, 0x6C, 0x9D, 0xAA, 0xE9, 0xA8, 0x6B, 0xF0, 0x43, 0x6B, 0xEC),
+            Vector128.Create(0xC1, 0x27, 0xF3, 0x3B, 0x59, 0x11, 0x53, 0xA2, 0x2B, 0x33, 0x57, 0xF9, 0x50, 0x69, 0x1E, 0xCB),
+            Vector128.Create(0xD9, 0xD0, 0x0E, 0x60, 0x53, 0x03, 0xED, 0xE4, 0x9C, 0x61, 0xDA, 0x00, 0x75, 0x0C, 0xEE, 0x2C),
+            Vector128.Create(0x50, 0xA3, 0xA4, 0x63, 0xBC, 0xBA, 0xBB, 0x80, 0xAB, 0x0C, 0xE9, 0x96, 0xA1, 0xA5, 0xB1, 0xF0),
+            Vector128.Create(0x39, 0xCA, 0x8D, 0x93, 0x30, 0xDE, 0x0D, 0xAB, 0x88, 0x29, 0x96, 0x5E, 0x02, 0xB1, 0x3D, 0xAE),
+            Vector128.Create(0x42, 0xB4, 0x75, 0x2E, 0xA8, 0xF3, 0x14, 0x88, 0x0B, 0xA4, 0x54, 0xD5, 0x38, 0x8F, 0xBB, 0x17),
+            Vector128.Create(0xF6, 0x16, 0x0A, 0x36, 0x79, 0xB7, 0xB6, 0xAE, 0xD7, 0x7F, 0x42, 0x5F, 0x5B, 0x8A, 0xBB, 0x34),
+            Vector128.Create(0xDE, 0xAF, 0xBA, 0xFF, 0x18, 0x59, 0xCE, 0x43, 0x38, 0x54, 0xE5, 0xCB, 0x41, 0x52, 0xF6, 0x26),
+            Vector128.Create(0x78, 0xC9, 0x9E, 0x83, 0xF7, 0x9C, 0xCA, 0xA2, 0x6A, 0x02, 0xF3, 0xB9, 0x54, 0x9A, 0xE9, 0x4C),
+            Vector128.Create(0x35, 0x12, 0x90, 0x22, 0x28, 0x6E, 0xC0, 0x40, 0xBE, 0xF7, 0xDF, 0x1B, 0x1A, 0xA5, 0x51, 0xAE),
+            Vector128.Create(0xCF, 0x59, 0xA6, 0x48, 0x0F, 0xBC, 0x73, 0xC1, 0x2B, 0xD2, 0x7E, 0xBA, 0x3C, 0x61, 0xC1, 0xA0),
+            Vector128.Create(0xA1, 0x9D, 0xC5, 0xE9, 0xFD, 0xBD, 0xD6, 0x4A, 0x88, 0x82, 0x28, 0x02, 0x03, 0xCC, 0x6A, 0x75),
+        };
+
+        public static void Hash(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka512_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+            var s2 = Load128(input[32..48]);
+            var s3 = Load128(input[48..64]);
+
+            ImplRounds(ref s0, ref s1, ref s2, ref s3, DefaultRoundConstants.AsSpan(0, 40));
+
+            s0 = Sse2.Xor(s0, Load128(input[  ..16]));
+            s1 = Sse2.Xor(s1, Load128(input[16..32]));
+            s2 = Sse2.Xor(s2, Load128(input[32..48]));
+            s3 = Sse2.Xor(s3, Load128(input[48..64]));
+
+            Store64(s0.GetUpper(), output[  .. 8]);
+            Store64(s1.GetUpper(), output[ 8..16]);
+            Store64(s2.GetLower(), output[16..24]);
+            Store64(s3.GetLower(), output[24..32]);
+        }
+
+        public static void Hash(ReadOnlySpan<byte> input, Span<byte> output,
+            ReadOnlySpan<Vector128<byte>> roundConstants)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka512_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+            var s2 = Load128(input[32..48]);
+            var s3 = Load128(input[48..64]);
+
+            ImplRounds(ref s0, ref s1, ref s2, ref s3, roundConstants[..40]);
+
+            s0 = Sse2.Xor(s0, Load128(input[  ..16]));
+            s1 = Sse2.Xor(s1, Load128(input[16..32]));
+            s2 = Sse2.Xor(s2, Load128(input[32..48]));
+            s3 = Sse2.Xor(s3, Load128(input[48..64]));
+
+            Store64(s0.GetUpper(), output[  .. 8]);
+            Store64(s1.GetUpper(), output[ 8..16]);
+            Store64(s2.GetLower(), output[16..24]);
+            Store64(s3.GetLower(), output[24..32]);
+        }
+
+        public static void Permute(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka512_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+            var s2 = Load128(input[32..48]);
+            var s3 = Load128(input[48..64]);
+
+            ImplRounds(ref s0, ref s1, ref s2, ref s3, DefaultRoundConstants.AsSpan(0, 40));
+
+            Store128(s0, output[  ..16]);
+            Store128(s1, output[16..32]);
+            Store128(s2, output[32..48]);
+            Store128(s3, output[48..64]);
+        }
+
+        public static void Permute(ReadOnlySpan<byte> input, Span<byte> output,
+            ReadOnlySpan<Vector128<byte>> roundConstants)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(Haraka512_X86));
+
+            var s0 = Load128(input[  ..16]);
+            var s1 = Load128(input[16..32]);
+            var s2 = Load128(input[32..48]);
+            var s3 = Load128(input[48..64]);
+
+            ImplRounds(ref s0, ref s1, ref s2, ref s3, roundConstants[..40]);
+
+            Store128(s0, output[  ..16]);
+            Store128(s1, output[16..32]);
+            Store128(s2, output[32..48]);
+            Store128(s3, output[48..64]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplRounds(ref Vector128<byte> s0, ref Vector128<byte> s1, ref Vector128<byte> s2,
+            ref Vector128<byte> s3, ReadOnlySpan<Vector128<byte>> rc)
+        {
+            ImplRound(ref s0, ref s1, ref s2, ref s3, rc[  .. 8]);
+            ImplRound(ref s0, ref s1, ref s2, ref s3, rc[ 8..16]);
+            ImplRound(ref s0, ref s1, ref s2, ref s3, rc[16..24]);
+            ImplRound(ref s0, ref s1, ref s2, ref s3, rc[24..32]);
+            ImplRound(ref s0, ref s1, ref s2, ref s3, rc[32..40]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplRound(ref Vector128<byte> s0, ref Vector128<byte> s1, ref Vector128<byte> s2,
+            ref Vector128<byte> s3, ReadOnlySpan<Vector128<byte>> rc)
+        {
+            ImplAes(ref s0, ref s1, ref s2, ref s3, rc[..8]);
+            ImplMix(ref s0, ref s1, ref s2, ref s3);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplAes(ref Vector128<byte> s0, ref Vector128<byte> s1, ref Vector128<byte> s2,
+            ref Vector128<byte> s3, ReadOnlySpan<Vector128<byte>> rc)
+        {
+            s0 = Aes.Encrypt(s0, rc[0]);
+            s1 = Aes.Encrypt(s1, rc[1]);
+            s2 = Aes.Encrypt(s2, rc[2]);
+            s3 = Aes.Encrypt(s3, rc[3]);
+
+            s0 = Aes.Encrypt(s0, rc[4]);
+            s1 = Aes.Encrypt(s1, rc[5]);
+            s2 = Aes.Encrypt(s2, rc[6]);
+            s3 = Aes.Encrypt(s3, rc[7]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ImplMix(ref Vector128<byte> s0, ref Vector128<byte> s1, ref Vector128<byte> s2,
+            ref Vector128<byte> s3)
+        {
+            var t0 = s0.AsUInt32();
+            var t1 = s1.AsUInt32();
+            var t2 = s2.AsUInt32();
+            var t3 = s3.AsUInt32();
+
+            var u0 = Sse2.UnpackLow(t0, t1);
+            var u1 = Sse2.UnpackHigh(t0, t1);
+            var u2 = Sse2.UnpackLow(t2, t3);
+            var u3 = Sse2.UnpackHigh(t2, t3);
+
+            s0 = Sse2.UnpackHigh(u1, u3).AsByte();
+            s1 = Sse2.UnpackLow(u2, u0).AsByte();
+            s2 = Sse2.UnpackHigh(u2, u0).AsByte();
+            s3 = Sse2.UnpackLow(u1, u3).AsByte();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Vector128<byte> Load128(ReadOnlySpan<byte> t)
+        {
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+                return MemoryMarshal.Read<Vector128<byte>>(t);
+
+            return Vector128.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[..8]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[8..])
+            ).AsByte();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Store128(Vector128<byte> s, Span<byte> t)
+        {
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                MemoryMarshal.Write(t, ref s);
+                return;
+            }
+
+            var u = s.AsUInt64();
+            BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Store64(Vector64<byte> s, Span<byte> t)
+        {
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector64<byte>>() == 8)
+            {
+                MemoryMarshal.Write(t, ref s);
+                return;
+            }
+
+            var u = s.AsUInt64();
+            BinaryPrimitives.WriteUInt64LittleEndian(t, u.ToScalar());
+        }
+    }
+}
+#endif
diff --git a/crypto/src/crypto/digests/HarakaBase.cs b/crypto/src/crypto/digests/HarakaBase.cs
index 6157198f1..1ca688914 100644
--- a/crypto/src/crypto/digests/HarakaBase.cs
+++ b/crypto/src/crypto/digests/HarakaBase.cs
@@ -1,105 +1,126 @@
 using System;
-using System.Collections.Generic;
-using System.Text;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
-    public abstract class HarakaBase : IDigest
+    public abstract class HarakaBase
+        : IDigest
     {
-        protected static readonly int DIGEST_SIZE = 32;
-
-        protected static readonly byte[,] S = {
-        {(byte)0x63, (byte)0x7c, (byte)0x77, (byte)0x7b, (byte)0xf2, (byte)0x6b, (byte)0x6f, (byte)0xc5, (byte)0x30, (byte)0x01, (byte)0x67, (byte)0x2b, (byte)0xfe, (byte)0xd7, (byte)0xab, (byte)0x76},
-        {(byte)0xca, (byte)0x82, (byte)0xc9, (byte)0x7d, (byte)0xfa, (byte)0x59, (byte)0x47, (byte)0xf0, (byte)0xad, (byte)0xd4, (byte)0xa2, (byte)0xaf, (byte)0x9c, (byte)0xa4, (byte)0x72, (byte)0xc0},
-        {(byte)0xb7, (byte)0xfd, (byte)0x93, (byte)0x26, (byte)0x36, (byte)0x3f, (byte)0xf7, (byte)0xcc, (byte)0x34, (byte)0xa5, (byte)0xe5, (byte)0xf1, (byte)0x71, (byte)0xd8, (byte)0x31, (byte)0x15},
-        {(byte)0x04, (byte)0xc7, (byte)0x23, (byte)0xc3, (byte)0x18, (byte)0x96, (byte)0x05, (byte)0x9a, (byte)0x07, (byte)0x12, (byte)0x80, (byte)0xe2, (byte)0xeb, (byte)0x27, (byte)0xb2, (byte)0x75},
-        {(byte)0x09, (byte)0x83, (byte)0x2c, (byte)0x1a, (byte)0x1b, (byte)0x6e, (byte)0x5a, (byte)0xa0, (byte)0x52, (byte)0x3b, (byte)0xd6, (byte)0xb3, (byte)0x29, (byte)0xe3, (byte)0x2f, (byte)0x84},
-        {(byte)0x53, (byte)0xd1, (byte)0x00, (byte)0xed, (byte)0x20, (byte)0xfc, (byte)0xb1, (byte)0x5b, (byte)0x6a, (byte)0xcb, (byte)0xbe, (byte)0x39, (byte)0x4a, (byte)0x4c, (byte)0x58, (byte)0xcf},
-        {(byte)0xd0, (byte)0xef, (byte)0xaa, (byte)0xfb, (byte)0x43, (byte)0x4d, (byte)0x33, (byte)0x85, (byte)0x45, (byte)0xf9, (byte)0x02, (byte)0x7f, (byte)0x50, (byte)0x3c, (byte)0x9f, (byte)0xa8},
-        {(byte)0x51, (byte)0xa3, (byte)0x40, (byte)0x8f, (byte)0x92, (byte)0x9d, (byte)0x38, (byte)0xf5, (byte)0xbc, (byte)0xb6, (byte)0xda, (byte)0x21, (byte)0x10, (byte)0xff, (byte)0xf3, (byte)0xd2},
-        {(byte)0xcd, (byte)0x0c, (byte)0x13, (byte)0xec, (byte)0x5f, (byte)0x97, (byte)0x44, (byte)0x17, (byte)0xc4, (byte)0xa7, (byte)0x7e, (byte)0x3d, (byte)0x64, (byte)0x5d, (byte)0x19, (byte)0x73},
-        {(byte)0x60, (byte)0x81, (byte)0x4f, (byte)0xdc, (byte)0x22, (byte)0x2a, (byte)0x90, (byte)0x88, (byte)0x46, (byte)0xee, (byte)0xb8, (byte)0x14, (byte)0xde, (byte)0x5e, (byte)0x0b, (byte)0xdb},
-        {(byte)0xe0, (byte)0x32, (byte)0x3a, (byte)0x0a, (byte)0x49, (byte)0x06, (byte)0x24, (byte)0x5c, (byte)0xc2, (byte)0xd3, (byte)0xac, (byte)0x62, (byte)0x91, (byte)0x95, (byte)0xe4, (byte)0x79},
-        {(byte)0xe7, (byte)0xc8, (byte)0x37, (byte)0x6d, (byte)0x8d, (byte)0xd5, (byte)0x4e, (byte)0xa9, (byte)0x6c, (byte)0x56, (byte)0xf4, (byte)0xea, (byte)0x65, (byte)0x7a, (byte)0xae, (byte)0x08},
-        {(byte)0xba, (byte)0x78, (byte)0x25, (byte)0x2e, (byte)0x1c, (byte)0xa6, (byte)0xb4, (byte)0xc6, (byte)0xe8, (byte)0xdd, (byte)0x74, (byte)0x1f, (byte)0x4b, (byte)0xbd, (byte)0x8b, (byte)0x8a},
-        {(byte)0x70, (byte)0x3e, (byte)0xb5, (byte)0x66, (byte)0x48, (byte)0x03, (byte)0xf6, (byte)0x0e, (byte)0x61, (byte)0x35, (byte)0x57, (byte)0xb9, (byte)0x86, (byte)0xc1, (byte)0x1d, (byte)0x9e},
-        {(byte)0xe1, (byte)0xf8, (byte)0x98, (byte)0x11, (byte)0x69, (byte)0xd9, (byte)0x8e, (byte)0x94, (byte)0x9b, (byte)0x1e, (byte)0x87, (byte)0xe9, (byte)0xce, (byte)0x55, (byte)0x28, (byte)0xdf},
-        {(byte)0x8c, (byte)0xa1, (byte)0x89, (byte)0x0d, (byte)0xbf, (byte)0xe6, (byte)0x42, (byte)0x68, (byte)0x41, (byte)0x99, (byte)0x2d, (byte)0x0f, (byte)0xb0, (byte)0x54, (byte)0xbb, (byte)0x16}};
-
-        public string AlgorithmName
+        internal static readonly int DIGEST_SIZE = 32;
+
+        // Haraka round constants
+        internal static readonly byte[][] RC = new byte[][]
         {
-            get { return "Haraka Base"; }
-        }
+            new byte[]{ 0x9D, 0x7B, 0x81, 0x75, 0xF0, 0xFE, 0xC5, 0xB2, 0x0A, 0xC0, 0x20, 0xE6, 0x4C, 0x70, 0x84, 0x06 },
+            new byte[]{ 0x17, 0xF7, 0x08, 0x2F, 0xA4, 0x6B, 0x0F, 0x64, 0x6B, 0xA0, 0xF3, 0x88, 0xE1, 0xB4, 0x66, 0x8B },
+            new byte[]{ 0x14, 0x91, 0x02, 0x9F, 0x60, 0x9D, 0x02, 0xCF, 0x98, 0x84, 0xF2, 0x53, 0x2D, 0xDE, 0x02, 0x34 },
+            new byte[]{ 0x79, 0x4F, 0x5B, 0xFD, 0xAF, 0xBC, 0xF3, 0xBB, 0x08, 0x4F, 0x7B, 0x2E, 0xE6, 0xEA, 0xD6, 0x0E },
+            new byte[]{ 0x44, 0x70, 0x39, 0xBE, 0x1C, 0xCD, 0xEE, 0x79, 0x8B, 0x44, 0x72, 0x48, 0xCB, 0xB0, 0xCF, 0xCB },
+            new byte[]{ 0x7B, 0x05, 0x8A, 0x2B, 0xED, 0x35, 0x53, 0x8D, 0xB7, 0x32, 0x90, 0x6E, 0xEE, 0xCD, 0xEA, 0x7E },
+            new byte[]{ 0x1B, 0xEF, 0x4F, 0xDA, 0x61, 0x27, 0x41, 0xE2, 0xD0, 0x7C, 0x2E, 0x5E, 0x43, 0x8F, 0xC2, 0x67 },
+            new byte[]{ 0x3B, 0x0B, 0xC7, 0x1F, 0xE2, 0xFD, 0x5F, 0x67, 0x07, 0xCC, 0xCA, 0xAF, 0xB0, 0xD9, 0x24, 0x29 },
+            new byte[]{ 0xEE, 0x65, 0xD4, 0xB9, 0xCA, 0x8F, 0xDB, 0xEC, 0xE9, 0x7F, 0x86, 0xE6, 0xF1, 0x63, 0x4D, 0xAB },
+            new byte[]{ 0x33, 0x7E, 0x03, 0xAD, 0x4F, 0x40, 0x2A, 0x5B, 0x64, 0xCD, 0xB7, 0xD4, 0x84, 0xBF, 0x30, 0x1C },
+            new byte[]{ 0x00, 0x98, 0xF6, 0x8D, 0x2E, 0x8B, 0x02, 0x69, 0xBF, 0x23, 0x17, 0x94, 0xB9, 0x0B, 0xCC, 0xB2 },
+            new byte[]{ 0x8A, 0x2D, 0x9D, 0x5C, 0xC8, 0x9E, 0xAA, 0x4A, 0x72, 0x55, 0x6F, 0xDE, 0xA6, 0x78, 0x04, 0xFA },
+            new byte[]{ 0xD4, 0x9F, 0x12, 0x29, 0x2E, 0x4F, 0xFA, 0x0E, 0x12, 0x2A, 0x77, 0x6B, 0x2B, 0x9F, 0xB4, 0xDF },
+            new byte[]{ 0xEE, 0x12, 0x6A, 0xBB, 0xAE, 0x11, 0xD6, 0x32, 0x36, 0xA2, 0x49, 0xF4, 0x44, 0x03, 0xA1, 0x1E },
+            new byte[]{ 0xA6, 0xEC, 0xA8, 0x9C, 0xC9, 0x00, 0x96, 0x5F, 0x84, 0x00, 0x05, 0x4B, 0x88, 0x49, 0x04, 0xAF },
+            new byte[]{ 0xEC, 0x93, 0xE5, 0x27, 0xE3, 0xC7, 0xA2, 0x78, 0x4F, 0x9C, 0x19, 0x9D, 0xD8, 0x5E, 0x02, 0x21 },
+            new byte[]{ 0x73, 0x01, 0xD4, 0x82, 0xCD, 0x2E, 0x28, 0xB9, 0xB7, 0xC9, 0x59, 0xA7, 0xF8, 0xAA, 0x3A, 0xBF },
+            new byte[]{ 0x6B, 0x7D, 0x30, 0x10, 0xD9, 0xEF, 0xF2, 0x37, 0x17, 0xB0, 0x86, 0x61, 0x0D, 0x70, 0x60, 0x62 },
+            new byte[]{ 0xC6, 0x9A, 0xFC, 0xF6, 0x53, 0x91, 0xC2, 0x81, 0x43, 0x04, 0x30, 0x21, 0xC2, 0x45, 0xCA, 0x5A },
+            new byte[]{ 0x3A, 0x94, 0xD1, 0x36, 0xE8, 0x92, 0xAF, 0x2C, 0xBB, 0x68, 0x6B, 0x22, 0x3C, 0x97, 0x23, 0x92 },
+            new byte[]{ 0xB4, 0x71, 0x10, 0xE5, 0x58, 0xB9, 0xBA, 0x6C, 0xEB, 0x86, 0x58, 0x22, 0x38, 0x92, 0xBF, 0xD3 },
+            new byte[]{ 0x8D, 0x12, 0xE1, 0x24, 0xDD, 0xFD, 0x3D, 0x93, 0x77, 0xC6, 0xF0, 0xAE, 0xE5, 0x3C, 0x86, 0xDB },
+            new byte[]{ 0xB1, 0x12, 0x22, 0xCB, 0xE3, 0x8D, 0xE4, 0x83, 0x9C, 0xA0, 0xEB, 0xFF, 0x68, 0x62, 0x60, 0xBB },
+            new byte[]{ 0x7D, 0xF7, 0x2B, 0xC7, 0x4E, 0x1A, 0xB9, 0x2D, 0x9C, 0xD1, 0xE4, 0xE2, 0xDC, 0xD3, 0x4B, 0x73 },
+            new byte[]{ 0x4E, 0x92, 0xB3, 0x2C, 0xC4, 0x15, 0x14, 0x4B, 0x43, 0x1B, 0x30, 0x61, 0xC3, 0x47, 0xBB, 0x43 },
+            new byte[]{ 0x99, 0x68, 0xEB, 0x16, 0xDD, 0x31, 0xB2, 0x03, 0xF6, 0xEF, 0x07, 0xE7, 0xA8, 0x75, 0xA7, 0xDB },
+            new byte[]{ 0x2C, 0x47, 0xCA, 0x7E, 0x02, 0x23, 0x5E, 0x8E, 0x77, 0x59, 0x75, 0x3C, 0x4B, 0x61, 0xF3, 0x6D },
+            new byte[]{ 0xF9, 0x17, 0x86, 0xB8, 0xB9, 0xE5, 0x1B, 0x6D, 0x77, 0x7D, 0xDE, 0xD6, 0x17, 0x5A, 0xA7, 0xCD },
+            new byte[]{ 0x5D, 0xEE, 0x46, 0xA9, 0x9D, 0x06, 0x6C, 0x9D, 0xAA, 0xE9, 0xA8, 0x6B, 0xF0, 0x43, 0x6B, 0xEC },
+            new byte[]{ 0xC1, 0x27, 0xF3, 0x3B, 0x59, 0x11, 0x53, 0xA2, 0x2B, 0x33, 0x57, 0xF9, 0x50, 0x69, 0x1E, 0xCB },
+            new byte[]{ 0xD9, 0xD0, 0x0E, 0x60, 0x53, 0x03, 0xED, 0xE4, 0x9C, 0x61, 0xDA, 0x00, 0x75, 0x0C, 0xEE, 0x2C },
+            new byte[]{ 0x50, 0xA3, 0xA4, 0x63, 0xBC, 0xBA, 0xBB, 0x80, 0xAB, 0x0C, 0xE9, 0x96, 0xA1, 0xA5, 0xB1, 0xF0 },
+            new byte[]{ 0x39, 0xCA, 0x8D, 0x93, 0x30, 0xDE, 0x0D, 0xAB, 0x88, 0x29, 0x96, 0x5E, 0x02, 0xB1, 0x3D, 0xAE },
+            new byte[]{ 0x42, 0xB4, 0x75, 0x2E, 0xA8, 0xF3, 0x14, 0x88, 0x0B, 0xA4, 0x54, 0xD5, 0x38, 0x8F, 0xBB, 0x17 },
+            new byte[]{ 0xF6, 0x16, 0x0A, 0x36, 0x79, 0xB7, 0xB6, 0xAE, 0xD7, 0x7F, 0x42, 0x5F, 0x5B, 0x8A, 0xBB, 0x34 },
+            new byte[]{ 0xDE, 0xAF, 0xBA, 0xFF, 0x18, 0x59, 0xCE, 0x43, 0x38, 0x54, 0xE5, 0xCB, 0x41, 0x52, 0xF6, 0x26 },
+            new byte[]{ 0x78, 0xC9, 0x9E, 0x83, 0xF7, 0x9C, 0xCA, 0xA2, 0x6A, 0x02, 0xF3, 0xB9, 0x54, 0x9A, 0xE9, 0x4C },
+            new byte[]{ 0x35, 0x12, 0x90, 0x22, 0x28, 0x6E, 0xC0, 0x40, 0xBE, 0xF7, 0xDF, 0x1B, 0x1A, 0xA5, 0x51, 0xAE },
+            new byte[]{ 0xCF, 0x59, 0xA6, 0x48, 0x0F, 0xBC, 0x73, 0xC1, 0x2B, 0xD2, 0x7E, 0xBA, 0x3C, 0x61, 0xC1, 0xA0 },
+            new byte[]{ 0xA1, 0x9D, 0xC5, 0xE9, 0xFD, 0xBD, 0xD6, 0x4A, 0x88, 0x82, 0x28, 0x02, 0x03, 0xCC, 0x6A, 0x75 },
+        };
+
+        private static readonly byte[,] S =
+        {
+            { 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76 },
+            { 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0 },
+            { 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15 },
+            { 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75 },
+            { 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84 },
+            { 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF },
+            { 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8 },
+            { 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2 },
+            { 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73 },
+            { 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB },
+            { 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79 },
+            { 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08 },
+            { 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A },
+            { 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E },
+            { 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF },
+            { 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 },
+        };
 
-        static byte sBox(byte x)
+        private static byte SBox(byte x)
         {
-            return S[(uint)(((x & 0xFF) >> 4)),x & 0xF];
+            return S[(uint)x >> 4, x & 0xFU];
         }
 
-        static byte[] subBytes(byte[] s)
+        private static byte[] SubBytes(byte[] s)
         {
             byte[] output = new byte[s.Length];
             for(int i = 0; i < 16; ++i)
             {
-                output[i] = sBox(s[i]);
+                output[i] = SBox(s[i]);
             }
             return output;
         }
 
-        static byte[] shiftRows(byte[] s)
+        private static byte[] ShiftRows(byte[] s)
         {
             return new byte[]{
-            s[0], s[5], s[10], s[15],
-            s[4], s[9], s[14], s[3],
-            s[8], s[13], s[2], s[7],
-            s[12], s[1], s[6], s[11]
-        };
+                s[0], s[5], s[10], s[15],
+                s[4], s[9], s[14], s[3],
+                s[8], s[13], s[2], s[7],
+                s[12], s[1], s[6], s[11]
+            };
         }
 
-        protected static byte[] aesEnc(byte[] s, byte[] rk)
+        internal static byte[] AesEnc(byte[] s, byte[] rk)
         {
-            s = subBytes(s);
-            s = shiftRows(s);
-            s = mixColumns(s);
-            xorReverse(s, rk);
+            s = SubBytes(s);
+            s = ShiftRows(s);
+            s = MixColumns(s);
+            XorTo(rk, s);
             return s;
         }
 
-        static byte xTime(byte x)
+        private static byte MulX(byte p)
         {
-            if ((x >> 7) > 0)
-            {
-                return (byte)(((x << 1) ^ 0x1b) & 0xff);
-            }
-            else
-            {
-                return (byte)((x << 1) & 0xff);
-            }
+            return (byte)(((p & 0x7F) << 1) ^ (((uint)p >> 7) * 0x1BU));
         }
 
-
-        static void xorReverse(byte[] x, byte[] y)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void Xor(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y, Span<byte> z)
         {
-            x[0] = (byte)(x[0] ^ y[15]);
-            x[1] = (byte)(x[1] ^ y[14]);
-            x[2] = (byte)(x[2] ^ y[13]);
-            x[3] = (byte)(x[3] ^ y[12]);
-            x[4] = (byte)(x[4] ^ y[11]);
-            x[5] = (byte)(x[5] ^ y[10]);
-            x[6] = (byte)(x[6] ^ y[9]);
-            x[7] = (byte)(x[7] ^ y[8]);
-            x[8] = (byte)(x[8] ^ y[7]);
-            x[9] = (byte)(x[9] ^ y[6]);
-            x[10] = (byte)(x[10] ^ y[5]);
-            x[11] = (byte)(x[11] ^ y[4]);
-            x[12] = (byte)(x[12] ^ y[3]);
-            x[13] = (byte)(x[13] ^ y[2]);
-            x[14] = (byte)(x[14] ^ y[1]);
-            x[15] = (byte)(x[15] ^ y[0]);
+            for (int i = 0; i < z.Length; i++)
+            {
+                z[i] = (byte)(x[i] ^ y[i]);
+            }
         }
-
-
-        protected static byte[] Xor(byte[] x, byte[] y, int yStart)
+#else
+        internal static byte[] Xor(byte[] x, byte[] y, int yStart)
         {
             byte[] output = new byte[16];
             for (int i = 0; i < output.Length; i++)
@@ -108,33 +129,43 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
             return output;
         }
+#endif
 
+        private static void XorTo(byte[] x, byte[] z)
+        {
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] ^= x[i + 0];
+                z[i + 1] ^= x[i + 1];
+                z[i + 2] ^= x[i + 2];
+                z[i + 3] ^= x[i + 3];
+            }
+        }
 
-        static private byte[] mixColumns(byte[] s)
+        private static byte[] MixColumns(byte[] s)
         {
             byte[] output = new byte[s.Length];
             int j = 0, i4;
             for (int i = 0; i < 4; i++)
             {
                 i4 = i << 2;
-                output[j++] = (byte)(xTime(s[i4]) ^ xTime(s[i4 + 1]) ^ s[i4 + 1] ^ s[i4 + 2] ^ s[i4 + 3]);
-                output[j++] = (byte)(s[i4] ^ xTime(s[i4 + 1]) ^ xTime(s[i4 + 2]) ^ s[i4 + 2] ^ s[i4 + 3]);
-                output[j++] = (byte)(s[i4] ^ s[i4 + 1] ^ xTime(s[i4 + 2]) ^ xTime(s[i4 + 3]) ^ s[i4 + 3]);
-                output[j++] = (byte)(xTime(s[i4]) ^ s[i4] ^ s[i4 + 1] ^ s[i4 + 2] ^ xTime(s[i4 + 3]));
+                output[j++] = (byte)(MulX(s[i4]) ^ MulX(s[i4 + 1]) ^ s[i4 + 1] ^ s[i4 + 2] ^ s[i4 + 3]);
+                output[j++] = (byte)(s[i4] ^ MulX(s[i4 + 1]) ^ MulX(s[i4 + 2]) ^ s[i4 + 2] ^ s[i4 + 3]);
+                output[j++] = (byte)(s[i4] ^ s[i4 + 1] ^ MulX(s[i4 + 2]) ^ MulX(s[i4 + 3]) ^ s[i4 + 3]);
+                output[j++] = (byte)(MulX(s[i4]) ^ s[i4] ^ s[i4 + 1] ^ s[i4 + 2] ^ MulX(s[i4 + 3]));
             }
 
             return output;
         }
 
+        public abstract string AlgorithmName { get; }
+
         public int GetDigestSize()
         {
             return DIGEST_SIZE;
         }
 
-        public int GetByteLength()
-        {
-            throw new NotImplementedException();
-        }
+        public abstract int GetByteLength();
 
         public abstract void Update(byte input);
 
@@ -144,5 +175,10 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         public abstract void Reset();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void BlockUpdate(ReadOnlySpan<byte> input);
+
+        public abstract int DoFinal(Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/KeccakDigest.cs b/crypto/src/crypto/digests/KeccakDigest.cs
index 2da2e099e..9f4a36b88 100644
--- a/crypto/src/crypto/digests/KeccakDigest.cs
+++ b/crypto/src/crypto/digests/KeccakDigest.cs
@@ -76,6 +76,13 @@ namespace Org.BouncyCastle.Crypto.Digests
             Absorb(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            Absorb(input);
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
             Squeeze(output, outOff, fixedOutputLength);
@@ -85,6 +92,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             return GetDigestSize();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            int digestSize = GetDigestSize();
+            Squeeze(output[..digestSize]);
+
+            Reset();
+
+            return digestSize;
+        }
+#endif
+
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
@@ -199,6 +218,46 @@ namespace Org.BouncyCastle.Crypto.Digests
             this.bitsInQueue = remaining << 3;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected void Absorb(ReadOnlySpan<byte> data)
+        {
+            if ((bitsInQueue & 7) != 0)
+                throw new InvalidOperationException("attempt to absorb with odd length queue");
+            if (squeezing)
+                throw new InvalidOperationException("attempt to absorb while squeezing");
+
+            int bytesInQueue = bitsInQueue >> 3;
+            int rateBytes = rate >> 3;
+
+            int len = data.Length;
+            int available = rateBytes - bytesInQueue;
+            if (len < available)
+            {
+                data.CopyTo(dataQueue.AsSpan(bytesInQueue));
+                this.bitsInQueue += len << 3;
+                return;
+            }
+
+            int count = 0;
+            if (bytesInQueue > 0)
+            {
+                data[..available].CopyTo(dataQueue.AsSpan(bytesInQueue));
+                count += available;
+                KeccakAbsorb(dataQueue, 0);
+            }
+
+            int remaining;
+            while ((remaining = len - count) >= rateBytes)
+            {
+                KeccakAbsorb(data[count..]);
+                count += rateBytes;
+            }
+
+            data[count..].CopyTo(dataQueue.AsSpan());
+            this.bitsInQueue = remaining << 3;
+        }
+#endif
+
         protected void AbsorbBits(int data, int bits)
         {
             if (bits < 1 || bits > 7)
@@ -270,6 +329,30 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected void Squeeze(Span<byte> output)
+        {
+            if (!squeezing)
+            {
+                PadAndSwitchToSqueezingPhase();
+            }
+            long outputLength = (long)output.Length << 3;
+
+            long i = 0;
+            while (i < outputLength)
+            {
+                if (bitsInQueue == 0)
+                {
+                    KeccakExtract();
+                }
+                int partialBlock = (int)System.Math.Min(bitsInQueue, outputLength - i);
+                dataQueue.AsSpan((rate - bitsInQueue) >> 3, partialBlock >> 3).CopyTo(output[(int)(i >> 3)..]);
+                bitsInQueue -= partialBlock;
+                i += partialBlock;
+            }
+        }
+#endif
+
         private void KeccakAbsorb(byte[] data, int off)
         {
             int count = rate >> 6;
@@ -282,6 +365,20 @@ namespace Org.BouncyCastle.Crypto.Digests
             KeccakPermutation();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void KeccakAbsorb(ReadOnlySpan<byte> data)
+        {
+            int count = rate >> 6, off = 0;
+            for (int i = 0; i < count; ++i)
+            {
+                state[i] ^= Pack.LE_To_UInt64(data[off..]);
+                off += 8;
+            }
+
+            KeccakPermutation();
+        }
+#endif
+
         private void KeccakExtract()
         {
             KeccakPermutation();
@@ -310,11 +407,11 @@ namespace Org.BouncyCastle.Crypto.Digests
                 ulong c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23;
                 ulong c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24;
 
-                ulong d1 = (c1 << 1 | c1 >> -1) ^ c4;
-                ulong d2 = (c2 << 1 | c2 >> -1) ^ c0;
-                ulong d3 = (c3 << 1 | c3 >> -1) ^ c1;
-                ulong d4 = (c4 << 1 | c4 >> -1) ^ c2;
-                ulong d0 = (c0 << 1 | c0 >> -1) ^ c3;
+                ulong d1 = Longs.RotateLeft(c1, 1) ^ c4;
+                ulong d2 = Longs.RotateLeft(c2, 1) ^ c0;
+                ulong d3 = Longs.RotateLeft(c3, 1) ^ c1;
+                ulong d4 = Longs.RotateLeft(c4, 1) ^ c2;
+                ulong d0 = Longs.RotateLeft(c0, 1) ^ c3;
 
                 a00 ^= d1; a05 ^= d1; a10 ^= d1; a15 ^= d1; a20 ^= d1;
                 a01 ^= d2; a06 ^= d2; a11 ^= d2; a16 ^= d2; a21 ^= d2;
@@ -323,30 +420,30 @@ namespace Org.BouncyCastle.Crypto.Digests
                 a04 ^= d0; a09 ^= d0; a14 ^= d0; a19 ^= d0; a24 ^= d0;
 
                 // rho/pi
-                c1  = a01 <<  1 | a01 >> 63;
-                a01 = a06 << 44 | a06 >> 20;
-                a06 = a09 << 20 | a09 >> 44;
-                a09 = a22 << 61 | a22 >>  3;
-                a22 = a14 << 39 | a14 >> 25;
-                a14 = a20 << 18 | a20 >> 46;
-                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;
-                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;
-                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;
+                c1  = Longs.RotateLeft(a01,  1);
+                a01 = Longs.RotateLeft(a06, 44);
+                a06 = Longs.RotateLeft(a09, 20);
+                a09 = Longs.RotateLeft(a22, 61);
+                a22 = Longs.RotateLeft(a14, 39);
+                a14 = Longs.RotateLeft(a20, 18);
+                a20 = Longs.RotateLeft(a02, 62);
+                a02 = Longs.RotateLeft(a12, 43);
+                a12 = Longs.RotateLeft(a13, 25);
+                a13 = Longs.RotateLeft(a19,  8);
+                a19 = Longs.RotateLeft(a23, 56);
+                a23 = Longs.RotateLeft(a15, 41);
+                a15 = Longs.RotateLeft(a04, 27);
+                a04 = Longs.RotateLeft(a24, 14);
+                a24 = Longs.RotateLeft(a21,  2);
+                a21 = Longs.RotateLeft(a08, 55);
+                a08 = Longs.RotateLeft(a16, 45);
+                a16 = Longs.RotateLeft(a05, 36);
+                a05 = Longs.RotateLeft(a03, 28);
+                a03 = Longs.RotateLeft(a18, 21);
+                a18 = Longs.RotateLeft(a17, 15);
+                a17 = Longs.RotateLeft(a11, 10);
+                a11 = Longs.RotateLeft(a07,  6);
+                a07 = Longs.RotateLeft(a10,  3);
                 a10 = c1;
 
                 // chi
diff --git a/crypto/src/crypto/digests/LongDigest.cs b/crypto/src/crypto/digests/LongDigest.cs
index aaa0b43ce..6a2f94ece 100644
--- a/crypto/src/crypto/digests/LongDigest.cs
+++ b/crypto/src/crypto/digests/LongDigest.cs
@@ -10,46 +10,46 @@ namespace Org.BouncyCastle.Crypto.Digests
     * Base class for SHA-384 and SHA-512.
     */
     public abstract class LongDigest
-		: IDigest, IMemoable
+        : IDigest, IMemoable
     {
-        private int     MyByteLength = 128;
+        private int MyByteLength = 128;
 
-        private byte[]  xBuf;
-        private int     xBufOff;
+        private byte[] xBuf;
+        private int xBufOff;
 
-        private long	byteCount1;
-        private long	byteCount2;
+        private long byteCount1;
+        private long byteCount2;
 
         internal ulong H1, H2, H3, H4, H5, H6, H7, H8;
 
         private ulong[] W = new ulong[80];
         private int wOff;
 
-		/**
+        /**
         * Constructor for variable length word
         */
         internal LongDigest()
         {
             xBuf = new byte[8];
 
-			Reset();
+            Reset();
         }
 
-		/**
+        /**
         * Copy constructor.  We are using copy constructors in place
         * of the object.Clone() interface as this interface is not
         * supported by J2ME.
         */
         internal LongDigest(
-			LongDigest t)
-		{
-			xBuf = new byte[t.xBuf.Length];
+            LongDigest t)
+        {
+            xBuf = new byte[t.xBuf.Length];
 
-			CopyIn(t);
-		}
+            CopyIn(t);
+        }
 
-		protected void CopyIn(LongDigest t)
-		{
+        protected void CopyIn(LongDigest t)
+        {
             Array.Copy(t.xBuf, 0, xBuf, 0, t.xBuf.Length);
 
             xBufOff = t.xBufOff;
@@ -84,9 +84,9 @@ namespace Org.BouncyCastle.Crypto.Digests
         }
 
         public void BlockUpdate(
-            byte[]  input,
-            int     inOff,
-            int     length)
+            byte[] input,
+            int inOff,
+            int length)
         {
             //
             // fill the current word
@@ -123,12 +123,54 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int inOff = 0;
+            int length = input.Length;
+
+            //
+            // fill the current word
+            //
+            while ((xBufOff != 0) && (length > 0))
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+
+            //
+            // process whole words.
+            //
+            while (length >= xBuf.Length)
+            {
+                ProcessWord(input.Slice(inOff, xBuf.Length));
+
+                inOff += xBuf.Length;
+                length -= xBuf.Length;
+                byteCount1 += xBuf.Length;
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (length > 0)
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+        }
+#endif
+
         public void Finish()
         {
             AdjustByteCounts();
 
-            long    lowBitLength = byteCount1 << 3;
-            long    hiBitLength = byteCount2;
+            long lowBitLength = byteCount1 << 3;
+            long hiBitLength = byteCount2;
 
             //
             // add the pad bytes.
@@ -151,20 +193,20 @@ namespace Org.BouncyCastle.Crypto.Digests
             byteCount2 = 0;
 
             xBufOff = 0;
-            for ( int i = 0; i < xBuf.Length; i++ )
+            for (int i = 0; i < xBuf.Length; i++)
             {
                 xBuf[i] = 0;
             }
 
             wOff = 0;
-			Array.Clear(W, 0, W.Length);
+            Array.Clear(W, 0, W.Length);
         }
 
         internal void ProcessWord(
-            byte[]  input,
-            int     inOff)
+            byte[] input,
+            int inOff)
         {
-			W[wOff] = Pack.BE_To_UInt64(input, inOff);
+            W[wOff] = Pack.BE_To_UInt64(input, inOff);
 
             if (++wOff == 16)
             {
@@ -172,6 +214,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            W[wOff] = Pack.BE_To_UInt64(word);
+
+            if (++wOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         /**
         * adjust the byte counts so that byteCount2 represents the
         * upper long (less 3 bits) word of the byte count.
@@ -180,14 +234,14 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             if (byteCount1 > 0x1fffffffffffffffL)
             {
-                byteCount2 += (long) ((ulong) byteCount1 >> 61);
+                byteCount2 += (long)((ulong)byteCount1 >> 61);
                 byteCount1 &= 0x1fffffffffffffffL;
             }
         }
 
         internal void ProcessLength(
-            long	lowW,
-            long	hiW)
+            long lowW,
+            long hiW)
         {
             if (wOff > 14)
             {
@@ -222,51 +276,51 @@ namespace Org.BouncyCastle.Crypto.Digests
             ulong g = H7;
             ulong h = H8;
 
-			int t = 0;
-			for(int i = 0; i < 10; i ++)
-			{
-				// t = 8 * i
-				h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++];
-				d += h;
-				h += Sum0(a) + Maj(a, b, c);
-
-				// t = 8 * i + 1
-				g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++];
-				c += g;
-				g += Sum0(h) + Maj(h, a, b);
-
-				// t = 8 * i + 2
-				f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++];
-				b += f;
-				f += Sum0(g) + Maj(g, h, a);
-
-				// t = 8 * i + 3
-				e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++];
-				a += e;
-				e += Sum0(f) + Maj(f, g, h);
-
-				// t = 8 * i + 4
-				d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++];
-				h += d;
-				d += Sum0(e) + Maj(e, f, g);
-
-				// t = 8 * i + 5
-				c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++];
-				g += c;
-				c += Sum0(d) + Maj(d, e, f);
-
-				// t = 8 * i + 6
-				b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++];
-				f += b;
-				b += Sum0(c) + Maj(c, d, e);
-
-				// t = 8 * i + 7
-				a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++];
-				e += a;
-				a += Sum0(b) + Maj(b, c, d);
-			}
-
-			H1 += a;
+            int t = 0;
+            for (int i = 0; i < 10; i++)
+            {
+                // t = 8 * i
+                h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++];
+                d += h;
+                h += Sum0(a) + Maj(a, b, c);
+
+                // t = 8 * i + 1
+                g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++];
+                c += g;
+                g += Sum0(h) + Maj(h, a, b);
+
+                // t = 8 * i + 2
+                f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++];
+                b += f;
+                f += Sum0(g) + Maj(g, h, a);
+
+                // t = 8 * i + 3
+                e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++];
+                a += e;
+                e += Sum0(f) + Maj(f, g, h);
+
+                // t = 8 * i + 4
+                d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++];
+                h += d;
+                d += Sum0(e) + Maj(e, f, g);
+
+                // t = 8 * i + 5
+                c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++];
+                g += c;
+                c += Sum0(d) + Maj(d, e, f);
+
+                // t = 8 * i + 6
+                b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++];
+                f += b;
+                b += Sum0(c) + Maj(c, d, e);
+
+                // t = 8 * i + 7
+                a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++];
+                e += a;
+                a += Sum0(b) + Maj(b, c, d);
+            }
+
+            H1 += a;
             H2 += b;
             H3 += c;
             H4 += d;
@@ -275,14 +329,14 @@ namespace Org.BouncyCastle.Crypto.Digests
             H7 += g;
             H8 += h;
 
-			//
+            //
             // reset the offset and clean out the word buffer.
             //
             wOff = 0;
-			Array.Clear(W, 0, 16);
-		}
+            Array.Clear(W, 0, 16);
+        }
 
-		/* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */
+        /* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */
         private static ulong Ch(ulong x, ulong y, ulong z)
         {
             return (x & y) ^ (~x & z);
@@ -295,61 +349,64 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         private static ulong Sum0(ulong x)
         {
-	        return ((x << 36) | (x >> 28)) ^ ((x << 30) | (x >> 34)) ^ ((x << 25) | (x >> 39));
+            return ((x << 36) | (x >> 28)) ^ ((x << 30) | (x >> 34)) ^ ((x << 25) | (x >> 39));
         }
 
-		private static ulong Sum1(ulong x)
+        private static ulong Sum1(ulong x)
         {
-	        return ((x << 50) | (x >> 14)) ^ ((x << 46) | (x >> 18)) ^ ((x << 23) | (x >> 41));
+            return ((x << 50) | (x >> 14)) ^ ((x << 46) | (x >> 18)) ^ ((x << 23) | (x >> 41));
         }
 
         private static ulong Sigma0(ulong x)
         {
-	        return ((x << 63) | (x >> 1)) ^ ((x << 56) | (x >> 8)) ^ (x >> 7);
+            return ((x << 63) | (x >> 1)) ^ ((x << 56) | (x >> 8)) ^ (x >> 7);
         }
 
         private static ulong Sigma1(ulong x)
         {
-	        return ((x << 45) | (x >> 19)) ^ ((x << 3) | (x >> 61)) ^ (x >> 6);
+            return ((x << 45) | (x >> 19)) ^ ((x << 3) | (x >> 61)) ^ (x >> 6);
         }
 
         /* SHA-384 and SHA-512 Constants
          * (represent the first 64 bits of the fractional parts of the
          * cube roots of the first sixty-four prime numbers)
          */
-		internal static readonly ulong[] K =
-		{
-			0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
-			0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
-			0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
-			0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
-			0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
-			0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
-			0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
-			0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
-			0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
-			0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
-			0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
-			0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
-			0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
-			0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
-			0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
-			0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
-			0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
-			0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
-			0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
-			0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
-		};
-
-		public int GetByteLength()
-		{
-			return MyByteLength;
-		}
-
-		public abstract string AlgorithmName { get; }
-		public abstract int GetDigestSize();
+        internal static readonly ulong[] K =
+        {
+            0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
+            0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
+            0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
+            0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
+            0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
+            0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
+            0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
+            0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
+            0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
+            0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
+            0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
+            0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
+            0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
+            0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
+            0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
+            0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
+            0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
+            0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
+            0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
+            0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
+        };
+
+        public int GetByteLength()
+        {
+            return MyByteLength;
+        }
+
+        public abstract string AlgorithmName { get; }
+        public abstract int GetDigestSize();
         public abstract int DoFinal(byte[] output, int outOff);
-		public abstract IMemoable Copy();
-		public abstract void Reset(IMemoable t);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract int DoFinal(Span<byte> output);
+#endif
+        public abstract IMemoable Copy();
+        public abstract void Reset(IMemoable t);
     }
 }
diff --git a/crypto/src/crypto/digests/MD2Digest.cs b/crypto/src/crypto/digests/MD2Digest.cs
index f72d08768..cea89a311 100644
--- a/crypto/src/crypto/digests/MD2Digest.cs
+++ b/crypto/src/crypto/digests/MD2Digest.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
@@ -99,6 +98,30 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            // add padding
+            byte paddingByte = (byte)(M.Length - mOff);
+            for (int i = mOff; i < M.Length; i++)
+            {
+                M[i] = paddingByte;
+            }
+            //do final check sum
+            ProcessChecksum(M);
+            // do final block process
+            ProcessBlock(M);
+
+            ProcessBlock(C);
+
+            X.AsSpan(xOff, 16).CopyTo(output);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the digest back to it's initial state.
         */
@@ -179,6 +202,40 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            //
+            // fill the current word
+            //
+            while ((mOff != 0) && (input.Length > 0))
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+
+            //
+            // process whole words.
+            //
+            while (input.Length >= 16)
+            {
+                input[..16].CopyTo(M);
+                ProcessChecksum(M);
+                ProcessBlock(M);
+                input = input[16..];
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+        }
+#endif
+
         internal void ProcessChecksum(byte[] m)
         {
             int L = C[15];
diff --git a/crypto/src/crypto/digests/MD4Digest.cs b/crypto/src/crypto/digests/MD4Digest.cs
index 8743f7dad..f7d5e23c1 100644
--- a/crypto/src/crypto/digests/MD4Digest.cs
+++ b/crypto/src/crypto/digests/MD4Digest.cs
@@ -1,5 +1,6 @@
 using System;
 
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
@@ -60,12 +61,9 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
 		}
 
-		internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+		internal override void ProcessWord(byte[] input, int inOff)
         {
-            X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8)
-                | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24);
+            X[xOff++] = (int)Pack.LE_To_UInt32(input, inOff);
 
             if (xOff == 16)
             {
@@ -73,6 +71,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long    bitLength)
         {
@@ -85,32 +95,35 @@ namespace Org.BouncyCastle.Crypto.Digests
             X[15] = (int)((ulong) bitLength >> 32);
         }
 
-        private void UnpackWord(
-            int     word,
-            byte[]  outBytes,
-            int     outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
-            outBytes[outOff]     = (byte)word;
-            outBytes[outOff + 1] = (byte)((uint) word >> 8);
-            outBytes[outOff + 2] = (byte)((uint) word >> 16);
-            outBytes[outOff + 3] = (byte)((uint) word >> 24);
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H1, output, outOff);
+            Pack.UInt32_To_LE((uint)H2, output, outOff + 4);
+            Pack.UInt32_To_LE((uint)H3, output, outOff + 8);
+            Pack.UInt32_To_LE((uint)H4, output, outOff + 12);
+
+            Reset();
+
+            return DigestLength;
         }
 
-        public override int DoFinal(
-            byte[]  output,
-            int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
         {
             Finish();
 
-            UnpackWord(H1, output, outOff);
-            UnpackWord(H2, output, outOff + 4);
-            UnpackWord(H3, output, outOff + 8);
-            UnpackWord(H4, output, outOff + 12);
+            Pack.UInt32_To_LE((uint)H1, output);
+            Pack.UInt32_To_LE((uint)H2, output[4..]);
+            Pack.UInt32_To_LE((uint)H3, output[8..]);
+            Pack.UInt32_To_LE((uint)H4, output[12..]);
 
             Reset();
 
             return DigestLength;
         }
+#endif
 
         /**
         * reset the chaining variables to the IV values.
@@ -157,16 +170,6 @@ namespace Org.BouncyCastle.Crypto.Digests
         private const int S34 = 15;
 
         /*
-        * rotate int x left n bits.
-        */
-        private int RotateLeft(
-            int x,
-            int n)
-        {
-            return (x << n) | (int) ((uint) x >> (32 - n));
-        }
-
-        /*
         * F, G, H and I are the basic MD4 functions.
         */
         private int F(
@@ -203,62 +206,62 @@ namespace Org.BouncyCastle.Crypto.Digests
             //
             // Round 1 - F cycle, 16 times.
             //
-            a = RotateLeft((a + F(b, c, d) + X[ 0]), S11);
-            d = RotateLeft((d + F(a, b, c) + X[ 1]), S12);
-            c = RotateLeft((c + F(d, a, b) + X[ 2]), S13);
-            b = RotateLeft((b + F(c, d, a) + X[ 3]), S14);
-            a = RotateLeft((a + F(b, c, d) + X[ 4]), S11);
-            d = RotateLeft((d + F(a, b, c) + X[ 5]), S12);
-            c = RotateLeft((c + F(d, a, b) + X[ 6]), S13);
-            b = RotateLeft((b + F(c, d, a) + X[ 7]), S14);
-            a = RotateLeft((a + F(b, c, d) + X[ 8]), S11);
-            d = RotateLeft((d + F(a, b, c) + X[ 9]), S12);
-            c = RotateLeft((c + F(d, a, b) + X[10]), S13);
-            b = RotateLeft((b + F(c, d, a) + X[11]), S14);
-            a = RotateLeft((a + F(b, c, d) + X[12]), S11);
-            d = RotateLeft((d + F(a, b, c) + X[13]), S12);
-            c = RotateLeft((c + F(d, a, b) + X[14]), S13);
-            b = RotateLeft((b + F(c, d, a) + X[15]), S14);
+            a = Integers.RotateLeft((a + F(b, c, d) + X[ 0]), S11);
+            d = Integers.RotateLeft((d + F(a, b, c) + X[ 1]), S12);
+            c = Integers.RotateLeft((c + F(d, a, b) + X[ 2]), S13);
+            b = Integers.RotateLeft((b + F(c, d, a) + X[ 3]), S14);
+            a = Integers.RotateLeft((a + F(b, c, d) + X[ 4]), S11);
+            d = Integers.RotateLeft((d + F(a, b, c) + X[ 5]), S12);
+            c = Integers.RotateLeft((c + F(d, a, b) + X[ 6]), S13);
+            b = Integers.RotateLeft((b + F(c, d, a) + X[ 7]), S14);
+            a = Integers.RotateLeft((a + F(b, c, d) + X[ 8]), S11);
+            d = Integers.RotateLeft((d + F(a, b, c) + X[ 9]), S12);
+            c = Integers.RotateLeft((c + F(d, a, b) + X[10]), S13);
+            b = Integers.RotateLeft((b + F(c, d, a) + X[11]), S14);
+            a = Integers.RotateLeft((a + F(b, c, d) + X[12]), S11);
+            d = Integers.RotateLeft((d + F(a, b, c) + X[13]), S12);
+            c = Integers.RotateLeft((c + F(d, a, b) + X[14]), S13);
+            b = Integers.RotateLeft((b + F(c, d, a) + X[15]), S14);
 
             //
             // Round 2 - G cycle, 16 times.
             //
-            a = RotateLeft((a + G(b, c, d) + X[ 0] + 0x5a827999), S21);
-            d = RotateLeft((d + G(a, b, c) + X[ 4] + 0x5a827999), S22);
-            c = RotateLeft((c + G(d, a, b) + X[ 8] + 0x5a827999), S23);
-            b = RotateLeft((b + G(c, d, a) + X[12] + 0x5a827999), S24);
-            a = RotateLeft((a + G(b, c, d) + X[ 1] + 0x5a827999), S21);
-            d = RotateLeft((d + G(a, b, c) + X[ 5] + 0x5a827999), S22);
-            c = RotateLeft((c + G(d, a, b) + X[ 9] + 0x5a827999), S23);
-            b = RotateLeft((b + G(c, d, a) + X[13] + 0x5a827999), S24);
-            a = RotateLeft((a + G(b, c, d) + X[ 2] + 0x5a827999), S21);
-            d = RotateLeft((d + G(a, b, c) + X[ 6] + 0x5a827999), S22);
-            c = RotateLeft((c + G(d, a, b) + X[10] + 0x5a827999), S23);
-            b = RotateLeft((b + G(c, d, a) + X[14] + 0x5a827999), S24);
-            a = RotateLeft((a + G(b, c, d) + X[ 3] + 0x5a827999), S21);
-            d = RotateLeft((d + G(a, b, c) + X[ 7] + 0x5a827999), S22);
-            c = RotateLeft((c + G(d, a, b) + X[11] + 0x5a827999), S23);
-            b = RotateLeft((b + G(c, d, a) + X[15] + 0x5a827999), S24);
+            a = Integers.RotateLeft((a + G(b, c, d) + X[ 0] + 0x5a827999), S21);
+            d = Integers.RotateLeft((d + G(a, b, c) + X[ 4] + 0x5a827999), S22);
+            c = Integers.RotateLeft((c + G(d, a, b) + X[ 8] + 0x5a827999), S23);
+            b = Integers.RotateLeft((b + G(c, d, a) + X[12] + 0x5a827999), S24);
+            a = Integers.RotateLeft((a + G(b, c, d) + X[ 1] + 0x5a827999), S21);
+            d = Integers.RotateLeft((d + G(a, b, c) + X[ 5] + 0x5a827999), S22);
+            c = Integers.RotateLeft((c + G(d, a, b) + X[ 9] + 0x5a827999), S23);
+            b = Integers.RotateLeft((b + G(c, d, a) + X[13] + 0x5a827999), S24);
+            a = Integers.RotateLeft((a + G(b, c, d) + X[ 2] + 0x5a827999), S21);
+            d = Integers.RotateLeft((d + G(a, b, c) + X[ 6] + 0x5a827999), S22);
+            c = Integers.RotateLeft((c + G(d, a, b) + X[10] + 0x5a827999), S23);
+            b = Integers.RotateLeft((b + G(c, d, a) + X[14] + 0x5a827999), S24);
+            a = Integers.RotateLeft((a + G(b, c, d) + X[ 3] + 0x5a827999), S21);
+            d = Integers.RotateLeft((d + G(a, b, c) + X[ 7] + 0x5a827999), S22);
+            c = Integers.RotateLeft((c + G(d, a, b) + X[11] + 0x5a827999), S23);
+            b = Integers.RotateLeft((b + G(c, d, a) + X[15] + 0x5a827999), S24);
 
             //
             // Round 3 - H cycle, 16 times.
             //
-            a = RotateLeft((a + H(b, c, d) + X[ 0] + 0x6ed9eba1), S31);
-            d = RotateLeft((d + H(a, b, c) + X[ 8] + 0x6ed9eba1), S32);
-            c = RotateLeft((c + H(d, a, b) + X[ 4] + 0x6ed9eba1), S33);
-            b = RotateLeft((b + H(c, d, a) + X[12] + 0x6ed9eba1), S34);
-            a = RotateLeft((a + H(b, c, d) + X[ 2] + 0x6ed9eba1), S31);
-            d = RotateLeft((d + H(a, b, c) + X[10] + 0x6ed9eba1), S32);
-            c = RotateLeft((c + H(d, a, b) + X[ 6] + 0x6ed9eba1), S33);
-            b = RotateLeft((b + H(c, d, a) + X[14] + 0x6ed9eba1), S34);
-            a = RotateLeft((a + H(b, c, d) + X[ 1] + 0x6ed9eba1), S31);
-            d = RotateLeft((d + H(a, b, c) + X[ 9] + 0x6ed9eba1), S32);
-            c = RotateLeft((c + H(d, a, b) + X[ 5] + 0x6ed9eba1), S33);
-            b = RotateLeft((b + H(c, d, a) + X[13] + 0x6ed9eba1), S34);
-            a = RotateLeft((a + H(b, c, d) + X[ 3] + 0x6ed9eba1), S31);
-            d = RotateLeft((d + H(a, b, c) + X[11] + 0x6ed9eba1), S32);
-            c = RotateLeft((c + H(d, a, b) + X[ 7] + 0x6ed9eba1), S33);
-            b = RotateLeft((b + H(c, d, a) + X[15] + 0x6ed9eba1), S34);
+            a = Integers.RotateLeft((a + H(b, c, d) + X[ 0] + 0x6ed9eba1), S31);
+            d = Integers.RotateLeft((d + H(a, b, c) + X[ 8] + 0x6ed9eba1), S32);
+            c = Integers.RotateLeft((c + H(d, a, b) + X[ 4] + 0x6ed9eba1), S33);
+            b = Integers.RotateLeft((b + H(c, d, a) + X[12] + 0x6ed9eba1), S34);
+            a = Integers.RotateLeft((a + H(b, c, d) + X[ 2] + 0x6ed9eba1), S31);
+            d = Integers.RotateLeft((d + H(a, b, c) + X[10] + 0x6ed9eba1), S32);
+            c = Integers.RotateLeft((c + H(d, a, b) + X[ 6] + 0x6ed9eba1), S33);
+            b = Integers.RotateLeft((b + H(c, d, a) + X[14] + 0x6ed9eba1), S34);
+            a = Integers.RotateLeft((a + H(b, c, d) + X[ 1] + 0x6ed9eba1), S31);
+            d = Integers.RotateLeft((d + H(a, b, c) + X[ 9] + 0x6ed9eba1), S32);
+            c = Integers.RotateLeft((c + H(d, a, b) + X[ 5] + 0x6ed9eba1), S33);
+            b = Integers.RotateLeft((b + H(c, d, a) + X[13] + 0x6ed9eba1), S34);
+            a = Integers.RotateLeft((a + H(b, c, d) + X[ 3] + 0x6ed9eba1), S31);
+            d = Integers.RotateLeft((d + H(a, b, c) + X[11] + 0x6ed9eba1), S32);
+            c = Integers.RotateLeft((c + H(d, a, b) + X[ 7] + 0x6ed9eba1), S33);
+            b = Integers.RotateLeft((b + H(c, d, a) + X[15] + 0x6ed9eba1), S34);
 
             H1 += a;
             H2 += b;
@@ -286,7 +289,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
-
 }
diff --git a/crypto/src/crypto/digests/MD5Digest.cs b/crypto/src/crypto/digests/MD5Digest.cs
index c60ac92a3..062d7bb46 100644
--- a/crypto/src/crypto/digests/MD5Digest.cs
+++ b/crypto/src/crypto/digests/MD5Digest.cs
@@ -55,9 +55,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
-        internal override void ProcessWord(
-            byte[] input,
-            int inOff)
+        internal override void ProcessWord(byte[] input, int inOff)
         {
             X[xOff] = Pack.LE_To_UInt32(input, inOff);
 
@@ -67,6 +65,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.LE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -103,6 +113,22 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE(H1, output);
+            Pack.UInt32_To_LE(H2, output[4..]);
+            Pack.UInt32_To_LE(H3, output[8..]);
+            Pack.UInt32_To_LE(H4, output[12..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables to the IV values.
         */
diff --git a/crypto/src/crypto/digests/NonMemoableDigest.cs b/crypto/src/crypto/digests/NonMemoableDigest.cs
index 02c49b887..bad38911b 100644
--- a/crypto/src/crypto/digests/NonMemoableDigest.cs
+++ b/crypto/src/crypto/digests/NonMemoableDigest.cs
@@ -44,11 +44,25 @@ namespace Org.BouncyCastle.Crypto.Digests
             mBaseDigest.BlockUpdate(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            mBaseDigest.BlockUpdate(input);
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
             return mBaseDigest.DoFinal(output, outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            return mBaseDigest.DoFinal(output);
+        }
+#endif
+
         public virtual void Reset()
         {
             mBaseDigest.Reset();
diff --git a/crypto/src/crypto/digests/NullDigest.cs b/crypto/src/crypto/digests/NullDigest.cs
index d14dd5c9f..28554cf3e 100644
--- a/crypto/src/crypto/digests/NullDigest.cs
+++ b/crypto/src/crypto/digests/NullDigest.cs
@@ -35,17 +35,48 @@ namespace Org.BouncyCastle.Crypto.Digests
 			bOut.Write(inBytes, inOff, len);
 		}
 
-        public int DoFinal(byte[] outBytes, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
 		{
+			bOut.Write(input);
+		}
+#endif
+
+		public int DoFinal(byte[] outBytes, int outOff)
+		{
+            try
+            {
+                byte[] data = bOut.GetBuffer();
+				int length = Convert.ToInt32(bOut.Length);
+
+				Array.Copy(data, 0, outBytes, outOff, length);
+
+				return length;
+			}
+			finally
+            {
+                Reset();
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
             try
             {
-                return Streams.WriteBufTo(bOut, outBytes, outOff);
+				byte[] data = bOut.GetBuffer();
+				int length = Convert.ToInt32(bOut.Length);
+
+				data.AsSpan(0, length).CopyTo(output);
+
+				return length;
             }
             finally
             {
                 Reset();
             }
         }
+#endif
 
         public void Reset()
 		{
diff --git a/crypto/src/crypto/digests/ParallelHash.cs b/crypto/src/crypto/digests/ParallelHash.cs
index f28795f5a..541d7f951 100644
--- a/crypto/src/crypto/digests/ParallelHash.cs
+++ b/crypto/src/crypto/digests/ParallelHash.cs
@@ -85,7 +85,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             buffer[bufOff++] = b;
             if (bufOff == buffer.Length)
             {
-                compress();
+                Compress();
             }
         }
 
@@ -106,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
                 if (bufOff == buffer.Length)
                 {
-                    compress();
+                    Compress();
                 }
             }
 
@@ -114,7 +114,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             {
                 while (len - i >= B)
                 {
-                    compress(inBuf, inOff + i, B);
+                    Compress(inBuf, inOff + i, B);
                     i += B;
                 }
             }
@@ -125,27 +125,75 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-        private void compress()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            compress(buffer, 0, bufOff);
+            //
+            // fill the current word
+            //
+            int i = 0;
+            if (bufOff != 0)
+            {
+                while (i < input.Length && bufOff != buffer.Length)
+                {
+                    buffer[bufOff++] = input[i++];
+                }
+
+                if (bufOff == buffer.Length)
+                {
+                    Compress();
+                }
+            }
+
+            if (i < input.Length)
+            {
+                while (input.Length - i >= B)
+                {
+                    Compress(input, i, B);
+                    i += B;
+                }
+            }
+
+            while (i < input.Length)
+            {
+                Update(input[i++]);
+            }
+        }
+#endif
+
+        private void Compress()
+        {
+            Compress(buffer, 0, bufOff);
             bufOff = 0;
         }
 
-        private void compress(byte[] buf, int offSet, int len)
+        private void Compress(byte[] buf, int offSet, int len)
         {
             compressor.BlockUpdate(buf, offSet, len);
-            compressor.DoFinal(compressorBuffer, 0, compressorBuffer.Length);
+            compressor.OutputFinal(compressorBuffer, 0, compressorBuffer.Length);
 
             cshake.BlockUpdate(compressorBuffer, 0, compressorBuffer.Length);
 
             nCount++;
         }
 
-        private void wrapUp(int outputSize)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> input, int pos, int len)
+        {
+            compressor.BlockUpdate(input.Slice(pos, len));
+            compressor.OutputFinal(compressorBuffer, 0, compressorBuffer.Length);
+
+            cshake.BlockUpdate(compressorBuffer, 0, compressorBuffer.Length);
+
+            nCount++;
+        }
+#endif
+
+        private void WrapUp(int outputSize)
         {
             if (bufOff != 0)
             {
-                compress();
+                Compress();
             }
             byte[] nOut = XofUtilities.RightEncode(nCount);
             byte[] encOut = XofUtilities.RightEncode(outputSize * 8);
@@ -160,39 +208,83 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             if (firstOutput)
             {
-                wrapUp(outputLength);
+                WrapUp(outputLength);
+            }
+
+            int rv = cshake.DoFinal(outBuf, outOff);
+
+            Reset();
+
+            return rv;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                WrapUp(outputLength);
+            }
+
+            int rv = cshake.DoFinal(output);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
+        public virtual int OutputFinal(byte[] outBuf, int outOff, int outLen)
+        {
+            if (firstOutput)
+            {
+                WrapUp(outputLength);
             }
 
-            int rv = cshake.DoFinal(outBuf, outOff, GetDigestSize());
+            int rv = cshake.OutputFinal(outBuf, outOff, outLen);
 
             Reset();
 
             return rv;
         }
 
-        public virtual int DoFinal(byte[] outBuf, int outOff, int outLen)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int OutputFinal(Span<byte> output)
         {
             if (firstOutput)
             {
-                wrapUp(outputLength);
+                WrapUp(outputLength);
             }
 
-            int rv = cshake.DoFinal(outBuf, outOff, outLen);
+            int rv = cshake.OutputFinal(output);
 
             Reset();
 
             return rv;
         }
+#endif
+
+        public virtual int Output(byte[] outBuf, int outOff, int outLen)
+        {
+            if (firstOutput)
+            {
+                WrapUp(0);
+            }
+
+            return cshake.Output(outBuf, outOff, outLen);
+        }
 
-        public virtual int DoOutput(byte[] outBuf, int outOff, int outLen)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int Output(Span<byte> output)
         {
             if (firstOutput)
             {
-                wrapUp(0);
+                WrapUp(0);
             }
 
-            return cshake.DoOutput(outBuf, outOff, outLen);
+            return cshake.Output(output);
         }
+#endif
 
         public virtual void Reset()
         {
diff --git a/crypto/src/crypto/digests/RipeMD128Digest.cs b/crypto/src/crypto/digests/RipeMD128Digest.cs
index cba2c65d3..b66452682 100644
--- a/crypto/src/crypto/digests/RipeMD128Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD128Digest.cs
@@ -68,6 +68,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -94,6 +106,22 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables to the IV values.
         */
diff --git a/crypto/src/crypto/digests/RipeMD160Digest.cs b/crypto/src/crypto/digests/RipeMD160Digest.cs
index 0fc2a4a1c..a95bff48a 100644
--- a/crypto/src/crypto/digests/RipeMD160Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD160Digest.cs
@@ -70,6 +70,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -97,6 +109,23 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+            Pack.UInt32_To_LE((uint)H4, output[16..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables to the IV values.
         */
diff --git a/crypto/src/crypto/digests/RipeMD256Digest.cs b/crypto/src/crypto/digests/RipeMD256Digest.cs
index 621162a6f..40508e9f7 100644
--- a/crypto/src/crypto/digests/RipeMD256Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD256Digest.cs
@@ -70,6 +70,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -100,6 +112,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+            Pack.UInt32_To_LE((uint)H4, output[16..]);
+            Pack.UInt32_To_LE((uint)H5, output[20..]);
+            Pack.UInt32_To_LE((uint)H6, output[24..]);
+            Pack.UInt32_To_LE((uint)H7, output[28..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /// <summary> reset the chaining variables to the IV values.</summary>
         public override void  Reset()
         {
diff --git a/crypto/src/crypto/digests/RipeMD320Digest.cs b/crypto/src/crypto/digests/RipeMD320Digest.cs
index c46bc4fea..ddaf858ff 100644
--- a/crypto/src/crypto/digests/RipeMD320Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD320Digest.cs
@@ -73,6 +73,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -105,6 +117,28 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+            Pack.UInt32_To_LE((uint)H4, output[16..]);
+            Pack.UInt32_To_LE((uint)H5, output[20..]);
+            Pack.UInt32_To_LE((uint)H6, output[24..]);
+            Pack.UInt32_To_LE((uint)H7, output[28..]);
+            Pack.UInt32_To_LE((uint)H8, output[32..]);
+            Pack.UInt32_To_LE((uint)H9, output[36..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /// <summary> reset the chaining variables to the IV values.</summary>
         public override void  Reset()
         {
diff --git a/crypto/src/crypto/digests/SHA3Digest.cs b/crypto/src/crypto/digests/SHA3Digest.cs
index 3dc63a6ed..778c453d8 100644
--- a/crypto/src/crypto/digests/SHA3Digest.cs
+++ b/crypto/src/crypto/digests/SHA3Digest.cs
@@ -55,6 +55,15 @@ namespace Org.BouncyCastle.Crypto.Digests
             return base.DoFinal(output,  outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            AbsorbBits(0x02, 2);
+
+            return base.DoFinal(output);
+        }
+#endif
+
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
diff --git a/crypto/src/crypto/digests/SM3Digest.cs b/crypto/src/crypto/digests/SM3Digest.cs
index 449d7c161..81d4a68a9 100644
--- a/crypto/src/crypto/digests/SM3Digest.cs
+++ b/crypto/src/crypto/digests/SM3Digest.cs
@@ -118,7 +118,6 @@ namespace Org.BouncyCastle.Crypto.Digests
 			this.xOff = 0;
 		}
 
-
 		public override int DoFinal(byte[] output, int outOff)
 		{
 			Finish();
@@ -130,13 +129,22 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DIGEST_LENGTH;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(V, output);
+
+            Reset();
 
-		internal override void ProcessWord(byte[] input,
-		                                   int inOff)
+            return DIGEST_LENGTH;
+        }
+#endif
+
+        internal override void ProcessWord(byte[] input, int inOff)
 		{
-			uint n = Pack.BE_To_UInt32(input, inOff);
-			this.inwords[this.xOff] = n;
-			++this.xOff;
+			inwords[xOff++] = Pack.BE_To_UInt32(input, inOff);
 
 			if (this.xOff >= 16)
 			{
@@ -144,7 +152,19 @@ namespace Org.BouncyCastle.Crypto.Digests
 			}
 		}
 
-		internal override void ProcessLength(long bitLength)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            inwords[xOff++] = Pack.BE_To_UInt32(word);
+
+            if (this.xOff >= 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
+        internal override void ProcessLength(long bitLength)
 		{
 			if (this.xOff > (BLOCK_SIZE - 2))
 			{
diff --git a/crypto/src/crypto/digests/Sha1Digest.cs b/crypto/src/crypto/digests/Sha1Digest.cs
index 60ec651d5..9b384b8cb 100644
--- a/crypto/src/crypto/digests/Sha1Digest.cs
+++ b/crypto/src/crypto/digests/Sha1Digest.cs
@@ -61,9 +61,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
-        internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+        internal override void ProcessWord(byte[] input, int inOff)
         {
             X[xOff] = Pack.BE_To_UInt32(input, inOff);
 
@@ -73,6 +71,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.BE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(long    bitLength)
         {
             if (xOff > 14)
@@ -101,6 +111,23 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(H1, output);
+            Pack.UInt32_To_BE(H2, output[4..]);
+            Pack.UInt32_To_BE(H3, output[8..]);
+            Pack.UInt32_To_BE(H4, output[12..]);
+            Pack.UInt32_To_BE(H5, output[16..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
          * reset the chaining variables
          */
@@ -279,6 +306,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha224Digest.cs b/crypto/src/crypto/digests/Sha224Digest.cs
index b4e853745..28d09adec 100644
--- a/crypto/src/crypto/digests/Sha224Digest.cs
+++ b/crypto/src/crypto/digests/Sha224Digest.cs
@@ -72,9 +72,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
 		}
 
-		internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+		internal override void ProcessWord(byte[] input, int inOff)
         {
 			X[xOff] = Pack.BE_To_UInt32(input, inOff);
 
@@ -84,7 +82,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-		internal override void ProcessLength(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.BE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
+        internal override void ProcessLength(
             long bitLength)
         {
             if (xOff > 14)
@@ -96,9 +106,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             X[15] = (uint)((ulong)bitLength);
         }
 
-        public override int DoFinal(
-            byte[]	output,
-            int		outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
             Finish();
 
@@ -115,7 +123,26 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(H1, output);
+            Pack.UInt32_To_BE(H2, output[4..]);
+            Pack.UInt32_To_BE(H3, output[8..]);
+            Pack.UInt32_To_BE(H4, output[12..]);
+            Pack.UInt32_To_BE(H5, output[16..]);
+            Pack.UInt32_To_BE(H6, output[20..]);
+            Pack.UInt32_To_BE(H7, output[24..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
+        /**
          * reset the chaining variables
          */
         public override void Reset()
@@ -284,6 +311,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha256Digest.cs b/crypto/src/crypto/digests/Sha256Digest.cs
index 63d5b8bee..51859697e 100644
--- a/crypto/src/crypto/digests/Sha256Digest.cs
+++ b/crypto/src/crypto/digests/Sha256Digest.cs
@@ -67,9 +67,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
 		}
 
-		internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+		internal override void ProcessWord(byte[] input, int inOff)
 		{
 			X[xOff] = Pack.BE_To_UInt32(input, inOff);
 
@@ -79,7 +77,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-		internal override void ProcessLength(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.BE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
+        internal override void ProcessLength(
             long bitLength)
         {
             if (xOff > 14)
@@ -91,26 +101,44 @@ namespace Org.BouncyCastle.Crypto.Digests
             X[15] = (uint)((ulong)bitLength);
         }
 
-        public override int DoFinal(
-            byte[]  output,
-            int     outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
             Finish();
 
-            Pack.UInt32_To_BE((uint)H1, output, outOff);
-            Pack.UInt32_To_BE((uint)H2, output, outOff + 4);
-            Pack.UInt32_To_BE((uint)H3, output, outOff + 8);
-            Pack.UInt32_To_BE((uint)H4, output, outOff + 12);
-            Pack.UInt32_To_BE((uint)H5, output, outOff + 16);
-            Pack.UInt32_To_BE((uint)H6, output, outOff + 20);
-            Pack.UInt32_To_BE((uint)H7, output, outOff + 24);
-            Pack.UInt32_To_BE((uint)H8, output, outOff + 28);
+            Pack.UInt32_To_BE(H1, output, outOff);
+            Pack.UInt32_To_BE(H2, output, outOff + 4);
+            Pack.UInt32_To_BE(H3, output, outOff + 8);
+            Pack.UInt32_To_BE(H4, output, outOff + 12);
+            Pack.UInt32_To_BE(H5, output, outOff + 16);
+            Pack.UInt32_To_BE(H6, output, outOff + 20);
+            Pack.UInt32_To_BE(H7, output, outOff + 24);
+            Pack.UInt32_To_BE(H8, output, outOff + 28);
 
             Reset();
 
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(H1, output);
+            Pack.UInt32_To_BE(H2, output[4..]);
+            Pack.UInt32_To_BE(H3, output[8..]);
+            Pack.UInt32_To_BE(H4, output[12..]);
+            Pack.UInt32_To_BE(H5, output[16..]);
+            Pack.UInt32_To_BE(H6, output[20..]);
+            Pack.UInt32_To_BE(H7, output[24..]);
+            Pack.UInt32_To_BE(H8, output[28..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
@@ -313,6 +341,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha384Digest.cs b/crypto/src/crypto/digests/Sha384Digest.cs
index e6c9a9aa9..e4e65ed85 100644
--- a/crypto/src/crypto/digests/Sha384Digest.cs
+++ b/crypto/src/crypto/digests/Sha384Digest.cs
@@ -64,6 +64,24 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt64_To_BE(H1, output);
+            Pack.UInt64_To_BE(H2, output[8..]);
+            Pack.UInt64_To_BE(H3, output[16..]);
+            Pack.UInt64_To_BE(H4, output[24..]);
+            Pack.UInt64_To_BE(H5, output[32..]);
+            Pack.UInt64_To_BE(H6, output[40..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
@@ -72,9 +90,9 @@ namespace Org.BouncyCastle.Crypto.Digests
             base.Reset();
 
             /* SHA-384 initial hash value
-                * The first 64 bits of the fractional parts of the square roots
-                * of the 9th through 16th prime numbers
-                */
+             * The first 64 bits of the fractional parts of the square roots
+             * of the 9th through 16th prime numbers
+              */
             H1 = 0xcbbb9d5dc1059ed8;
             H2 = 0x629a292a367cd507;
             H3 = 0x9159015a3070dd17;
@@ -96,6 +114,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha512Digest.cs b/crypto/src/crypto/digests/Sha512Digest.cs
index 2a0964fd3..9156c24bf 100644
--- a/crypto/src/crypto/digests/Sha512Digest.cs
+++ b/crypto/src/crypto/digests/Sha512Digest.cs
@@ -67,6 +67,26 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt64_To_BE(H1, output);
+            Pack.UInt64_To_BE(H2, output[8..]);
+            Pack.UInt64_To_BE(H3, output[16..]);
+            Pack.UInt64_To_BE(H4, output[24..]);
+            Pack.UInt64_To_BE(H5, output[32..]);
+            Pack.UInt64_To_BE(H6, output[40..]);
+            Pack.UInt64_To_BE(H7, output[48..]);
+            Pack.UInt64_To_BE(H8, output[56..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
@@ -99,6 +119,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha512tDigest.cs b/crypto/src/crypto/digests/Sha512tDigest.cs
index 2caefa763..939dbda4f 100644
--- a/crypto/src/crypto/digests/Sha512tDigest.cs
+++ b/crypto/src/crypto/digests/Sha512tDigest.cs
@@ -76,6 +76,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return digestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            UInt64_To_BE(H1, output, 0, digestLength);
+            UInt64_To_BE(H2, output, 8, digestLength - 8);
+            UInt64_To_BE(H3, output, 16, digestLength - 16);
+            UInt64_To_BE(H4, output, 24, digestLength - 24);
+            UInt64_To_BE(H5, output, 32, digestLength - 32);
+            UInt64_To_BE(H6, output, 40, digestLength - 40);
+            UInt64_To_BE(H7, output, 48, digestLength - 48);
+            UInt64_To_BE(H8, output, 56, digestLength - 56);
+
+            Reset();
+
+            return digestLength;
+        }
+#endif
+
         /**
          * reset the chaining variables
          */
@@ -170,7 +190,32 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-		public override IMemoable Copy()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void UInt64_To_BE(ulong n, Span<byte> bs, int off, int max)
+        {
+            if (max > 0)
+            {
+                UInt32_To_BE((uint)(n >> 32), bs, off, max);
+
+                if (max > 4)
+                {
+                    UInt32_To_BE((uint)n, bs, off + 4, max - 4);
+                }
+            }
+        }
+
+        private static void UInt32_To_BE(uint n, Span<byte> bs, int off, int max)
+        {
+            int num = System.Math.Min(4, max);
+            while (--num >= 0)
+            {
+                int shift = 8 * (3 - num);
+                bs[off + num] = (byte)(n >> shift);
+            }
+        }
+#endif
+
+        public override IMemoable Copy()
 		{
 			return new Sha512tDigest(this);
 		}
diff --git a/crypto/src/crypto/digests/ShakeDigest.cs b/crypto/src/crypto/digests/ShakeDigest.cs
index 8d7a7d6e3..f99d44de9 100644
--- a/crypto/src/crypto/digests/ShakeDigest.cs
+++ b/crypto/src/crypto/digests/ShakeDigest.cs
@@ -53,19 +53,19 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         public override int DoFinal(byte[] output, int outOff)
         {
-            return DoFinal(output, outOff, GetDigestSize());
+            return OutputFinal(output, outOff, GetDigestSize());
         }
 
-        public virtual int DoFinal(byte[] output, int outOff, int outLen)
+        public virtual int OutputFinal(byte[] output, int outOff, int outLen)
         {
-            int length = DoOutput(output, outOff, outLen);
+            int length = Output(output, outOff, outLen);
 
             Reset();
 
             return length;
         }
 
-        public virtual int DoOutput(byte[] output, int outOff, int outLen)
+        public virtual int Output(byte[] output, int outOff, int outLen)
         {
             if (!squeezing)
             {
@@ -77,18 +77,46 @@ namespace Org.BouncyCastle.Crypto.Digests
             return outLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..GetDigestSize()]);
+        }
+
+        public virtual int OutputFinal(Span<byte> output)
+        {
+            int length = Output(output);
+
+            Reset();
+
+            return length;
+        }
+
+        public virtual int Output(Span<byte> output)
+        {
+            if (!squeezing)
+            {
+                AbsorbBits(0x0F, 4);
+            }
+
+            Squeeze(output);
+
+            return output.Length;
+        }
+#endif
+
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
         protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits)
         {
-            return DoFinal(output, outOff, GetDigestSize(), partialByte, partialBits);
+            return OutputFinal(output, outOff, GetDigestSize(), partialByte, partialBits);
         }
 
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
-        protected virtual int DoFinal(byte[] output, int outOff, int outLen, byte partialByte, int partialBits)
+        protected virtual int OutputFinal(byte[] output, int outOff, int outLen, byte partialByte, int partialBits)
         {
             if (partialBits < 0 || partialBits > 7)
                 throw new ArgumentException("must be in the range [0,7]", "partialBits");
diff --git a/crypto/src/crypto/digests/ShortenedDigest.cs b/crypto/src/crypto/digests/ShortenedDigest.cs
index 9e4d99e7b..0ea3cc1a1 100644
--- a/crypto/src/crypto/digests/ShortenedDigest.cs
+++ b/crypto/src/crypto/digests/ShortenedDigest.cs
@@ -1,5 +1,4 @@
 using System;
-using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
@@ -58,7 +57,14 @@ namespace Org.BouncyCastle.Crypto.Digests
 			baseDigest.BlockUpdate(input, inOff, length);
 		}
 
-		public int DoFinal(byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            baseDigest.BlockUpdate(input);
+        }
+#endif
+
+        public int DoFinal(byte[] output, int outOff)
 		{
 			byte[] tmp = new byte[baseDigest.GetDigestSize()];
 
@@ -69,7 +75,23 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return length;
 		}
 
-		public void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+			int baseDigestSize = baseDigest.GetDigestSize();
+            Span<byte> tmp = baseDigestSize <= 128
+				? stackalloc byte[baseDigestSize]
+				: new byte[baseDigestSize];
+
+            baseDigest.DoFinal(tmp);
+
+            tmp[..length].CopyTo(output);
+
+            return length;
+        }
+#endif
+
+        public void Reset()
 		{
 			baseDigest.Reset();
 		}
diff --git a/crypto/src/crypto/digests/SkeinDigest.cs b/crypto/src/crypto/digests/SkeinDigest.cs
index 394f0acd5..d56c0e788 100644
--- a/crypto/src/crypto/digests/SkeinDigest.cs
+++ b/crypto/src/crypto/digests/SkeinDigest.cs
@@ -5,7 +5,6 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
-
 	/// <summary>
 	/// Implementation of the Skein parameterised hash function in 256, 512 and 1024 bit block sizes,
 	/// based on the <see cref="Org.BouncyCastle.Crypto.Engines.ThreefishEngine">Threefish</see> tweakable block cipher.
@@ -103,7 +102,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 		public void BlockUpdate(byte[] inBytes, int inOff, int len)
 		{
-			engine.Update(inBytes, inOff, len);
+			engine.BlockUpdate(inBytes, inOff, len);
 		}
 
 		public int DoFinal(byte[] outBytes, int outOff)
@@ -111,5 +110,16 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return engine.DoFinal(outBytes, outOff);
 		}
 
-	}
-}
\ No newline at end of file
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            engine.BlockUpdate(input);
+        }
+
+        public int DoFinal(Span<byte> output)
+        {
+            return engine.DoFinal(output);
+        }
+#endif
+    }
+}
diff --git a/crypto/src/crypto/digests/SkeinEngine.cs b/crypto/src/crypto/digests/SkeinEngine.cs
index a36ac8fe7..2535f786a 100644
--- a/crypto/src/crypto/digests/SkeinEngine.cs
+++ b/crypto/src/crypto/digests/SkeinEngine.cs
@@ -431,7 +431,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                         currentOffset = 0;
                     }
 
-                    int toCopy = System.Math.Min((len - copied), currentBlock.Length - currentOffset);
+                    int toCopy = System.Math.Min(len - copied, currentBlock.Length - currentOffset);
                     Array.Copy(value, offset + copied, currentBlock, currentOffset, toCopy);
                     copied += toCopy;
                     currentOffset += toCopy;
@@ -439,6 +439,32 @@ namespace Org.BouncyCastle.Crypto.Digests
                 }
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public void Update(ReadOnlySpan<byte> input, ulong[] output)
+            {
+                /*
+                 * Buffer complete blocks for the underlying Threefish cipher, only flushing when there
+                 * are subsequent bytes (last block must be processed in doFinal() with final=true set).
+                 */
+                int copied = 0, len = input.Length;
+                while (len > copied)
+                {
+                    if (currentOffset == currentBlock.Length)
+                    {
+                        ProcessBlock(output);
+                        tweak.First = false;
+                        currentOffset = 0;
+                    }
+
+                    int toCopy = System.Math.Min(len - copied, currentBlock.Length - currentOffset);
+                    input.Slice(copied, toCopy).CopyTo(currentBlock.AsSpan(currentOffset));
+                    copied += toCopy;
+                    currentOffset += toCopy;
+                    tweak.AdvancePosition(toCopy);
+                }
+            }
+#endif
+
             private void ProcessBlock(ulong[] output)
             {
                 engine.threefish.Init(true, engine.chain, tweak.GetWords());
@@ -726,15 +752,23 @@ namespace Org.BouncyCastle.Crypto.Digests
         public void Update(byte inByte)
         {
             singleByte[0] = inByte;
-            Update(singleByte, 0, 1);
+            BlockUpdate(singleByte, 0, 1);
         }
 
-        public void Update(byte[] inBytes, int inOff, int len)
+        public void BlockUpdate(byte[] inBytes, int inOff, int len)
         {
             CheckInitialised();
             ubi.Update(inBytes, inOff, len, chain);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            CheckInitialised();
+            ubi.Update(input, chain);
+        }
+#endif
+
         public int DoFinal(byte[] outBytes, int outOff)
         {
             CheckInitialised();
@@ -770,6 +804,42 @@ namespace Org.BouncyCastle.Crypto.Digests
             return outputSizeBytes;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            CheckInitialised();
+            if (output.Length < outputSizeBytes)
+                throw new DataLengthException("Output span is too short to hold output");
+
+            // Finalise message block
+            UbiFinal();
+
+            // Process additional post-message parameters
+            if (postMessageParameters != null)
+            {
+                for (int i = 0; i < postMessageParameters.Length; i++)
+                {
+                    Parameter param = postMessageParameters[i];
+                    UbiComplete(param.Type, param.Value);
+                }
+            }
+
+            // Perform the output transform
+            int blockSize = BlockSize;
+            int blocksRequired = (outputSizeBytes + blockSize - 1) / blockSize;
+            for (int i = 0; i < blocksRequired; i++)
+            {
+                int toWrite = System.Math.Min(blockSize, outputSizeBytes - (i * blockSize));
+                //Output((ulong)i, outBytes, outOff + (i * blockSize), toWrite);
+                Output((ulong)i, output[(i * blockSize)..], toWrite);
+            }
+
+            Reset();
+
+            return outputSizeBytes;
+        }
+#endif
+
         private void Output(ulong outputSequence, byte[] outBytes, int outOff, int outputBytes)
         {
             byte[] currentBytes = new byte[8];
@@ -796,5 +866,34 @@ namespace Org.BouncyCastle.Crypto.Digests
                 }
             }
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Output(ulong outputSequence, Span<byte> output, int outputBytes)
+        {
+            Span<byte> currentBytes = stackalloc byte[8];
+            Pack.UInt64_To_LE(outputSequence, currentBytes);
+
+            // Output is a sequence of UBI invocations all of which use and preserve the pre-output state
+            ulong[] outputWords = new ulong[chain.Length];
+            UbiInit(PARAM_TYPE_OUTPUT);
+            this.ubi.Update(currentBytes, outputWords);
+            ubi.DoFinal(outputWords);
+
+            int wordsRequired = (outputBytes + 8 - 1) / 8;
+            for (int i = 0; i < wordsRequired; i++)
+            {
+                int toWrite = System.Math.Min(8, outputBytes - (i * 8));
+                if (toWrite == 8)
+                {
+                    Pack.UInt64_To_LE(outputWords[i], output[(i * 8)..]);
+                }
+                else
+                {
+                    Pack.UInt64_To_LE(outputWords[i], currentBytes);
+                    currentBytes[..toWrite].CopyTo(output[(i * 8)..]);
+                }
+            }
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/TigerDigest.cs b/crypto/src/crypto/digests/TigerDigest.cs
index a452d3f0b..d83e905db 100644
--- a/crypto/src/crypto/digests/TigerDigest.cs
+++ b/crypto/src/crypto/digests/TigerDigest.cs
@@ -603,6 +603,20 @@ namespace Org.BouncyCastle.Crypto.Digests
             bOff = 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ProcessWord(ReadOnlySpan<byte> b)
+        {
+            x[xOff++] = (long)Pack.LE_To_UInt64(b);
+
+            if (xOff == x.Length)
+            {
+                ProcessBlock();
+            }
+
+            bOff = 0;
+        }
+#endif
+
         public void Update(
             byte input)
         {
@@ -656,6 +670,47 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int inOff = 0, length = input.Length;
+
+            //
+            // fill the current word
+            //
+            while ((bOff != 0) && (length > 0))
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+
+            //
+            // process whole words.
+            //
+            while (length >= 8)
+            {
+                ProcessWord(input[inOff..]);
+
+                inOff += 8;
+                length -= 8;
+                byteCount += 8;
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (length > 0)
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+        }
+#endif
+
         private void RoundABC(
             long    x,
             long    mul)
@@ -809,6 +864,21 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt64_To_LE((ulong)a, output);
+            Pack.UInt64_To_LE((ulong)b, output[8..]);
+            Pack.UInt64_To_LE((ulong)c, output[16..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
diff --git a/crypto/src/crypto/digests/TupleHash.cs b/crypto/src/crypto/digests/TupleHash.cs
index 98c2d2acf..a71ffb2c4 100644
--- a/crypto/src/crypto/digests/TupleHash.cs
+++ b/crypto/src/crypto/digests/TupleHash.cs
@@ -78,7 +78,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             cshake.BlockUpdate(bytes, 0, bytes.Length);
         }
 
-        private void wrapUp(int outputSize)
+        private void WrapUp(int outputSize)
         {
             byte[] encOut = XofUtilities.RightEncode(outputSize * 8);
 
@@ -89,46 +89,73 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         public virtual int DoFinal(byte[] outBuf, int outOff)
         {
+            return OutputFinal(outBuf, outOff, GetDigestSize());
+        }
+
+        public virtual int OutputFinal(byte[] outBuf, int outOff, int outLen)
+        {
             if (firstOutput)
             {
-                wrapUp(GetDigestSize());
+                WrapUp(GetDigestSize());
             }
 
-            int rv = cshake.DoFinal(outBuf, outOff, GetDigestSize());
+            int rv = cshake.OutputFinal(outBuf, outOff, outLen);
 
             Reset();
 
             return rv;
         }
 
-        public virtual int DoFinal(byte[] outBuf, int outOff, int outLen)
+        public virtual int Output(byte[] outBuf, int outOff, int outLen)
         {
             if (firstOutput)
             {
-                wrapUp(GetDigestSize());
+                WrapUp(0);
             }
 
-            int rv = cshake.DoFinal(outBuf, outOff, outLen);
+            return cshake.Output(outBuf, outOff, outLen);
+        }
 
-            Reset();
+        public virtual void Reset()
+        {
+            cshake.Reset();
+            firstOutput = true;
+        }
 
-            return rv;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            XofUtilities.EncodeTo(cshake, input);
+        }
+
+        public virtual int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..GetDigestSize()]);
         }
 
-        public virtual int DoOutput(byte[] outBuf, int outOff, int outLen)
+        public virtual int OutputFinal(Span<byte> output)
         {
             if (firstOutput)
             {
-                wrapUp(0);
+                WrapUp(GetDigestSize());
             }
 
-            return cshake.DoOutput(outBuf, outOff, outLen);
+            int rv = cshake.OutputFinal(output);
+
+            Reset();
+
+            return rv;
         }
 
-        public virtual void Reset()
+        public virtual int Output(Span<byte> output)
         {
-            cshake.Reset();
-            firstOutput = true;
+            if (firstOutput)
+            {
+                WrapUp(0);
+            }
+
+            return cshake.Output(output);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/WhirlpoolDigest.cs b/crypto/src/crypto/digests/WhirlpoolDigest.cs
index b28e259f3..73d389a3c 100644
--- a/crypto/src/crypto/digests/WhirlpoolDigest.cs
+++ b/crypto/src/crypto/digests/WhirlpoolDigest.cs
@@ -162,6 +162,20 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return GetDigestSize();
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			// sets output[0..DIGEST_LENGTH_BYTES]
+			Finish();
+
+			Pack.UInt64_To_BE(_hash, output);
+
+			Reset();
+
+			return GetDigestSize();
+		}
+#endif
+
 		/**
 		* Reset the chaining variables
 		*/
@@ -276,6 +290,16 @@ namespace Org.BouncyCastle.Crypto.Digests
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
+		{
+			for (int i = 0; i < input.Length; ++i)
+			{
+				Update(input[i]);
+			}
+		}
+#endif
+
 		private void Finish()
 		{
 			/*
diff --git a/crypto/src/crypto/digests/XofUtils.cs b/crypto/src/crypto/digests/XofUtils.cs
index a4d6622b3..a1242f987 100644
--- a/crypto/src/crypto/digests/XofUtils.cs
+++ b/crypto/src/crypto/digests/XofUtils.cs
@@ -28,6 +28,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return b;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static int LeftEncode(long length, Span<byte> lengthEncoding)
+        {
+            byte n = 1;
+
+            long v = length;
+            while ((v >>= 8) != 0)
+            {
+                n++;
+            }
+
+            lengthEncoding[0] = n;
+            for (int i = 1; i <= n; i++)
+            {
+                lengthEncoding[i] = (byte)(length >> (8 * (n - i)));
+            }
+            return 1 + n;
+        }
+#endif
+
         internal static byte[] RightEncode(long strLen)
         {
             byte n = 1;
@@ -50,6 +70,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return b;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static int RightEncode(long length, Span<byte> lengthEncoding)
+        {
+            byte n = 1;
+
+            long v = length;
+            while ((v >>= 8) != 0)
+            {
+                n++;
+            }
+
+            lengthEncoding[n] = n;
+            for (int i = 0; i < n; i++)
+            {
+                lengthEncoding[i] = (byte)(length >> (8 * (n - i - 1)));
+            }
+            return n + 1;
+        }
+#endif
+
         internal static byte[] Encode(byte X)
         {
             return Arrays.Concatenate(LeftEncode(8), new byte[] { X });
@@ -63,5 +103,15 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
             return Arrays.Concatenate(LeftEncode(len * 8), Arrays.CopyOfRange(inBuf, inOff, inOff + len));
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void EncodeTo(IDigest digest, ReadOnlySpan<byte> buf)
+        {
+            Span<byte> lengthEncoding = stackalloc byte[9];
+            int count = LeftEncode(buf.Length * 8, lengthEncoding);
+            digest.BlockUpdate(lengthEncoding[..count]);
+            digest.BlockUpdate(buf);
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/encodings/ISO9796d1Encoding.cs b/crypto/src/crypto/encodings/ISO9796d1Encoding.cs
index 30e988356..c08de1f9a 100644
--- a/crypto/src/crypto/encodings/ISO9796d1Encoding.cs
+++ b/crypto/src/crypto/encodings/ISO9796d1Encoding.cs
@@ -29,31 +29,21 @@ namespace Org.BouncyCastle.Crypto.Encodings
 		private int padBits = 0;
 		private BigInteger modulus;
 
-		public ISO9796d1Encoding(
-			IAsymmetricBlockCipher   cipher)
+		public ISO9796d1Encoding(IAsymmetricBlockCipher cipher)
 		{
 			this.engine = cipher;
 		}
 
-		public string AlgorithmName
-		{
-			get { return engine.AlgorithmName + "/ISO9796-1Padding"; }
-		}
+		public string AlgorithmName => engine.AlgorithmName + "/ISO9796-1Padding";
 
-		public IAsymmetricBlockCipher GetUnderlyingCipher()
-		{
-			return engine;
-		}
+		public IAsymmetricBlockCipher UnderlyingCipher => engine;
 
-		public void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
-		{
-			RsaKeyParameters kParam;
-			if (parameters is ParametersWithRandom)
+        public void Init(bool forEncryption, ICipherParameters parameters)
+        {
+            RsaKeyParameters kParam;
+			if (parameters is ParametersWithRandom withRandom)
 			{
-				ParametersWithRandom rParam = (ParametersWithRandom)parameters;
-				kParam = (RsaKeyParameters)rParam.Parameters;
+				kParam = (RsaKeyParameters)withRandom.Parameters;
 			}
 			else
 			{
diff --git a/crypto/src/crypto/encodings/OaepEncoding.cs b/crypto/src/crypto/encodings/OaepEncoding.cs
index f3550b9cd..6871a039a 100644
--- a/crypto/src/crypto/encodings/OaepEncoding.cs
+++ b/crypto/src/crypto/encodings/OaepEncoding.cs
@@ -62,31 +62,22 @@ namespace Org.BouncyCastle.Crypto.Encodings
             hash.DoFinal(defHash, 0);
         }
 
-        public IAsymmetricBlockCipher GetUnderlyingCipher()
-        {
-            return engine;
-        }
+        public string AlgorithmName => engine.AlgorithmName + "/OAEPPadding";
 
-        public string AlgorithmName
-        {
-            get { return engine.AlgorithmName + "/OAEPPadding"; }
-        }
+        public IAsymmetricBlockCipher UnderlyingCipher => engine;
 
-        public void Init(
-            bool				forEncryption,
-            ICipherParameters	param)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
-            if (param is ParametersWithRandom)
+            if (parameters is ParametersWithRandom withRandom)
             {
-                ParametersWithRandom rParam = (ParametersWithRandom)param;
-                this.random = rParam.Random;
+                this.random = withRandom.Random;
             }
             else
             {
-                this.random = new SecureRandom();
+                this.random = CryptoServicesRegistrar.GetSecureRandom();
             }
 
-            engine.Init(forEncryption, param);
+            engine.Init(forEncryption, parameters);
 
             this.forEncryption = forEncryption;
         }
@@ -304,7 +295,7 @@ namespace Org.BouncyCastle.Crypto.Encodings
             {
                 byte[] mask = new byte[length];
                 mgf1Hash.BlockUpdate(Z, zOff, zLen);
-                ((IXof)mgf1Hash).DoFinal(mask, 0, mask.Length);
+                ((IXof)mgf1Hash).OutputFinal(mask, 0, mask.Length);
 
                 return mask;
             }
diff --git a/crypto/src/crypto/encodings/Pkcs1Encoding.cs b/crypto/src/crypto/encodings/Pkcs1Encoding.cs
index 53c046a8a..06e59d4f3 100644
--- a/crypto/src/crypto/encodings/Pkcs1Encoding.cs
+++ b/crypto/src/crypto/encodings/Pkcs1Encoding.cs
@@ -96,29 +96,21 @@ namespace Org.BouncyCastle.Crypto.Encodings
             this.pLen = fallback.Length;
         }
 
-        public IAsymmetricBlockCipher GetUnderlyingCipher()
-        {
-            return engine;
-        }
+        public string AlgorithmName => engine.AlgorithmName + "/PKCS1Padding";
 
-        public string AlgorithmName
-        {
-            get { return engine.AlgorithmName + "/PKCS1Padding"; }
-        }
+        public IAsymmetricBlockCipher UnderlyingCipher => engine;
 
         public void Init(bool forEncryption, ICipherParameters parameters)
         {
             AsymmetricKeyParameter kParam;
-            if (parameters is ParametersWithRandom)
+            if (parameters is ParametersWithRandom withRandom)
             {
-                ParametersWithRandom rParam = (ParametersWithRandom)parameters;
-
-                this.random = rParam.Random;
-                kParam = (AsymmetricKeyParameter)rParam.Parameters;
+                this.random = withRandom.Random;
+                kParam = (AsymmetricKeyParameter)withRandom.Parameters;
             }
             else
             {
-                this.random = new SecureRandom();
+                this.random = CryptoServicesRegistrar.GetSecureRandom();
                 kParam = (AsymmetricKeyParameter)parameters;
             }
 
diff --git a/crypto/src/crypto/engines/AesEngine.cs b/crypto/src/crypto/engines/AesEngine.cs
index 10c720968..914550d6d 100644
--- a/crypto/src/crypto/engines/AesEngine.cs
+++ b/crypto/src/crypto/engines/AesEngine.cs
@@ -31,7 +31,7 @@ namespace Org.BouncyCastle.Crypto.Engines
     * This file contains the middle performance version with 2Kbytes of static tables for round precomputation.
     * </p>
     */
-    public class AesEngine
+    public sealed class AesEngine
         : IBlockCipher
     {
         // The S box
@@ -447,13 +447,9 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
-            KeyParameter keyParameter = parameters as KeyParameter;
-
-            if (keyParameter == null)
+            if (!(parameters is KeyParameter keyParameter))
                 throw new ArgumentException("invalid parameter passed to AES init - "
                     + Platform.GetTypeName(parameters));
 
@@ -463,22 +459,17 @@ namespace Org.BouncyCastle.Crypto.Engines
             this.s = Arrays.Clone(forEncryption ? S : Si);
         }
 
-        public virtual string AlgorithmName
+        public string AlgorithmName
         {
             get { return "AES"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
-        public virtual int GetBlockSize()
+        public int GetBlockSize()
         {
             return BLOCK_SIZE;
         }
 
-        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             if (WorkingKey == null)
                 throw new InvalidOperationException("AES engine not initialised");
@@ -486,6 +477,16 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, 16, "input buffer too short");
             Check.OutputLength(output, outOff, 16, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (forEncryption)
+            {
+                EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff), WorkingKey);
+            }
+            else
+            {
+                DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff), WorkingKey);
+            }
+#else
             if (forEncryption)
             {
                 EncryptBlock(input, inOff, output, outOff, WorkingKey);
@@ -494,14 +495,130 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff, WorkingKey);
             }
+#endif
 
             return BLOCK_SIZE;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (WorkingKey == null)
+                throw new InvalidOperationException("AES engine not initialised");
+
+            Check.DataLength(input, 16, "input buffer too short");
+            Check.OutputLength(output, 16, "output buffer too short");
+
+            if (forEncryption)
+            {
+                EncryptBlock(input, output, WorkingKey);
+            }
+            else
+            {
+                DecryptBlock(input, output, WorkingKey);
+            }
+
+            return BLOCK_SIZE;
         }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output, uint[][] KW)
+        {
+            uint C0 = Pack.LE_To_UInt32(input);
+            uint C1 = Pack.LE_To_UInt32(input[4..]);
+            uint C2 = Pack.LE_To_UInt32(input[8..]);
+            uint C3 = Pack.LE_To_UInt32(input[12..]);
+
+            uint[] kw = KW[0];
+            uint t0 = C0 ^ kw[0];
+            uint t1 = C1 ^ kw[1];
+            uint t2 = C2 ^ kw[2];
 
+            uint r0, r1, r2, r3 = C3 ^ kw[3];
+            int r = 1;
+            while (r < ROUNDS - 1)
+            {
+                kw = KW[r++];
+                r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
+                r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1];
+                r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2];
+                r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3];
+                kw = KW[r++];
+                t0 = T0[r0 & 255] ^ Shift(T0[(r1 >> 8) & 255], 24) ^ Shift(T0[(r2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
+                t1 = T0[r1 & 255] ^ Shift(T0[(r2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(r0 >> 24) & 255], 8) ^ kw[1];
+                t2 = T0[r2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(r0 >> 16) & 255], 16) ^ Shift(T0[(r1 >> 24) & 255], 8) ^ kw[2];
+                r3 = T0[r3 & 255] ^ Shift(T0[(r0 >> 8) & 255], 24) ^ Shift(T0[(r1 >> 16) & 255], 16) ^ Shift(T0[(r2 >> 24) & 255], 8) ^ kw[3];
+            }
+
+            kw = KW[r++];
+            r0 = T0[t0 & 255] ^ Shift(T0[(t1 >> 8) & 255], 24) ^ Shift(T0[(t2 >> 16) & 255], 16) ^ Shift(T0[(r3 >> 24) & 255], 8) ^ kw[0];
+            r1 = T0[t1 & 255] ^ Shift(T0[(t2 >> 8) & 255], 24) ^ Shift(T0[(r3 >> 16) & 255], 16) ^ Shift(T0[(t0 >> 24) & 255], 8) ^ kw[1];
+            r2 = T0[t2 & 255] ^ Shift(T0[(r3 >> 8) & 255], 24) ^ Shift(T0[(t0 >> 16) & 255], 16) ^ Shift(T0[(t1 >> 24) & 255], 8) ^ kw[2];
+            r3 = T0[r3 & 255] ^ Shift(T0[(t0 >> 8) & 255], 24) ^ Shift(T0[(t1 >> 16) & 255], 16) ^ Shift(T0[(t2 >> 24) & 255], 8) ^ kw[3];
+
+            // the final round's table is a simple function of S so we don't use a whole other four tables for it
+
+            kw = KW[r];
+            C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[0];
+            C1 = (uint)s[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[1];
+            C2 = (uint)s[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24) ^ kw[2];
+            C3 = (uint)s[r3 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24) ^ kw[3];
+
+            Pack.UInt32_To_LE(C0, output);
+            Pack.UInt32_To_LE(C1, output[4..]);
+            Pack.UInt32_To_LE(C2, output[8..]);
+            Pack.UInt32_To_LE(C3, output[12..]);
+        }
+
+        private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output, uint[][] KW)
+        {
+            uint C0 = Pack.LE_To_UInt32(input);
+            uint C1 = Pack.LE_To_UInt32(input[4..]);
+            uint C2 = Pack.LE_To_UInt32(input[8..]);
+            uint C3 = Pack.LE_To_UInt32(input[12..]);
+
+            uint[] kw = KW[ROUNDS];
+            uint t0 = C0 ^ kw[0];
+            uint t1 = C1 ^ kw[1];
+            uint t2 = C2 ^ kw[2];
+
+            uint r0, r1, r2, r3 = C3 ^ kw[3];
+            int r = ROUNDS - 1;
+            while (r > 1)
+            {
+                kw = KW[r--];
+                r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0];
+                r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1];
+                r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
+                r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3];
+                kw = KW[r--];
+                t0 = Tinv0[r0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(r2 >> 16) & 255], 16) ^ Shift(Tinv0[(r1 >> 24) & 255], 8) ^ kw[0];
+                t1 = Tinv0[r1 & 255] ^ Shift(Tinv0[(r0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(r2 >> 24) & 255], 8) ^ kw[1];
+                t2 = Tinv0[r2 & 255] ^ Shift(Tinv0[(r1 >> 8) & 255], 24) ^ Shift(Tinv0[(r0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
+                r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(r2 >> 8) & 255], 24) ^ Shift(Tinv0[(r1 >> 16) & 255], 16) ^ Shift(Tinv0[(r0 >> 24) & 255], 8) ^ kw[3];
+            }
+
+            kw = KW[1];
+            r0 = Tinv0[t0 & 255] ^ Shift(Tinv0[(r3 >> 8) & 255], 24) ^ Shift(Tinv0[(t2 >> 16) & 255], 16) ^ Shift(Tinv0[(t1 >> 24) & 255], 8) ^ kw[0];
+            r1 = Tinv0[t1 & 255] ^ Shift(Tinv0[(t0 >> 8) & 255], 24) ^ Shift(Tinv0[(r3 >> 16) & 255], 16) ^ Shift(Tinv0[(t2 >> 24) & 255], 8) ^ kw[1];
+            r2 = Tinv0[t2 & 255] ^ Shift(Tinv0[(t1 >> 8) & 255], 24) ^ Shift(Tinv0[(t0 >> 16) & 255], 16) ^ Shift(Tinv0[(r3 >> 24) & 255], 8) ^ kw[2];
+            r3 = Tinv0[r3 & 255] ^ Shift(Tinv0[(t2 >> 8) & 255], 24) ^ Shift(Tinv0[(t1 >> 16) & 255], 16) ^ Shift(Tinv0[(t0 >> 24) & 255], 8) ^ kw[3];
+
+            // the final round's table is a simple function of Si so we don't use a whole other four tables for it
+
+            kw = KW[0];
+            C0 = (uint)Si[r0 & 255] ^ (((uint)s[(r3 >> 8) & 255]) << 8) ^ (((uint)s[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[(r1 >> 24) & 255]) << 24) ^ kw[0];
+            C1 = (uint)s[r1 & 255] ^ (((uint)s[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)s[(r2 >> 24) & 255]) << 24) ^ kw[1];
+            C2 = (uint)s[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)s[(r3 >> 24) & 255]) << 24) ^ kw[2];
+            C3 = (uint)Si[r3 & 255] ^ (((uint)s[(r2 >> 8) & 255]) << 8) ^ (((uint)s[(r1 >> 16) & 255]) << 16) ^ (((uint)s[(r0 >> 24) & 255]) << 24) ^ kw[3];
+
+            Pack.UInt32_To_LE(C0, output);
+            Pack.UInt32_To_LE(C1, output[4..]);
+            Pack.UInt32_To_LE(C2, output[8..]);
+            Pack.UInt32_To_LE(C3, output[12..]);
+        }
+#else
         private void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff, uint[][] KW)
         {
             uint C0 = Pack.LE_To_UInt32(input, inOff +  0);
@@ -597,5 +714,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_LE(C2, output, outOff + 8);
             Pack.UInt32_To_LE(C3, output, outOff + 12);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/AesEngine_X86.cs b/crypto/src/crypto/engines/AesEngine_X86.cs
new file mode 100644
index 000000000..e61deb174
--- /dev/null
+++ b/crypto/src/crypto/engines/AesEngine_X86.cs
@@ -0,0 +1,830 @@
+#if NETCOREAPP3_0_OR_GREATER
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Engines
+{
+    using Aes = System.Runtime.Intrinsics.X86.Aes;
+    using Sse2 = System.Runtime.Intrinsics.X86.Sse2;
+
+    public struct AesEngine_X86
+        : IBlockCipher
+    {
+        public static bool IsSupported => Aes.IsSupported;
+
+        private static Vector128<byte>[] CreateRoundKeys(byte[] key, bool forEncryption)
+        {
+            Vector128<byte>[] K;
+
+            switch (key.Length)
+            {
+            case 16:
+            {
+                ReadOnlySpan<byte> rcon = stackalloc byte[]{ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
+
+                K = new Vector128<byte>[11];
+
+                var s = Load128(key.AsSpan(0, 16));
+                K[0] = s;
+
+                for (int round = 0; round < 10;)
+                {
+                    var t = Aes.KeygenAssist(s, rcon[round++]);
+                    t = Sse2.Shuffle(t.AsInt32(), 0xFF).AsByte();
+                    s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 8));
+                    s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 4));
+                    s = Sse2.Xor(s, t);
+                    K[round] = s;
+                }
+
+                break;
+            }
+            case 24:
+            {
+                K = new Vector128<byte>[13];
+
+                var s1 = Load128(key.AsSpan(0, 16));
+                var s2 = Load64(key.AsSpan(16, 8)).ToVector128();
+                K[0] = s1;
+
+                byte rcon = 0x01;
+                for (int round = 0;;)
+                {
+                    var t1 = Aes.KeygenAssist(s2, rcon);    rcon <<= 1;
+                    t1 = Sse2.Shuffle(t1.AsInt32(), 0x55).AsByte();
+
+                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 8));
+                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 4));
+                    s1 = Sse2.Xor(s1, t1);
+
+                    K[++round] = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s1, 8));
+
+                    var s3 = Sse2.Xor(s2, Sse2.ShiftRightLogical128BitLane(s1, 12));
+                    s3 = Sse2.Xor(s3, Sse2.ShiftLeftLogical128BitLane(s3, 4));
+
+                    K[++round] = Sse2.Xor(
+                        Sse2.ShiftRightLogical128BitLane(s1, 8),
+                        Sse2.ShiftLeftLogical128BitLane(s3, 8));
+
+                    var t2 = Aes.KeygenAssist(s3, rcon);    rcon <<= 1;
+                    t2 = Sse2.Shuffle(t2.AsInt32(), 0x55).AsByte();
+
+                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 8));
+                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 4));
+                    s1 = Sse2.Xor(s1, t2);
+
+                    K[++round] = s1;
+
+                    if (round == 12)
+                        break;
+
+                    s2 = Sse2.Xor(s3, Sse2.ShiftRightLogical128BitLane(s1, 12));
+                    s2 = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s2, 4));
+                    s2 = s2.WithUpper(Vector64<byte>.Zero);
+                }
+
+                break;
+            }
+            case 32:
+            {
+                K = new Vector128<byte>[15];
+
+                var s1 = Load128(key.AsSpan(0, 16));
+                var s2 = Load128(key.AsSpan(16, 16));
+                K[0] = s1;
+                K[1] = s2;
+
+                byte rcon = 0x01;
+                for (int round = 1;;)
+                {
+                    var t1 = Aes.KeygenAssist(s2, rcon);    rcon <<= 1;
+                    t1 = Sse2.Shuffle(t1.AsInt32(), 0xFF).AsByte();
+                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 8));
+                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 4));
+                    s1 = Sse2.Xor(s1, t1);
+                    K[++round] = s1;
+
+                    if (round == 14)
+                        break;
+
+                    var t2 = Aes.KeygenAssist(s1, 0x00);
+                    t2 = Sse2.Shuffle(t2.AsInt32(), 0xAA).AsByte();
+                    s2 = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s2, 8));
+                    s2 = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s2, 4));
+                    s2 = Sse2.Xor(s2, t2);
+                    K[++round] = s2;
+                }
+
+                break;
+            }
+            default:
+                throw new ArgumentException("Key length not 128/192/256 bits.");
+            }
+
+            if (!forEncryption)
+            {
+                for (int i = 1, last = K.Length - 1; i < last; ++i)
+                {
+                    K[i] = Aes.InverseMixColumns(K[i]);
+                }
+
+                Array.Reverse(K);
+            }
+
+            return K;
+        }
+
+        private enum Mode { DEC_128, DEC_192, DEC_256, ENC_128, ENC_192, ENC_256, UNINITIALIZED };
+
+        private Vector128<byte>[] m_roundKeys = null;
+        private Mode m_mode = Mode.UNINITIALIZED;
+
+        public AesEngine_X86()
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(AesEngine_X86));
+        }
+
+        public string AlgorithmName => "AES";
+
+        public int GetBlockSize() => 16;
+
+        public void Init(bool forEncryption, ICipherParameters parameters)
+        {
+            if (!(parameters is KeyParameter keyParameter))
+            {
+                ArgumentNullException.ThrowIfNull(parameters, nameof(parameters));
+                throw new ArgumentException("invalid type: " + Platform.GetTypeName(parameters), nameof(parameters));
+            }
+
+            m_roundKeys = CreateRoundKeys(keyParameter.GetKey(), forEncryption);
+
+            if (m_roundKeys.Length == 11)
+            {
+                m_mode = forEncryption ? Mode.ENC_128 : Mode.DEC_128;
+            }
+            else if (m_roundKeys.Length == 13)
+            {
+                m_mode = forEncryption ? Mode.ENC_192 : Mode.DEC_192;
+            }
+            else
+            {
+                m_mode = forEncryption ? Mode.ENC_256 : Mode.DEC_256;
+            }
+        }
+
+        public int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.DataLength(inBuf, inOff, 16, "input buffer too short");
+            Check.OutputLength(outBuf, outOff, 16, "output buffer too short");
+
+            var state = Load128(inBuf.AsSpan(inOff, 16));
+            ImplRounds(ref state);
+            Store128(state, outBuf.AsSpan(outOff, 16));
+            return 16;
+        }
+
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, 16, "input buffer too short");
+            Check.OutputLength(output, 16, "output buffer too short");
+
+            var state = Load128(input[..16]);
+            ImplRounds(ref state);
+            Store128(state, output[..16]);
+            return 16;
+        }
+
+        public int ProcessFourBlocks(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, 64, "input buffer too short");
+            Check.OutputLength(output, 64, "output buffer too short");
+
+            var s1 = Load128(input[..16]);
+            var s2 = Load128(input[16..32]);
+            var s3 = Load128(input[32..48]);
+            var s4 = Load128(input[48..64]);
+            ImplRounds(ref s1, ref s2, ref s3, ref s4);
+            Store128(s1, output[..16]);
+            Store128(s2, output[16..32]);
+            Store128(s3, output[32..48]);
+            Store128(s4, output[48..64]);
+            return 64;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ImplRounds(ref Vector128<byte> state)
+        {
+            switch (m_mode)
+            {
+            case Mode.DEC_128: Decrypt128(m_roundKeys, ref state); break;
+            case Mode.DEC_192: Decrypt192(m_roundKeys, ref state); break;
+            case Mode.DEC_256: Decrypt256(m_roundKeys, ref state); break;
+            case Mode.ENC_128: Encrypt128(m_roundKeys, ref state); break;
+            case Mode.ENC_192: Encrypt192(m_roundKeys, ref state); break;
+            case Mode.ENC_256: Encrypt256(m_roundKeys, ref state); break;
+            default: throw new InvalidOperationException(nameof(AesEngine_X86) + " not initialised");
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void ImplRounds(
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            switch (m_mode)
+            {
+            case Mode.DEC_128: DecryptFour128(m_roundKeys, ref s1, ref s2, ref s3, ref s4); break;
+            case Mode.DEC_192: DecryptFour192(m_roundKeys, ref s1, ref s2, ref s3, ref s4); break;
+            case Mode.DEC_256: DecryptFour256(m_roundKeys, ref s1, ref s2, ref s3, ref s4); break;
+            case Mode.ENC_128: EncryptFour128(m_roundKeys, ref s1, ref s2, ref s3, ref s4); break;
+            case Mode.ENC_192: EncryptFour192(m_roundKeys, ref s1, ref s2, ref s3, ref s4); break;
+            case Mode.ENC_256: EncryptFour256(m_roundKeys, ref s1, ref s2, ref s3, ref s4); break;
+            default: throw new InvalidOperationException(nameof(AesEngine_X86) + " not initialised");
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Decrypt128(Vector128<byte>[] roundKeys, ref Vector128<byte> state)
+        {
+            state = Sse2.Xor(state, roundKeys[0]);
+            state = Aes.Decrypt(state, roundKeys[1]);
+            state = Aes.Decrypt(state, roundKeys[2]);
+            state = Aes.Decrypt(state, roundKeys[3]);
+            state = Aes.Decrypt(state, roundKeys[4]);
+            state = Aes.Decrypt(state, roundKeys[5]);
+            state = Aes.Decrypt(state, roundKeys[6]);
+            state = Aes.Decrypt(state, roundKeys[7]);
+            state = Aes.Decrypt(state, roundKeys[8]);
+            state = Aes.Decrypt(state, roundKeys[9]);
+            state = Aes.DecryptLast(state, roundKeys[10]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Decrypt192(Vector128<byte>[] roundKeys, ref Vector128<byte> state)
+        {
+            state = Sse2.Xor(state, roundKeys[0]);
+            state = Aes.Decrypt(state, roundKeys[1]);
+            state = Aes.Decrypt(state, roundKeys[2]);
+            state = Aes.Decrypt(state, roundKeys[3]);
+            state = Aes.Decrypt(state, roundKeys[4]);
+            state = Aes.Decrypt(state, roundKeys[5]);
+            state = Aes.Decrypt(state, roundKeys[6]);
+            state = Aes.Decrypt(state, roundKeys[7]);
+            state = Aes.Decrypt(state, roundKeys[8]);
+            state = Aes.Decrypt(state, roundKeys[9]);
+            state = Aes.Decrypt(state, roundKeys[10]);
+            state = Aes.Decrypt(state, roundKeys[11]);
+            state = Aes.DecryptLast(state, roundKeys[12]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Decrypt256(Vector128<byte>[] roundKeys, ref Vector128<byte> state)
+        {
+            state = Sse2.Xor(state, roundKeys[0]);
+            state = Aes.Decrypt(state, roundKeys[1]);
+            state = Aes.Decrypt(state, roundKeys[2]);
+            state = Aes.Decrypt(state, roundKeys[3]);
+            state = Aes.Decrypt(state, roundKeys[4]);
+            state = Aes.Decrypt(state, roundKeys[5]);
+            state = Aes.Decrypt(state, roundKeys[6]);
+            state = Aes.Decrypt(state, roundKeys[7]);
+            state = Aes.Decrypt(state, roundKeys[8]);
+            state = Aes.Decrypt(state, roundKeys[9]);
+            state = Aes.Decrypt(state, roundKeys[10]);
+            state = Aes.Decrypt(state, roundKeys[11]);
+            state = Aes.Decrypt(state, roundKeys[12]);
+            state = Aes.Decrypt(state, roundKeys[13]);
+            state = Aes.DecryptLast(state, roundKeys[14]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void DecryptFour128(Vector128<byte>[] rk,
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            s1 = Sse2.Xor(s1, rk[0]);
+            s2 = Sse2.Xor(s2, rk[0]);
+            s3 = Sse2.Xor(s3, rk[0]);
+            s4 = Sse2.Xor(s4, rk[0]);
+
+            s1 = Aes.Decrypt(s1, rk[1]);
+            s2 = Aes.Decrypt(s2, rk[1]);
+            s3 = Aes.Decrypt(s3, rk[1]);
+            s4 = Aes.Decrypt(s4, rk[1]);
+
+            s1 = Aes.Decrypt(s1, rk[2]);
+            s2 = Aes.Decrypt(s2, rk[2]);
+            s3 = Aes.Decrypt(s3, rk[2]);
+            s4 = Aes.Decrypt(s4, rk[2]);
+
+            s1 = Aes.Decrypt(s1, rk[3]);
+            s2 = Aes.Decrypt(s2, rk[3]);
+            s3 = Aes.Decrypt(s3, rk[3]);
+            s4 = Aes.Decrypt(s4, rk[3]);
+
+            s1 = Aes.Decrypt(s1, rk[4]);
+            s2 = Aes.Decrypt(s2, rk[4]);
+            s3 = Aes.Decrypt(s3, rk[4]);
+            s4 = Aes.Decrypt(s4, rk[4]);
+
+            s1 = Aes.Decrypt(s1, rk[5]);
+            s2 = Aes.Decrypt(s2, rk[5]);
+            s3 = Aes.Decrypt(s3, rk[5]);
+            s4 = Aes.Decrypt(s4, rk[5]);
+
+            s1 = Aes.Decrypt(s1, rk[6]);
+            s2 = Aes.Decrypt(s2, rk[6]);
+            s3 = Aes.Decrypt(s3, rk[6]);
+            s4 = Aes.Decrypt(s4, rk[6]);
+
+            s1 = Aes.Decrypt(s1, rk[7]);
+            s2 = Aes.Decrypt(s2, rk[7]);
+            s3 = Aes.Decrypt(s3, rk[7]);
+            s4 = Aes.Decrypt(s4, rk[7]);
+
+            s1 = Aes.Decrypt(s1, rk[8]);
+            s2 = Aes.Decrypt(s2, rk[8]);
+            s3 = Aes.Decrypt(s3, rk[8]);
+            s4 = Aes.Decrypt(s4, rk[8]);
+
+            s1 = Aes.Decrypt(s1, rk[9]);
+            s2 = Aes.Decrypt(s2, rk[9]);
+            s3 = Aes.Decrypt(s3, rk[9]);
+            s4 = Aes.Decrypt(s4, rk[9]);
+
+            s1 = Aes.DecryptLast(s1, rk[10]);
+            s2 = Aes.DecryptLast(s2, rk[10]);
+            s3 = Aes.DecryptLast(s3, rk[10]);
+            s4 = Aes.DecryptLast(s4, rk[10]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void DecryptFour192(Vector128<byte>[] rk,
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            s1 = Sse2.Xor(s1, rk[0]);
+            s2 = Sse2.Xor(s2, rk[0]);
+            s3 = Sse2.Xor(s3, rk[0]);
+            s4 = Sse2.Xor(s4, rk[0]);
+
+            s1 = Aes.Decrypt(s1, rk[1]);
+            s2 = Aes.Decrypt(s2, rk[1]);
+            s3 = Aes.Decrypt(s3, rk[1]);
+            s4 = Aes.Decrypt(s4, rk[1]);
+
+            s1 = Aes.Decrypt(s1, rk[2]);
+            s2 = Aes.Decrypt(s2, rk[2]);
+            s3 = Aes.Decrypt(s3, rk[2]);
+            s4 = Aes.Decrypt(s4, rk[2]);
+
+            s1 = Aes.Decrypt(s1, rk[3]);
+            s2 = Aes.Decrypt(s2, rk[3]);
+            s3 = Aes.Decrypt(s3, rk[3]);
+            s4 = Aes.Decrypt(s4, rk[3]);
+
+            s1 = Aes.Decrypt(s1, rk[4]);
+            s2 = Aes.Decrypt(s2, rk[4]);
+            s3 = Aes.Decrypt(s3, rk[4]);
+            s4 = Aes.Decrypt(s4, rk[4]);
+
+            s1 = Aes.Decrypt(s1, rk[5]);
+            s2 = Aes.Decrypt(s2, rk[5]);
+            s3 = Aes.Decrypt(s3, rk[5]);
+            s4 = Aes.Decrypt(s4, rk[5]);
+
+            s1 = Aes.Decrypt(s1, rk[6]);
+            s2 = Aes.Decrypt(s2, rk[6]);
+            s3 = Aes.Decrypt(s3, rk[6]);
+            s4 = Aes.Decrypt(s4, rk[6]);
+
+            s1 = Aes.Decrypt(s1, rk[7]);
+            s2 = Aes.Decrypt(s2, rk[7]);
+            s3 = Aes.Decrypt(s3, rk[7]);
+            s4 = Aes.Decrypt(s4, rk[7]);
+
+            s1 = Aes.Decrypt(s1, rk[8]);
+            s2 = Aes.Decrypt(s2, rk[8]);
+            s3 = Aes.Decrypt(s3, rk[8]);
+            s4 = Aes.Decrypt(s4, rk[8]);
+
+            s1 = Aes.Decrypt(s1, rk[9]);
+            s2 = Aes.Decrypt(s2, rk[9]);
+            s3 = Aes.Decrypt(s3, rk[9]);
+            s4 = Aes.Decrypt(s4, rk[9]);
+
+            s1 = Aes.Decrypt(s1, rk[10]);
+            s2 = Aes.Decrypt(s2, rk[10]);
+            s3 = Aes.Decrypt(s3, rk[10]);
+            s4 = Aes.Decrypt(s4, rk[10]);
+
+            s1 = Aes.Decrypt(s1, rk[11]);
+            s2 = Aes.Decrypt(s2, rk[11]);
+            s3 = Aes.Decrypt(s3, rk[11]);
+            s4 = Aes.Decrypt(s4, rk[11]);
+
+            s1 = Aes.DecryptLast(s1, rk[12]);
+            s2 = Aes.DecryptLast(s2, rk[12]);
+            s3 = Aes.DecryptLast(s3, rk[12]);
+            s4 = Aes.DecryptLast(s4, rk[12]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void DecryptFour256(Vector128<byte>[] rk,
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            s1 = Sse2.Xor(s1, rk[0]);
+            s2 = Sse2.Xor(s2, rk[0]);
+            s3 = Sse2.Xor(s3, rk[0]);
+            s4 = Sse2.Xor(s4, rk[0]);
+
+            s1 = Aes.Decrypt(s1, rk[1]);
+            s2 = Aes.Decrypt(s2, rk[1]);
+            s3 = Aes.Decrypt(s3, rk[1]);
+            s4 = Aes.Decrypt(s4, rk[1]);
+
+            s1 = Aes.Decrypt(s1, rk[2]);
+            s2 = Aes.Decrypt(s2, rk[2]);
+            s3 = Aes.Decrypt(s3, rk[2]);
+            s4 = Aes.Decrypt(s4, rk[2]);
+
+            s1 = Aes.Decrypt(s1, rk[3]);
+            s2 = Aes.Decrypt(s2, rk[3]);
+            s3 = Aes.Decrypt(s3, rk[3]);
+            s4 = Aes.Decrypt(s4, rk[3]);
+
+            s1 = Aes.Decrypt(s1, rk[4]);
+            s2 = Aes.Decrypt(s2, rk[4]);
+            s3 = Aes.Decrypt(s3, rk[4]);
+            s4 = Aes.Decrypt(s4, rk[4]);
+
+            s1 = Aes.Decrypt(s1, rk[5]);
+            s2 = Aes.Decrypt(s2, rk[5]);
+            s3 = Aes.Decrypt(s3, rk[5]);
+            s4 = Aes.Decrypt(s4, rk[5]);
+
+            s1 = Aes.Decrypt(s1, rk[6]);
+            s2 = Aes.Decrypt(s2, rk[6]);
+            s3 = Aes.Decrypt(s3, rk[6]);
+            s4 = Aes.Decrypt(s4, rk[6]);
+
+            s1 = Aes.Decrypt(s1, rk[7]);
+            s2 = Aes.Decrypt(s2, rk[7]);
+            s3 = Aes.Decrypt(s3, rk[7]);
+            s4 = Aes.Decrypt(s4, rk[7]);
+
+            s1 = Aes.Decrypt(s1, rk[8]);
+            s2 = Aes.Decrypt(s2, rk[8]);
+            s3 = Aes.Decrypt(s3, rk[8]);
+            s4 = Aes.Decrypt(s4, rk[8]);
+
+            s1 = Aes.Decrypt(s1, rk[9]);
+            s2 = Aes.Decrypt(s2, rk[9]);
+            s3 = Aes.Decrypt(s3, rk[9]);
+            s4 = Aes.Decrypt(s4, rk[9]);
+
+            s1 = Aes.Decrypt(s1, rk[10]);
+            s2 = Aes.Decrypt(s2, rk[10]);
+            s3 = Aes.Decrypt(s3, rk[10]);
+            s4 = Aes.Decrypt(s4, rk[10]);
+
+            s1 = Aes.Decrypt(s1, rk[11]);
+            s2 = Aes.Decrypt(s2, rk[11]);
+            s3 = Aes.Decrypt(s3, rk[11]);
+            s4 = Aes.Decrypt(s4, rk[11]);
+
+            s1 = Aes.Decrypt(s1, rk[12]);
+            s2 = Aes.Decrypt(s2, rk[12]);
+            s3 = Aes.Decrypt(s3, rk[12]);
+            s4 = Aes.Decrypt(s4, rk[12]);
+
+            s1 = Aes.Decrypt(s1, rk[13]);
+            s2 = Aes.Decrypt(s2, rk[13]);
+            s3 = Aes.Decrypt(s3, rk[13]);
+            s4 = Aes.Decrypt(s4, rk[13]);
+
+            s1 = Aes.DecryptLast(s1, rk[14]);
+            s2 = Aes.DecryptLast(s2, rk[14]);
+            s3 = Aes.DecryptLast(s3, rk[14]);
+            s4 = Aes.DecryptLast(s4, rk[14]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Encrypt128(Vector128<byte>[] roundKeys, ref Vector128<byte> state)
+        {
+            state = Sse2.Xor(state, roundKeys[0]);
+            state = Aes.Encrypt(state, roundKeys[1]);
+            state = Aes.Encrypt(state, roundKeys[2]);
+            state = Aes.Encrypt(state, roundKeys[3]);
+            state = Aes.Encrypt(state, roundKeys[4]);
+            state = Aes.Encrypt(state, roundKeys[5]);
+            state = Aes.Encrypt(state, roundKeys[6]);
+            state = Aes.Encrypt(state, roundKeys[7]);
+            state = Aes.Encrypt(state, roundKeys[8]);
+            state = Aes.Encrypt(state, roundKeys[9]);
+            state = Aes.EncryptLast(state, roundKeys[10]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Encrypt192(Vector128<byte>[] roundKeys, ref Vector128<byte> state)
+        {
+            state = Sse2.Xor(state, roundKeys[0]);
+            state = Aes.Encrypt(state, roundKeys[1]);
+            state = Aes.Encrypt(state, roundKeys[2]);
+            state = Aes.Encrypt(state, roundKeys[3]);
+            state = Aes.Encrypt(state, roundKeys[4]);
+            state = Aes.Encrypt(state, roundKeys[5]);
+            state = Aes.Encrypt(state, roundKeys[6]);
+            state = Aes.Encrypt(state, roundKeys[7]);
+            state = Aes.Encrypt(state, roundKeys[8]);
+            state = Aes.Encrypt(state, roundKeys[9]);
+            state = Aes.Encrypt(state, roundKeys[10]);
+            state = Aes.Encrypt(state, roundKeys[11]);
+            state = Aes.EncryptLast(state, roundKeys[12]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Encrypt256(Vector128<byte>[] roundKeys, ref Vector128<byte> state)
+        {
+            state = Sse2.Xor(state, roundKeys[0]);
+            state = Aes.Encrypt(state, roundKeys[1]);
+            state = Aes.Encrypt(state, roundKeys[2]);
+            state = Aes.Encrypt(state, roundKeys[3]);
+            state = Aes.Encrypt(state, roundKeys[4]);
+            state = Aes.Encrypt(state, roundKeys[5]);
+            state = Aes.Encrypt(state, roundKeys[6]);
+            state = Aes.Encrypt(state, roundKeys[7]);
+            state = Aes.Encrypt(state, roundKeys[8]);
+            state = Aes.Encrypt(state, roundKeys[9]);
+            state = Aes.Encrypt(state, roundKeys[10]);
+            state = Aes.Encrypt(state, roundKeys[11]);
+            state = Aes.Encrypt(state, roundKeys[12]);
+            state = Aes.Encrypt(state, roundKeys[13]);
+            state = Aes.EncryptLast(state, roundKeys[14]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void EncryptFour128(Vector128<byte>[] rk,
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            s1 = Sse2.Xor(s1, rk[0]);
+            s2 = Sse2.Xor(s2, rk[0]);
+            s3 = Sse2.Xor(s3, rk[0]);
+            s4 = Sse2.Xor(s4, rk[0]);
+
+            s1 = Aes.Encrypt(s1, rk[1]);
+            s2 = Aes.Encrypt(s2, rk[1]);
+            s3 = Aes.Encrypt(s3, rk[1]);
+            s4 = Aes.Encrypt(s4, rk[1]);
+
+            s1 = Aes.Encrypt(s1, rk[2]);
+            s2 = Aes.Encrypt(s2, rk[2]);
+            s3 = Aes.Encrypt(s3, rk[2]);
+            s4 = Aes.Encrypt(s4, rk[2]);
+
+            s1 = Aes.Encrypt(s1, rk[3]);
+            s2 = Aes.Encrypt(s2, rk[3]);
+            s3 = Aes.Encrypt(s3, rk[3]);
+            s4 = Aes.Encrypt(s4, rk[3]);
+
+            s1 = Aes.Encrypt(s1, rk[4]);
+            s2 = Aes.Encrypt(s2, rk[4]);
+            s3 = Aes.Encrypt(s3, rk[4]);
+            s4 = Aes.Encrypt(s4, rk[4]);
+
+            s1 = Aes.Encrypt(s1, rk[5]);
+            s2 = Aes.Encrypt(s2, rk[5]);
+            s3 = Aes.Encrypt(s3, rk[5]);
+            s4 = Aes.Encrypt(s4, rk[5]);
+
+            s1 = Aes.Encrypt(s1, rk[6]);
+            s2 = Aes.Encrypt(s2, rk[6]);
+            s3 = Aes.Encrypt(s3, rk[6]);
+            s4 = Aes.Encrypt(s4, rk[6]);
+
+            s1 = Aes.Encrypt(s1, rk[7]);
+            s2 = Aes.Encrypt(s2, rk[7]);
+            s3 = Aes.Encrypt(s3, rk[7]);
+            s4 = Aes.Encrypt(s4, rk[7]);
+
+            s1 = Aes.Encrypt(s1, rk[8]);
+            s2 = Aes.Encrypt(s2, rk[8]);
+            s3 = Aes.Encrypt(s3, rk[8]);
+            s4 = Aes.Encrypt(s4, rk[8]);
+
+            s1 = Aes.Encrypt(s1, rk[9]);
+            s2 = Aes.Encrypt(s2, rk[9]);
+            s3 = Aes.Encrypt(s3, rk[9]);
+            s4 = Aes.Encrypt(s4, rk[9]);
+
+            s1 = Aes.EncryptLast(s1, rk[10]);
+            s2 = Aes.EncryptLast(s2, rk[10]);
+            s3 = Aes.EncryptLast(s3, rk[10]);
+            s4 = Aes.EncryptLast(s4, rk[10]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void EncryptFour192(Vector128<byte>[] rk,
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            s1 = Sse2.Xor(s1, rk[0]);
+            s2 = Sse2.Xor(s2, rk[0]);
+            s3 = Sse2.Xor(s3, rk[0]);
+            s4 = Sse2.Xor(s4, rk[0]);
+
+            s1 = Aes.Encrypt(s1, rk[1]);
+            s2 = Aes.Encrypt(s2, rk[1]);
+            s3 = Aes.Encrypt(s3, rk[1]);
+            s4 = Aes.Encrypt(s4, rk[1]);
+
+            s1 = Aes.Encrypt(s1, rk[2]);
+            s2 = Aes.Encrypt(s2, rk[2]);
+            s3 = Aes.Encrypt(s3, rk[2]);
+            s4 = Aes.Encrypt(s4, rk[2]);
+
+            s1 = Aes.Encrypt(s1, rk[3]);
+            s2 = Aes.Encrypt(s2, rk[3]);
+            s3 = Aes.Encrypt(s3, rk[3]);
+            s4 = Aes.Encrypt(s4, rk[3]);
+
+            s1 = Aes.Encrypt(s1, rk[4]);
+            s2 = Aes.Encrypt(s2, rk[4]);
+            s3 = Aes.Encrypt(s3, rk[4]);
+            s4 = Aes.Encrypt(s4, rk[4]);
+
+            s1 = Aes.Encrypt(s1, rk[5]);
+            s2 = Aes.Encrypt(s2, rk[5]);
+            s3 = Aes.Encrypt(s3, rk[5]);
+            s4 = Aes.Encrypt(s4, rk[5]);
+
+            s1 = Aes.Encrypt(s1, rk[6]);
+            s2 = Aes.Encrypt(s2, rk[6]);
+            s3 = Aes.Encrypt(s3, rk[6]);
+            s4 = Aes.Encrypt(s4, rk[6]);
+
+            s1 = Aes.Encrypt(s1, rk[7]);
+            s2 = Aes.Encrypt(s2, rk[7]);
+            s3 = Aes.Encrypt(s3, rk[7]);
+            s4 = Aes.Encrypt(s4, rk[7]);
+
+            s1 = Aes.Encrypt(s1, rk[8]);
+            s2 = Aes.Encrypt(s2, rk[8]);
+            s3 = Aes.Encrypt(s3, rk[8]);
+            s4 = Aes.Encrypt(s4, rk[8]);
+
+            s1 = Aes.Encrypt(s1, rk[9]);
+            s2 = Aes.Encrypt(s2, rk[9]);
+            s3 = Aes.Encrypt(s3, rk[9]);
+            s4 = Aes.Encrypt(s4, rk[9]);
+
+            s1 = Aes.Encrypt(s1, rk[10]);
+            s2 = Aes.Encrypt(s2, rk[10]);
+            s3 = Aes.Encrypt(s3, rk[10]);
+            s4 = Aes.Encrypt(s4, rk[10]);
+
+            s1 = Aes.Encrypt(s1, rk[11]);
+            s2 = Aes.Encrypt(s2, rk[11]);
+            s3 = Aes.Encrypt(s3, rk[11]);
+            s4 = Aes.Encrypt(s4, rk[11]);
+
+            s1 = Aes.EncryptLast(s1, rk[12]);
+            s2 = Aes.EncryptLast(s2, rk[12]);
+            s3 = Aes.EncryptLast(s3, rk[12]);
+            s4 = Aes.EncryptLast(s4, rk[12]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void EncryptFour256(Vector128<byte>[] rk,
+            ref Vector128<byte> s1, ref Vector128<byte> s2, ref Vector128<byte> s3, ref Vector128<byte> s4)
+        {
+            s1 = Sse2.Xor(s1, rk[0]);
+            s2 = Sse2.Xor(s2, rk[0]);
+            s3 = Sse2.Xor(s3, rk[0]);
+            s4 = Sse2.Xor(s4, rk[0]);
+
+            s1 = Aes.Encrypt(s1, rk[1]);
+            s2 = Aes.Encrypt(s2, rk[1]);
+            s3 = Aes.Encrypt(s3, rk[1]);
+            s4 = Aes.Encrypt(s4, rk[1]);
+
+            s1 = Aes.Encrypt(s1, rk[2]);
+            s2 = Aes.Encrypt(s2, rk[2]);
+            s3 = Aes.Encrypt(s3, rk[2]);
+            s4 = Aes.Encrypt(s4, rk[2]);
+
+            s1 = Aes.Encrypt(s1, rk[3]);
+            s2 = Aes.Encrypt(s2, rk[3]);
+            s3 = Aes.Encrypt(s3, rk[3]);
+            s4 = Aes.Encrypt(s4, rk[3]);
+
+            s1 = Aes.Encrypt(s1, rk[4]);
+            s2 = Aes.Encrypt(s2, rk[4]);
+            s3 = Aes.Encrypt(s3, rk[4]);
+            s4 = Aes.Encrypt(s4, rk[4]);
+
+            s1 = Aes.Encrypt(s1, rk[5]);
+            s2 = Aes.Encrypt(s2, rk[5]);
+            s3 = Aes.Encrypt(s3, rk[5]);
+            s4 = Aes.Encrypt(s4, rk[5]);
+
+            s1 = Aes.Encrypt(s1, rk[6]);
+            s2 = Aes.Encrypt(s2, rk[6]);
+            s3 = Aes.Encrypt(s3, rk[6]);
+            s4 = Aes.Encrypt(s4, rk[6]);
+
+            s1 = Aes.Encrypt(s1, rk[7]);
+            s2 = Aes.Encrypt(s2, rk[7]);
+            s3 = Aes.Encrypt(s3, rk[7]);
+            s4 = Aes.Encrypt(s4, rk[7]);
+
+            s1 = Aes.Encrypt(s1, rk[8]);
+            s2 = Aes.Encrypt(s2, rk[8]);
+            s3 = Aes.Encrypt(s3, rk[8]);
+            s4 = Aes.Encrypt(s4, rk[8]);
+
+            s1 = Aes.Encrypt(s1, rk[9]);
+            s2 = Aes.Encrypt(s2, rk[9]);
+            s3 = Aes.Encrypt(s3, rk[9]);
+            s4 = Aes.Encrypt(s4, rk[9]);
+
+            s1 = Aes.Encrypt(s1, rk[10]);
+            s2 = Aes.Encrypt(s2, rk[10]);
+            s3 = Aes.Encrypt(s3, rk[10]);
+            s4 = Aes.Encrypt(s4, rk[10]);
+
+            s1 = Aes.Encrypt(s1, rk[11]);
+            s2 = Aes.Encrypt(s2, rk[11]);
+            s3 = Aes.Encrypt(s3, rk[11]);
+            s4 = Aes.Encrypt(s4, rk[11]);
+
+            s1 = Aes.Encrypt(s1, rk[12]);
+            s2 = Aes.Encrypt(s2, rk[12]);
+            s3 = Aes.Encrypt(s3, rk[12]);
+            s4 = Aes.Encrypt(s4, rk[12]);
+
+            s1 = Aes.Encrypt(s1, rk[13]);
+            s2 = Aes.Encrypt(s2, rk[13]);
+            s3 = Aes.Encrypt(s3, rk[13]);
+            s4 = Aes.Encrypt(s4, rk[13]);
+
+            s1 = Aes.EncryptLast(s1, rk[14]);
+            s2 = Aes.EncryptLast(s2, rk[14]);
+            s3 = Aes.EncryptLast(s3, rk[14]);
+            s4 = Aes.EncryptLast(s4, rk[14]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Vector128<byte> Load128(ReadOnlySpan<byte> t)
+        {
+#if NET7_0_OR_GREATER
+            return Vector128.Create<byte>(t);
+#else
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+                return MemoryMarshal.Read<Vector128<byte>>(t);
+
+            return Vector128.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[..8]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[8..])
+            ).AsByte();
+#endif
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Vector64<byte> Load64(ReadOnlySpan<byte> t)
+        {
+#if NET7_0_OR_GREATER
+            return Vector64.Create<byte>(t);
+#else
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector64<byte>>() == 8)
+                return MemoryMarshal.Read<Vector64<byte>>(t);
+
+            return Vector64.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[..8])
+            ).AsByte();
+#endif
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void Store128(Vector128<byte> s, Span<byte> t)
+        {
+#if NET7_0_OR_GREATER
+            Vector128.CopyTo(s, t);
+#else
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                MemoryMarshal.Write(t, ref s);
+                return;
+            }
+
+            var u = s.AsUInt64();
+            BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
+#endif
+        }
+    }
+}
+#endif
diff --git a/crypto/src/crypto/engines/AesLightEngine.cs b/crypto/src/crypto/engines/AesLightEngine.cs
index 8d5a98a9f..8d76f4388 100644
--- a/crypto/src/crypto/engines/AesLightEngine.cs
+++ b/crypto/src/crypto/engines/AesLightEngine.cs
@@ -33,7 +33,7 @@ namespace Org.BouncyCastle.Crypto.Engines
     * for round precomputation, but it has the smallest foot print.
     * </p>
     */
-    public class AesLightEngine
+    public sealed class AesLightEngine
         : IBlockCipher
     {
         // The S box
@@ -342,13 +342,9 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
-            KeyParameter keyParameter = parameters as KeyParameter;
-
-            if (keyParameter == null)
+            if (!(parameters is KeyParameter keyParameter))
                 throw new ArgumentException("invalid parameter passed to AES init - "
                     + Platform.GetTypeName(parameters));
 
@@ -357,22 +353,17 @@ namespace Org.BouncyCastle.Crypto.Engines
             this.forEncryption = forEncryption;
         }
 
-        public virtual string AlgorithmName
+        public string AlgorithmName
         {
             get { return "AES"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
-        public virtual int GetBlockSize()
+        public int GetBlockSize()
         {
             return BLOCK_SIZE;
         }
 
-        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             if (WorkingKey == null)
                 throw new InvalidOperationException("AES engine not initialised");
@@ -380,6 +371,16 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, 16, "input buffer too short");
             Check.OutputLength(output, outOff, 16, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (forEncryption)
+            {
+                EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff), WorkingKey);
+            }
+            else
+            {
+                DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff), WorkingKey);
+            }
+#else
             if (forEncryption)
             {
                 EncryptBlock(input, inOff, output, outOff, WorkingKey);
@@ -388,14 +389,130 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff, WorkingKey);
             }
+#endif
 
             return BLOCK_SIZE;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (WorkingKey == null)
+                throw new InvalidOperationException("AES engine not initialised");
+
+            Check.DataLength(input, 16, "input buffer too short");
+            Check.OutputLength(output, 16, "output buffer too short");
+
+            if (forEncryption)
+            {
+                EncryptBlock(input, output, WorkingKey);
+            }
+            else
+            {
+                DecryptBlock(input, output, WorkingKey);
+            }
+
+            return BLOCK_SIZE;
         }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output, uint[][] KW)
+        {
+            uint C0 = Pack.LE_To_UInt32(input);
+            uint C1 = Pack.LE_To_UInt32(input[4..]);
+            uint C2 = Pack.LE_To_UInt32(input[8..]);
+            uint C3 = Pack.LE_To_UInt32(input[12..]);
+
+            uint[] kw = KW[0];
+            uint t0 = C0 ^ kw[0];
+            uint t1 = C1 ^ kw[1];
+            uint t2 = C2 ^ kw[2];
 
+            uint r0, r1, r2, r3 = C3 ^ kw[3];
+            int r = 1;
+            while (r < ROUNDS - 1)
+            {
+                kw = KW[r++];
+                r0 = Mcol((uint)S[t0 & 255] ^ (((uint)S[(t1 >> 8) & 255]) << 8) ^ (((uint)S[(t2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24)) ^ kw[0];
+                r1 = Mcol((uint)S[t1 & 255] ^ (((uint)S[(t2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(t0 >> 24) & 255]) << 24)) ^ kw[1];
+                r2 = Mcol((uint)S[t2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(t0 >> 16) & 255]) << 16) ^ (((uint)S[(t1 >> 24) & 255]) << 24)) ^ kw[2];
+                r3 = Mcol((uint)S[r3 & 255] ^ (((uint)S[(t0 >> 8) & 255]) << 8) ^ (((uint)S[(t1 >> 16) & 255]) << 16) ^ (((uint)S[(t2 >> 24) & 255]) << 24)) ^ kw[3];
+                kw = KW[r++];
+                t0 = Mcol((uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24)) ^ kw[0];
+                t1 = Mcol((uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(r0 >> 24) & 255]) << 24)) ^ kw[1];
+                t2 = Mcol((uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24)) ^ kw[2];
+                r3 = Mcol((uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24)) ^ kw[3];
+            }
+
+            kw = KW[r++];
+            r0 = Mcol((uint)S[t0 & 255] ^ (((uint)S[(t1 >> 8) & 255]) << 8) ^ (((uint)S[(t2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24)) ^ kw[0];
+            r1 = Mcol((uint)S[t1 & 255] ^ (((uint)S[(t2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(t0 >> 24) & 255]) << 24)) ^ kw[1];
+            r2 = Mcol((uint)S[t2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(t0 >> 16) & 255]) << 16) ^ (((uint)S[(t1 >> 24) & 255]) << 24)) ^ kw[2];
+            r3 = Mcol((uint)S[r3 & 255] ^ (((uint)S[(t0 >> 8) & 255]) << 8) ^ (((uint)S[(t1 >> 16) & 255]) << 16) ^ (((uint)S[(t2 >> 24) & 255]) << 24)) ^ kw[3];
+
+            // the final round is a simple function of S
+
+            kw = KW[r];
+            C0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[(r3 >> 24) & 255]) << 24) ^ kw[0];
+            C1 = (uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[(r0 >> 24) & 255]) << 24) ^ kw[1];
+            C2 = (uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[(r1 >> 24) & 255]) << 24) ^ kw[2];
+            C3 = (uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[(r2 >> 24) & 255]) << 24) ^ kw[3];
+
+            Pack.UInt32_To_LE(C0, output);
+            Pack.UInt32_To_LE(C1, output[4..]);
+            Pack.UInt32_To_LE(C2, output[8..]);
+            Pack.UInt32_To_LE(C3, output[12..]);
+        }
+
+        private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output, uint[][] KW)
+        {
+            uint C0 = Pack.LE_To_UInt32(input);
+            uint C1 = Pack.LE_To_UInt32(input[4..]);
+            uint C2 = Pack.LE_To_UInt32(input[8..]);
+            uint C3 = Pack.LE_To_UInt32(input[12..]);
+
+            uint[] kw = KW[ROUNDS];
+            uint t0 = C0 ^ kw[0];
+            uint t1 = C1 ^ kw[1];
+            uint t2 = C2 ^ kw[2];
+
+            uint r0, r1, r2, r3 = C3 ^ kw[3];
+            int r = ROUNDS - 1;
+            while (r > 1)
+            {
+                kw = KW[r--];
+                r0 = Inv_Mcol((uint)Si[t0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(t2 >> 16) & 255]) << 16) ^ ((uint)Si[(t1 >> 24) & 255] << 24)) ^ kw[0];
+                r1 = Inv_Mcol((uint)Si[t1 & 255] ^ (((uint)Si[(t0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ ((uint)Si[(t2 >> 24) & 255] << 24)) ^ kw[1];
+                r2 = Inv_Mcol((uint)Si[t2 & 255] ^ (((uint)Si[(t1 >> 8) & 255]) << 8) ^ (((uint)Si[(t0 >> 16) & 255]) << 16) ^ ((uint)Si[(r3 >> 24) & 255] << 24)) ^ kw[2];
+                r3 = Inv_Mcol((uint)Si[r3 & 255] ^ (((uint)Si[(t2 >> 8) & 255]) << 8) ^ (((uint)Si[(t1 >> 16) & 255]) << 16) ^ ((uint)Si[(t0 >> 24) & 255] << 24)) ^ kw[3];
+                kw = KW[r--];
+                t0 = Inv_Mcol((uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ ((uint)Si[(r1 >> 24) & 255] << 24)) ^ kw[0];
+                t1 = Inv_Mcol((uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ ((uint)Si[(r2 >> 24) & 255] << 24)) ^ kw[1];
+                t2 = Inv_Mcol((uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ ((uint)Si[(r3 >> 24) & 255] << 24)) ^ kw[2];
+                r3 = Inv_Mcol((uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ ((uint)Si[(r0 >> 24) & 255] << 24)) ^ kw[3];
+            }
+
+            kw = KW[1];
+            r0 = Inv_Mcol((uint)Si[t0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(t2 >> 16) & 255]) << 16) ^ ((uint)Si[(t1 >> 24) & 255] << 24)) ^ kw[0];
+            r1 = Inv_Mcol((uint)Si[t1 & 255] ^ (((uint)Si[(t0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ ((uint)Si[(t2 >> 24) & 255] << 24)) ^ kw[1];
+            r2 = Inv_Mcol((uint)Si[t2 & 255] ^ (((uint)Si[(t1 >> 8) & 255]) << 8) ^ (((uint)Si[(t0 >> 16) & 255]) << 16) ^ ((uint)Si[(r3 >> 24) & 255] << 24)) ^ kw[2];
+            r3 = Inv_Mcol((uint)Si[r3 & 255] ^ (((uint)Si[(t2 >> 8) & 255]) << 8) ^ (((uint)Si[(t1 >> 16) & 255]) << 16) ^ ((uint)Si[(t0 >> 24) & 255] << 24)) ^ kw[3];
+
+            // the final round's table is a simple function of Si
+
+            kw = KW[0];
+            C0 = (uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[(r1 >> 24) & 255]) << 24) ^ kw[0];
+            C1 = (uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)Si[(r2 >> 24) & 255]) << 24) ^ kw[1];
+            C2 = (uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)Si[(r3 >> 24) & 255]) << 24) ^ kw[2];
+            C3 = (uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ (((uint)Si[(r0 >> 24) & 255]) << 24) ^ kw[3];
+
+            Pack.UInt32_To_LE(C0, output);
+            Pack.UInt32_To_LE(C1, output[4..]);
+            Pack.UInt32_To_LE(C2, output[8..]);
+            Pack.UInt32_To_LE(C3, output[12..]);
+        }
+#else
         private void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff, uint[][] KW)
         {
             uint C0 = Pack.LE_To_UInt32(input, inOff + 0);
@@ -491,5 +608,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_LE(C2, output, outOff + 8);
             Pack.UInt32_To_LE(C3, output, outOff + 12);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/AesX86Engine.cs b/crypto/src/crypto/engines/AesX86Engine.cs
deleted file mode 100644
index a270c2ad2..000000000
--- a/crypto/src/crypto/engines/AesX86Engine.cs
+++ /dev/null
@@ -1,377 +0,0 @@
-#if NETCOREAPP3_0_OR_GREATER
-using System;
-using System.Runtime.CompilerServices;
-using System.Runtime.Intrinsics;
-
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Utilities;
-
-namespace Org.BouncyCastle.Crypto.Engines
-{
-    using Aes = System.Runtime.Intrinsics.X86.Aes;
-    using Sse2 = System.Runtime.Intrinsics.X86.Sse2;
-
-    public struct AesX86Engine
-        : IBlockCipher
-    {
-        public static bool IsSupported => Aes.IsSupported;
-
-        private static Vector128<byte>[] CreateRoundKeys(byte[] key, bool forEncryption)
-        {
-            Vector128<byte>[] K;
-
-            switch (key.Length)
-            {
-            case 16:
-            {
-                ReadOnlySpan<byte> rcon = stackalloc byte[]{ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
-
-                K = new Vector128<byte>[11];
-
-                var s = Load128(key.AsSpan(0, 16));
-                K[0] = s;
-
-                for (int round = 0; round < 10;)
-                {
-                    var t = Aes.KeygenAssist(s, rcon[round++]);
-                    t = Sse2.Shuffle(t.AsInt32(), 0xFF).AsByte();
-                    s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 8));
-                    s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 4));
-                    s = Sse2.Xor(s, t);
-                    K[round] = s;
-                }
-
-                break;
-            }
-            case 24:
-            {
-                K = new Vector128<byte>[13];
-
-                var s1 = Load128(key.AsSpan(0, 16));
-                var s2 = Load64(key.AsSpan(16, 8)).ToVector128();
-                K[0] = s1;
-
-                byte rcon = 0x01;
-                for (int round = 0;;)
-                {
-                    var t1 = Aes.KeygenAssist(s2, rcon);    rcon <<= 1;
-                    t1 = Sse2.Shuffle(t1.AsInt32(), 0x55).AsByte();
-
-                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 8));
-                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 4));
-                    s1 = Sse2.Xor(s1, t1);
-
-                    K[++round] = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s1, 8));
-
-                    var s3 = Sse2.Xor(s2, Sse2.ShiftRightLogical128BitLane(s1, 12));
-                    s3 = Sse2.Xor(s3, Sse2.ShiftLeftLogical128BitLane(s3, 4));
-
-                    K[++round] = Sse2.Xor(
-                        Sse2.ShiftRightLogical128BitLane(s1, 8),
-                        Sse2.ShiftLeftLogical128BitLane(s3, 8));
-
-                    var t2 = Aes.KeygenAssist(s3, rcon);    rcon <<= 1;
-                    t2 = Sse2.Shuffle(t2.AsInt32(), 0x55).AsByte();
-
-                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 8));
-                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 4));
-                    s1 = Sse2.Xor(s1, t2);
-
-                    K[++round] = s1;
-
-                    if (round == 12)
-                        break;
-
-                    s2 = Sse2.Xor(s3, Sse2.ShiftRightLogical128BitLane(s1, 12));
-                    s2 = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s2, 4));
-                    s2 = s2.WithUpper(Vector64<byte>.Zero);
-                }
-
-                break;
-            }
-            case 32:
-            {
-                K = new Vector128<byte>[15];
-
-                var s1 = Load128(key.AsSpan(0, 16));
-                var s2 = Load128(key.AsSpan(16, 16));
-                K[0] = s1;
-                K[1] = s2;
-
-                byte rcon = 0x01;
-                for (int round = 1;;)
-                {
-                    var t1 = Aes.KeygenAssist(s2, rcon);    rcon <<= 1;
-                    t1 = Sse2.Shuffle(t1.AsInt32(), 0xFF).AsByte();
-                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 8));
-                    s1 = Sse2.Xor(s1, Sse2.ShiftLeftLogical128BitLane(s1, 4));
-                    s1 = Sse2.Xor(s1, t1);
-                    K[++round] = s1;
-
-                    if (round == 14)
-                        break;
-
-                    var t2 = Aes.KeygenAssist(s1, 0x00);
-                    t2 = Sse2.Shuffle(t2.AsInt32(), 0xAA).AsByte();
-                    s2 = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s2, 8));
-                    s2 = Sse2.Xor(s2, Sse2.ShiftLeftLogical128BitLane(s2, 4));
-                    s2 = Sse2.Xor(s2, t2);
-                    K[++round] = s2;
-                }
-
-                break;
-            }
-            default:
-                throw new ArgumentException("Key length not 128/192/256 bits.");
-            }
-
-            if (!forEncryption)
-            {
-                for (int i = 1, last = K.Length - 1; i < last; ++i)
-                {
-                    K[i] = Aes.InverseMixColumns(K[i]);
-                }
-
-                Array.Reverse(K);
-            }
-
-            return K;
-        }
-
-        private enum Mode { DEC_128, DEC_192, DEC_256, ENC_128, ENC_192, ENC_256, UNINITIALIZED };
-
-        private Vector128<byte>[] m_roundKeys = null;
-        private Mode m_mode = Mode.UNINITIALIZED;
-
-        public AesX86Engine()
-        {
-            if (!IsSupported)
-                throw new PlatformNotSupportedException(nameof(AesX86Engine));
-        }
-
-        public string AlgorithmName => "AES";
-
-        public bool IsPartialBlockOkay => false;
-
-        public int GetBlockSize() => 16;
-
-        public void Init(bool forEncryption, ICipherParameters parameters)
-        {
-            if (!(parameters is KeyParameter keyParameter))
-            {
-                ArgumentNullException.ThrowIfNull(parameters, nameof(parameters));
-                throw new ArgumentException("invalid type: " + Platform.GetTypeName(parameters), nameof(parameters));
-            }
-
-            m_roundKeys = CreateRoundKeys(keyParameter.GetKey(), forEncryption);
-
-            if (m_roundKeys.Length == 11)
-            {
-                m_mode = forEncryption ? Mode.ENC_128 : Mode.DEC_128;
-            }
-            else if (m_roundKeys.Length == 13)
-            {
-                m_mode = forEncryption ? Mode.ENC_192 : Mode.DEC_192;
-            }
-            else
-            {
-                m_mode = forEncryption ? Mode.ENC_256 : Mode.DEC_256;
-            }
-        }
-
-        public int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
-        {
-            Check.DataLength(inBuf, inOff, 16, "input buffer too short");
-            Check.OutputLength(outBuf, outOff, 16, "output buffer too short");
-
-            var state = Load128(inBuf.AsSpan(inOff, 16));
-            ImplRounds(ref state);
-            Store128(ref state, outBuf.AsSpan(outOff, 16));
-            return 16;
-        }
-
-        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
-        {
-            Check.DataLength(input, 16, "input buffer too short");
-            Check.OutputLength(output, 16, "output buffer too short");
-
-            var state = Load128(input);
-            ImplRounds(ref state);
-            Store128(ref state, output);
-            return 16;
-        }
-
-        public void Reset()
-        {
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void ImplRounds(ref Vector128<byte> state)
-        {
-            switch (m_mode)
-            {
-            case Mode.DEC_128: Decrypt128(ref state, m_roundKeys); break;
-            case Mode.DEC_192: Decrypt192(ref state, m_roundKeys); break;
-            case Mode.DEC_256: Decrypt256(ref state, m_roundKeys); break;
-            case Mode.ENC_128: Encrypt128(ref state, m_roundKeys); break;
-            case Mode.ENC_192: Encrypt192(ref state, m_roundKeys); break;
-            case Mode.ENC_256: Encrypt256(ref state, m_roundKeys); break;
-            default: throw new InvalidOperationException(nameof(AesX86Engine) + " not initialised");
-            }
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Decrypt128(ref Vector128<byte> state, Vector128<byte>[] roundKeys)
-        {
-            state = Sse2.Xor(state, roundKeys[0]);
-            state = Aes.Decrypt(state, roundKeys[1]);
-            state = Aes.Decrypt(state, roundKeys[2]);
-            state = Aes.Decrypt(state, roundKeys[3]);
-            state = Aes.Decrypt(state, roundKeys[4]);
-            state = Aes.Decrypt(state, roundKeys[5]);
-            state = Aes.Decrypt(state, roundKeys[6]);
-            state = Aes.Decrypt(state, roundKeys[7]);
-            state = Aes.Decrypt(state, roundKeys[8]);
-            state = Aes.Decrypt(state, roundKeys[9]);
-            state = Aes.DecryptLast(state, roundKeys[10]);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Decrypt192(ref Vector128<byte> state, Vector128<byte>[] roundKeys)
-        {
-            state = Sse2.Xor(state, roundKeys[0]);
-            state = Aes.Decrypt(state, roundKeys[1]);
-            state = Aes.Decrypt(state, roundKeys[2]);
-            state = Aes.Decrypt(state, roundKeys[3]);
-            state = Aes.Decrypt(state, roundKeys[4]);
-            state = Aes.Decrypt(state, roundKeys[5]);
-            state = Aes.Decrypt(state, roundKeys[6]);
-            state = Aes.Decrypt(state, roundKeys[7]);
-            state = Aes.Decrypt(state, roundKeys[8]);
-            state = Aes.Decrypt(state, roundKeys[9]);
-            state = Aes.Decrypt(state, roundKeys[10]);
-            state = Aes.Decrypt(state, roundKeys[11]);
-            state = Aes.DecryptLast(state, roundKeys[12]);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Decrypt256(ref Vector128<byte> state, Vector128<byte>[] roundKeys)
-        {
-            state = Sse2.Xor(state, roundKeys[0]);
-            state = Aes.Decrypt(state, roundKeys[1]);
-            state = Aes.Decrypt(state, roundKeys[2]);
-            state = Aes.Decrypt(state, roundKeys[3]);
-            state = Aes.Decrypt(state, roundKeys[4]);
-            state = Aes.Decrypt(state, roundKeys[5]);
-            state = Aes.Decrypt(state, roundKeys[6]);
-            state = Aes.Decrypt(state, roundKeys[7]);
-            state = Aes.Decrypt(state, roundKeys[8]);
-            state = Aes.Decrypt(state, roundKeys[9]);
-            state = Aes.Decrypt(state, roundKeys[10]);
-            state = Aes.Decrypt(state, roundKeys[11]);
-            state = Aes.Decrypt(state, roundKeys[12]);
-            state = Aes.Decrypt(state, roundKeys[13]);
-            state = Aes.DecryptLast(state, roundKeys[14]);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Encrypt128(ref Vector128<byte> state, Vector128<byte>[] roundKeys)
-        {
-            state = Sse2.Xor(state, roundKeys[0]);
-            state = Aes.Encrypt(state, roundKeys[1]);
-            state = Aes.Encrypt(state, roundKeys[2]);
-            state = Aes.Encrypt(state, roundKeys[3]);
-            state = Aes.Encrypt(state, roundKeys[4]);
-            state = Aes.Encrypt(state, roundKeys[5]);
-            state = Aes.Encrypt(state, roundKeys[6]);
-            state = Aes.Encrypt(state, roundKeys[7]);
-            state = Aes.Encrypt(state, roundKeys[8]);
-            state = Aes.Encrypt(state, roundKeys[9]);
-            state = Aes.EncryptLast(state, roundKeys[10]);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Encrypt192(ref Vector128<byte> state, Vector128<byte>[] roundKeys)
-        {
-            state = Sse2.Xor(state, roundKeys[0]);
-            state = Aes.Encrypt(state, roundKeys[1]);
-            state = Aes.Encrypt(state, roundKeys[2]);
-            state = Aes.Encrypt(state, roundKeys[3]);
-            state = Aes.Encrypt(state, roundKeys[4]);
-            state = Aes.Encrypt(state, roundKeys[5]);
-            state = Aes.Encrypt(state, roundKeys[6]);
-            state = Aes.Encrypt(state, roundKeys[7]);
-            state = Aes.Encrypt(state, roundKeys[8]);
-            state = Aes.Encrypt(state, roundKeys[9]);
-            state = Aes.Encrypt(state, roundKeys[10]);
-            state = Aes.Encrypt(state, roundKeys[11]);
-            state = Aes.EncryptLast(state, roundKeys[12]);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Encrypt256(ref Vector128<byte> state, Vector128<byte>[] roundKeys)
-        {
-            state = Sse2.Xor(state, roundKeys[0]);
-            state = Aes.Encrypt(state, roundKeys[1]);
-            state = Aes.Encrypt(state, roundKeys[2]);
-            state = Aes.Encrypt(state, roundKeys[3]);
-            state = Aes.Encrypt(state, roundKeys[4]);
-            state = Aes.Encrypt(state, roundKeys[5]);
-            state = Aes.Encrypt(state, roundKeys[6]);
-            state = Aes.Encrypt(state, roundKeys[7]);
-            state = Aes.Encrypt(state, roundKeys[8]);
-            state = Aes.Encrypt(state, roundKeys[9]);
-            state = Aes.Encrypt(state, roundKeys[10]);
-            state = Aes.Encrypt(state, roundKeys[11]);
-            state = Aes.Encrypt(state, roundKeys[12]);
-            state = Aes.Encrypt(state, roundKeys[13]);
-            state = Aes.EncryptLast(state, roundKeys[14]);
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static Vector128<byte> Load128(ReadOnlySpan<byte> t)
-        {
-#if NET7_0_OR_GREATER
-            return Vector128.Create<byte>(t);
-#else
-            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
-                return Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(t[0]));
-
-            return Vector128.Create(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12],
-                t[13], t[14], t[15]);
-#endif
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static Vector64<byte> Load64(ReadOnlySpan<byte> t)
-        {
-#if NET7_0_OR_GREATER
-            return Vector64.Create<byte>(t);
-#else
-            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector64<byte>>() == 8)
-                return Unsafe.ReadUnaligned<Vector64<byte>>(ref Unsafe.AsRef(t[0]));
-
-            return Vector64.Create(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7]);
-#endif
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static void Store128(ref Vector128<byte> s, Span<byte> t)
-        {
-#if NET7_0_OR_GREATER
-            Vector128.CopyTo(s, t);
-#else
-            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
-            {
-                Unsafe.WriteUnaligned(ref t[0], s);
-                return;
-            }
-
-            var u = s.AsUInt64();
-            Utilities.Pack.UInt64_To_LE(u.GetElement(0), t);
-            Utilities.Pack.UInt64_To_LE(u.GetElement(1), t[8..]);
-#endif
-        }
-    }
-}
-#endif
diff --git a/crypto/src/crypto/engines/AriaEngine.cs b/crypto/src/crypto/engines/AriaEngine.cs
index 2f94dc048..c52fd30bf 100644
--- a/crypto/src/crypto/engines/AriaEngine.cs
+++ b/crypto/src/crypto/engines/AriaEngine.cs
@@ -157,11 +157,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "ARIA"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
         public virtual int GetBlockSize()
         {
             return BlockSize;
@@ -195,10 +190,35 @@ namespace Org.BouncyCastle.Crypto.Engines
             return BlockSize;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            // Empty
+            if (m_roundKeys == null)
+                throw new InvalidOperationException("ARIA engine not initialised");
+
+            Check.DataLength(input, BlockSize, "input buffer too short");
+            Check.OutputLength(output, BlockSize, "output buffer too short");
+
+            byte[] z = new byte[BlockSize];
+            input[..BlockSize].CopyTo(z);
+
+            int i = 0, rounds = m_roundKeys.Length - 3;
+            while (i < rounds)
+            {
+                FO(z, m_roundKeys[i++]);
+                FE(z, m_roundKeys[i++]);
+            }
+
+            FO(z, m_roundKeys[i++]);
+            Xor(z, m_roundKeys[i++]);
+            SL2(z);
+            Xor(z, m_roundKeys[i]);
+
+            z.CopyTo(output);
+
+            return BlockSize;
         }
+#endif
 
         protected static void A(byte[] z)
         {
diff --git a/crypto/src/crypto/engines/BlowfishEngine.cs b/crypto/src/crypto/engines/BlowfishEngine.cs
index 1b3dd9743..1152e3d78 100644
--- a/crypto/src/crypto/engines/BlowfishEngine.cs
+++ b/crypto/src/crypto/engines/BlowfishEngine.cs
@@ -342,16 +342,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "Blowfish"; }
         }
 
-		public bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
-		public  int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+		public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             if (workingKey == null)
                 throw new InvalidOperationException("Blowfish not initialised");
@@ -359,7 +350,17 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
-            if (encrypting)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			if (encrypting)
+			{
+				EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+			}
+			else
+			{
+				DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+			}
+#else
+			if (encrypting)
             {
                 EncryptBlock(input, inOff, output, outOff);
             }
@@ -367,13 +368,32 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff);
             }
+#endif
 
             return BLOCK_SIZE;
         }
 
-        public void Reset()
-        {
-        }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			if (workingKey == null)
+				throw new InvalidOperationException("Blowfish not initialised");
+
+			Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+			Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+			if (encrypting)
+			{
+				EncryptBlock(input, output);
+			}
+			else
+			{
+				DecryptBlock(input, output);
+			}
+
+			return BLOCK_SIZE;
+		}
+#endif
 
         public int GetBlockSize()
         {
@@ -499,16 +519,46 @@ namespace Org.BouncyCastle.Crypto.Engines
             ProcessTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3);
         }
 
-        /**
-        * Encrypt the given input starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        * The input will be an exact multiple of our blocksize.
-        */
-        private void EncryptBlock(
-            byte[]  src,
-            int     srcIndex,
-            byte[]  dst,
-            int     dstIndex)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint xl = Pack.BE_To_UInt32(input);
+			uint xr = Pack.BE_To_UInt32(input[4..]);
+
+			xl ^= P[0];
+
+			for (int i = 1; i < ROUNDS; i += 2)
+			{
+				xr ^= F(xl) ^ P[i];
+				xl ^= F(xr) ^ P[i + 1];
+			}
+
+			xr ^= P[ROUNDS + 1];
+
+			Pack.UInt32_To_BE(xr, output);
+			Pack.UInt32_To_BE(xl, output[4..]);
+		}
+
+		private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint xl = Pack.BE_To_UInt32(input);
+			uint xr = Pack.BE_To_UInt32(input[4..]);
+
+			xl ^= P[ROUNDS + 1];
+
+			for (int i = ROUNDS; i > 0; i -= 2)
+			{
+				xr ^= F(xl) ^ P[i];
+				xl ^= F(xr) ^ P[i - 1];
+			}
+
+			xr ^= P[0];
+
+			Pack.UInt32_To_BE(xr, output);
+			Pack.UInt32_To_BE(xl, output[4..]);
+		}
+#else
+		private void EncryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             uint xl = Pack.BE_To_UInt32(src, srcIndex);
             uint xr = Pack.BE_To_UInt32(src, srcIndex+4);
@@ -527,16 +577,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_BE(xl, dst, dstIndex + 4);
         }
 
-        /**
-        * Decrypt the given input starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        * The input will be an exact multiple of our blocksize.
-        */
-        private void DecryptBlock(
-            byte[] src,
-            int srcIndex,
-            byte[] dst,
-            int dstIndex)
+        private void DecryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             uint xl = Pack.BE_To_UInt32(src, srcIndex);
             uint xr = Pack.BE_To_UInt32(src, srcIndex + 4);
@@ -554,5 +595,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_BE(xr, dst, dstIndex);
             Pack.UInt32_To_BE(xl, dst, dstIndex + 4);
         }
-    }
+#endif
+	}
 }
diff --git a/crypto/src/crypto/engines/CamelliaEngine.cs b/crypto/src/crypto/engines/CamelliaEngine.cs
index 2222e4b7c..80d15ad99 100644
--- a/crypto/src/crypto/engines/CamelliaEngine.cs
+++ b/crypto/src/crypto/engines/CamelliaEngine.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
 {
@@ -275,25 +276,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			ki[3 + ioff] = ko[1 + ooff];
 		}
 
-		private static uint bytes2uint(byte[] src, int offset)
-		{
-			uint word = 0;
-			for (int i = 0; i < 4; i++)
-			{
-				word = (word << 8) + (uint)src[i + offset];
-			}
-			return word;
-		}
-
-		private static void uint2bytes(uint word, byte[] dst, int offset)
-		{
-			for (int i = 0; i < 4; i++)
-			{
-				dst[(3 - i) + offset] = (byte)word;
-				word >>= 8;
-			}
-		}
-
 		private static void camelliaF2(uint[] s, uint[] skey, int keyoff)
 		{
 			uint t1, t2, u, v;
@@ -346,38 +328,23 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			switch (key.Length)
 			{
-				case 16:
-					_keyIs128 = true;
-					k[0] = bytes2uint(key, 0);
-					k[1] = bytes2uint(key, 4);
-					k[2] = bytes2uint(key, 8);
-					k[3] = bytes2uint(key, 12);
-					k[4] = k[5] = k[6] = k[7] = 0;
-					break;
-				case 24:
-					k[0] = bytes2uint(key, 0);
-					k[1] = bytes2uint(key, 4);
-					k[2] = bytes2uint(key, 8);
-					k[3] = bytes2uint(key, 12);
-					k[4] = bytes2uint(key, 16);
-					k[5] = bytes2uint(key, 20);
-					k[6] = ~k[4];
-					k[7] = ~k[5];
-					_keyIs128 = false;
-					break;
-				case 32:
-					k[0] = bytes2uint(key, 0);
-					k[1] = bytes2uint(key, 4);
-					k[2] = bytes2uint(key, 8);
-					k[3] = bytes2uint(key, 12);
-					k[4] = bytes2uint(key, 16);
-					k[5] = bytes2uint(key, 20);
-					k[6] = bytes2uint(key, 24);
-					k[7] = bytes2uint(key, 28);
-					_keyIs128 = false;
-					break;
-				default:
-					throw new ArgumentException("key sizes are only 16/24/32 bytes.");
+			case 16:
+				_keyIs128 = true;
+				Pack.BE_To_UInt32(key, 0, k, 0, 4);
+				k[4] = k[5] = k[6] = k[7] = 0;
+				break;
+			case 24:
+				Pack.BE_To_UInt32(key, 0, k, 0, 6);
+				k[6] = ~k[4];
+				k[7] = ~k[5];
+				_keyIs128 = false;
+				break;
+			case 32:
+				Pack.BE_To_UInt32(key, 0, k, 0, 8);
+				_keyIs128 = false;
+				break;
+			default:
+				throw new ArgumentException("key sizes are only 16/24/32 bytes.");
 			}
 
 			for (int i = 0; i < 4; i++)
@@ -537,13 +504,78 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
-		private int processBlock128(byte[] input, int inOff, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private int ProcessBlock128(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint[] state = new uint[4];
+			Pack.BE_To_UInt32(input, state);
+
+			state[0] ^= kw[0];
+			state[1] ^= kw[1];
+			state[2] ^= kw[2];
+			state[3] ^= kw[3];
+
+			camelliaF2(state, subkey, 0);
+			camelliaF2(state, subkey, 4);
+			camelliaF2(state, subkey, 8);
+			camelliaFLs(state, ke, 0);
+			camelliaF2(state, subkey, 12);
+			camelliaF2(state, subkey, 16);
+			camelliaF2(state, subkey, 20);
+			camelliaFLs(state, ke, 4);
+			camelliaF2(state, subkey, 24);
+			camelliaF2(state, subkey, 28);
+			camelliaF2(state, subkey, 32);
+
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output[4..]);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output[8..]);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output[12..]);
+
+			return BLOCK_SIZE;
+		}
+
+		private int ProcessBlock192or256(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint[] state = new uint[4];
+			Pack.BE_To_UInt32(input, state);
+
+			state[0] ^= kw[0];
+			state[1] ^= kw[1];
+			state[2] ^= kw[2];
+			state[3] ^= kw[3];
+
+			camelliaF2(state, subkey, 0);
+			camelliaF2(state, subkey, 4);
+			camelliaF2(state, subkey, 8);
+			camelliaFLs(state, ke, 0);
+			camelliaF2(state, subkey, 12);
+			camelliaF2(state, subkey, 16);
+			camelliaF2(state, subkey, 20);
+			camelliaFLs(state, ke, 4);
+			camelliaF2(state, subkey, 24);
+			camelliaF2(state, subkey, 28);
+			camelliaF2(state, subkey, 32);
+			camelliaFLs(state, ke, 8);
+			camelliaF2(state, subkey, 36);
+			camelliaF2(state, subkey, 40);
+			camelliaF2(state, subkey, 44);
+
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output[4..]);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output[8..]);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output[12..]);
+
+			return BLOCK_SIZE;
+		}
+#else
+		private int ProcessBlock128(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			uint[] state = new uint[4];
 
 			for (int i = 0; i < 4; i++)
 			{
-				state[i] = bytes2uint(input, inOff + (i * 4)) ^ kw[i];
+				state[i] = Pack.BE_To_UInt32(input, inOff + (i * 4)) ^ kw[i];
 			}
 
 			camelliaF2(state, subkey, 0);
@@ -558,26 +590,21 @@ namespace Org.BouncyCastle.Crypto.Engines
 			camelliaF2(state, subkey, 28);
 			camelliaF2(state, subkey, 32);
 
-			state[2] ^= kw[4];
-			state[3] ^= kw[5];
-			state[0] ^= kw[6];
-			state[1] ^= kw[7];
-
-			uint2bytes(state[2], output, outOff);
-			uint2bytes(state[3], output, outOff + 4);
-			uint2bytes(state[0], output, outOff + 8);
-			uint2bytes(state[1], output, outOff + 12);
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output, outOff);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output, outOff + 4);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output, outOff + 8);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output, outOff + 12);
 
 			return BLOCK_SIZE;
 		}
 
-		private int processBlock192or256(byte[] input, int inOff, byte[] output, int outOff)
+		private int ProcessBlock192or256(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			uint[] state = new uint[4];
 
 			for (int i = 0; i < 4; i++)
 			{
-				state[i] = bytes2uint(input, inOff + (i * 4)) ^ kw[i];
+				state[i] = Pack.BE_To_UInt32(input, inOff + (i * 4)) ^ kw[i];
 			}
 
 			camelliaF2(state, subkey, 0);
@@ -596,18 +623,14 @@ namespace Org.BouncyCastle.Crypto.Engines
 			camelliaF2(state, subkey, 40);
 			camelliaF2(state, subkey, 44);
 
-			state[2] ^= kw[4];
-			state[3] ^= kw[5];
-			state[0] ^= kw[6];
-			state[1] ^= kw[7];
-
-			uint2bytes(state[2], output, outOff);
-			uint2bytes(state[3], output, outOff + 4);
-			uint2bytes(state[0], output, outOff + 8);
-			uint2bytes(state[1], output, outOff + 12);
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output, outOff);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output, outOff + 4);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output, outOff + 8);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output, outOff + 12);
 
 			return BLOCK_SIZE;
 		}
+#endif
 
 		public CamelliaEngine()
 		{
@@ -630,21 +653,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "Camellia"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return BLOCK_SIZE;
 		}
 
-        public virtual int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-			byte[]	output,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			if (!initialised)
 				throw new InvalidOperationException("Camellia engine not initialised");
@@ -652,19 +666,45 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
-            if (_keyIs128)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			if (_keyIs128)
+			{
+				return ProcessBlock128(input.AsSpan(inOff), output.AsSpan(outOff));
+			}
+			else
 			{
-				return processBlock128(input, inOff, output, outOff);
+				return ProcessBlock192or256(input.AsSpan(inOff), output.AsSpan(outOff));
+			}
+#else
+			if (_keyIs128)
+			{
+				return ProcessBlock128(input, inOff, output, outOff);
 			}
 			else
 			{
-				return processBlock192or256(input, inOff, output, outOff);
+				return ProcessBlock192or256(input, inOff, output, outOff);
 			}
+#endif
 		}
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
-			// nothing
+			if (!initialised)
+				throw new InvalidOperationException("Camellia engine not initialised");
+
+			Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+			Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+			if (_keyIs128)
+			{
+				return ProcessBlock128(input, output);
+			}
+			else
+			{
+				return ProcessBlock192or256(input, output);
+			}
 		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/engines/CamelliaLightEngine.cs b/crypto/src/crypto/engines/CamelliaLightEngine.cs
index daf0316e2..3111a8ddf 100644
--- a/crypto/src/crypto/engines/CamelliaLightEngine.cs
+++ b/crypto/src/crypto/engines/CamelliaLightEngine.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
 {
@@ -158,25 +159,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			ki[3 + ioff] = ko[1 + ooff];
 		}
 
-		private static uint bytes2uint(byte[] src, int offset)
-		{
-			uint word = 0;
-			for (int i = 0; i < 4; i++)
-			{
-				word = (word << 8) + (uint)src[i + offset];
-			}
-			return word;
-		}
-
-		private static void uint2bytes(uint word, byte[] dst, int offset)
-		{
-			for (int i = 0; i < 4; i++)
-			{
-				dst[(3 - i) + offset] = (byte)word;
-				word >>= 8;
-			}
-		}
-
 		private byte lRot8(byte v, int rot)
 		{
 			return (byte)(((uint)v << rot) | ((uint)v >> (8 - rot)));
@@ -258,38 +240,23 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			switch (key.Length)
 			{
-				case 16:
-					_keyis128 = true;
-					k[0] = bytes2uint(key, 0);
-					k[1] = bytes2uint(key, 4);
-					k[2] = bytes2uint(key, 8);
-					k[3] = bytes2uint(key, 12);
-					k[4] = k[5] = k[6] = k[7] = 0;
-					break;
-				case 24:
-					k[0] = bytes2uint(key, 0);
-					k[1] = bytes2uint(key, 4);
-					k[2] = bytes2uint(key, 8);
-					k[3] = bytes2uint(key, 12);
-					k[4] = bytes2uint(key, 16);
-					k[5] = bytes2uint(key, 20);
-					k[6] = ~k[4];
-					k[7] = ~k[5];
-					_keyis128 = false;
-					break;
-				case 32:
-					k[0] = bytes2uint(key, 0);
-					k[1] = bytes2uint(key, 4);
-					k[2] = bytes2uint(key, 8);
-					k[3] = bytes2uint(key, 12);
-					k[4] = bytes2uint(key, 16);
-					k[5] = bytes2uint(key, 20);
-					k[6] = bytes2uint(key, 24);
-					k[7] = bytes2uint(key, 28);
-					_keyis128 = false;
-					break;
-				default:
-					throw new ArgumentException("key sizes are only 16/24/32 bytes.");
+			case 16:
+				_keyis128 = true;
+				Pack.BE_To_UInt32(key, 0, k, 0, 4);
+				k[4] = k[5] = k[6] = k[7] = 0;
+				break;
+			case 24:
+				Pack.BE_To_UInt32(key, 0, k, 0, 6);
+				k[6] = ~k[4];
+				k[7] = ~k[5];
+				_keyis128 = false;
+				break;
+			case 32:
+				Pack.BE_To_UInt32(key, 0, k, 0, 8);
+				_keyis128 = false;
+				break;
+			default:
+				throw new ArgumentException("key sizes are only 16/24/32 bytes.");
 			}
 
 			for (int i = 0; i < 4; i++)
@@ -449,13 +416,78 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
-		private int processBlock128(byte[] input, int inOff, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private int ProcessBlock128(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint[] state = new uint[4];
+			Pack.BE_To_UInt32(input, state);
+
+			state[0] ^= kw[0];
+			state[1] ^= kw[1];
+			state[2] ^= kw[2];
+			state[3] ^= kw[3];
+
+			camelliaF2(state, subkey, 0);
+			camelliaF2(state, subkey, 4);
+			camelliaF2(state, subkey, 8);
+			camelliaFLs(state, ke, 0);
+			camelliaF2(state, subkey, 12);
+			camelliaF2(state, subkey, 16);
+			camelliaF2(state, subkey, 20);
+			camelliaFLs(state, ke, 4);
+			camelliaF2(state, subkey, 24);
+			camelliaF2(state, subkey, 28);
+			camelliaF2(state, subkey, 32);
+
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output[4..]);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output[8..]);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output[12..]);
+
+			return BLOCK_SIZE;
+		}
+
+		private int ProcessBlock192or256(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint[] state = new uint[4];
+			Pack.BE_To_UInt32(input, state);
+
+			state[0] ^= kw[0];
+			state[1] ^= kw[1];
+			state[2] ^= kw[2];
+			state[3] ^= kw[3];
+
+			camelliaF2(state, subkey, 0);
+			camelliaF2(state, subkey, 4);
+			camelliaF2(state, subkey, 8);
+			camelliaFLs(state, ke, 0);
+			camelliaF2(state, subkey, 12);
+			camelliaF2(state, subkey, 16);
+			camelliaF2(state, subkey, 20);
+			camelliaFLs(state, ke, 4);
+			camelliaF2(state, subkey, 24);
+			camelliaF2(state, subkey, 28);
+			camelliaF2(state, subkey, 32);
+			camelliaFLs(state, ke, 8);
+			camelliaF2(state, subkey, 36);
+			camelliaF2(state, subkey, 40);
+			camelliaF2(state, subkey, 44);
+
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output[4..]);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output[8..]);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output[12..]);
+
+			return BLOCK_SIZE;
+		}
+#else
+		private int ProcessBlock128(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			uint[] state = new uint[4];
 
 			for (int i = 0; i < 4; i++)
 			{
-				state[i] = bytes2uint(input, inOff + (i * 4)) ^ kw[i];
+				state[i] = Pack.BE_To_UInt32(input, inOff + (i * 4)) ^ kw[i];
 			}
 
 			camelliaF2(state, subkey, 0);
@@ -470,26 +502,21 @@ namespace Org.BouncyCastle.Crypto.Engines
 			camelliaF2(state, subkey, 28);
 			camelliaF2(state, subkey, 32);
 
-			state[2] ^= kw[4];
-			state[3] ^= kw[5];
-			state[0] ^= kw[6];
-			state[1] ^= kw[7];
-
-			uint2bytes(state[2], output, outOff);
-			uint2bytes(state[3], output, outOff + 4);
-			uint2bytes(state[0], output, outOff + 8);
-			uint2bytes(state[1], output, outOff + 12);
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output, outOff);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output, outOff + 4);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output, outOff + 8);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output, outOff + 12);
 
 			return BLOCK_SIZE;
 		}
 
-		private int processBlock192or256(byte[] input, int inOff, byte[] output, int outOff)
+		private int ProcessBlock192or256(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			uint[] state = new uint[4];
 
 			for (int i = 0; i < 4; i++)
 			{
-				state[i] = bytes2uint(input, inOff + (i * 4)) ^ kw[i];
+				state[i] = Pack.BE_To_UInt32(input, inOff + (i * 4)) ^ kw[i];
 			}
 
 			camelliaF2(state, subkey, 0);
@@ -508,18 +535,14 @@ namespace Org.BouncyCastle.Crypto.Engines
 			camelliaF2(state, subkey, 40);
 			camelliaF2(state, subkey, 44);
 
-			state[2] ^= kw[4];
-			state[3] ^= kw[5];
-			state[0] ^= kw[6];
-			state[1] ^= kw[7];
-
-			uint2bytes(state[2], output, outOff);
-			uint2bytes(state[3], output, outOff + 4);
-			uint2bytes(state[0], output, outOff + 8);
-			uint2bytes(state[1], output, outOff + 12);
+			Pack.UInt32_To_BE(state[2] ^ kw[4], output, outOff);
+			Pack.UInt32_To_BE(state[3] ^ kw[5], output, outOff + 4);
+			Pack.UInt32_To_BE(state[0] ^ kw[6], output, outOff + 8);
+			Pack.UInt32_To_BE(state[1] ^ kw[7], output, outOff + 12);
 
 			return BLOCK_SIZE;
 		}
+#endif
 
 		public CamelliaLightEngine()
 		{
@@ -531,11 +554,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "Camellia"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return BLOCK_SIZE;
@@ -553,11 +571,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 			initialised = true;
 		}
 
-        public virtual int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-            byte[]	output,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[]	output, int outOff)
 		{
 			if (!initialised)
 				throw new InvalidOperationException("Camellia engine not initialised");
@@ -565,18 +579,45 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
-            if (_keyis128)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			if (_keyis128)
+			{
+				return ProcessBlock128(input.AsSpan(inOff), output.AsSpan(outOff));
+			}
+			else
 			{
-				return processBlock128(input, inOff, output, outOff);
+				return ProcessBlock192or256(input.AsSpan(inOff), output.AsSpan(outOff));
+			}
+#else
+			if (_keyis128)
+			{
+				return ProcessBlock128(input, inOff, output, outOff);
 			}
 			else
 			{
-				return processBlock192or256(input, inOff, output, outOff);
+				return ProcessBlock192or256(input, inOff, output, outOff);
 			}
+#endif
 		}
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
+			if (!initialised)
+				throw new InvalidOperationException("Camellia engine not initialised");
+
+			Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+			Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+			if (_keyis128)
+			{
+				return ProcessBlock128(input, output);
+			}
+			else
+			{
+				return ProcessBlock192or256(input, output);
+			}
 		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/engines/Cast5Engine.cs b/crypto/src/crypto/engines/Cast5Engine.cs
index 398f6d43a..c53c8909a 100644
--- a/crypto/src/crypto/engines/Cast5Engine.cs
+++ b/crypto/src/crypto/engines/Cast5Engine.cs
@@ -347,24 +347,25 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "CAST5"; }
         }
 
-		public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
-		public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+		public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-			int blockSize = GetBlockSize();
             if (_workingKey == null)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
 
+            int blockSize = GetBlockSize();
             Check.DataLength(input, inOff, blockSize, "input buffer too short");
             Check.OutputLength(output, outOff, blockSize, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (_encrypting)
+            {
+                return EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+            else
+            {
+                return DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+#else
             if (_encrypting)
             {
                 return EncryptBlock(input, inOff, output, outOff);
@@ -373,11 +374,29 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 return DecryptBlock(input, inOff, output, outOff);
             }
+#endif
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (_workingKey == null)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            int blockSize = GetBlockSize();
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            if (_encrypting)
+            {
+                return EncryptBlock(input, output);
+            }
+            else
+            {
+                return DecryptBlock(input, output);
+            }
         }
+#endif
 
         public virtual int GetBlockSize()
         {
@@ -566,20 +585,45 @@ namespace Org.BouncyCastle.Crypto.Engines
             _Kr[16]=(int)((S5[x[0xE]]^S6[x[0xF]]^S7[x[0x1]]^S8[x[0x0]]^S8[x[0xD]])&0x1f);
         }
 
-        /**
-        * Encrypt the given input starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        *
-        * @param src        The plaintext buffer
-        * @param srcIndex    An offset into src
-        * @param dst        The ciphertext buffer
-        * @param dstIndex    An offset into dst
-        */
-        internal virtual int EncryptBlock(
-            byte[] src,
-            int srcIndex,
-            byte[] dst,
-            int dstIndex)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal virtual int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            // process the input block
+            // batch the units up into a 32 bit chunk and go for it
+            // the array is in bytes, the increment is 8x8 bits = 64
+
+            uint L0 = Pack.BE_To_UInt32(input);
+            uint R0 = Pack.BE_To_UInt32(input[4..]);
+
+            uint[] result = new uint[2];
+            CAST_Encipher(L0, R0, result);
+
+            // now stuff them into the destination block
+            Pack.UInt32_To_BE(result[0], output);
+            Pack.UInt32_To_BE(result[1], output[4..]);
+
+            return BLOCK_SIZE;
+        }
+
+        internal virtual int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            // process the input block
+            // batch the units up into a 32 bit chunk and go for it
+            // the array is in bytes, the increment is 8x8 bits = 64
+            uint L16 = Pack.BE_To_UInt32(input);
+            uint R16 = Pack.BE_To_UInt32(input[4..]);
+
+            uint[] result = new uint[2];
+            CAST_Decipher(L16, R16, result);
+
+            // now stuff them into the destination block
+            Pack.UInt32_To_BE(result[0], output);
+            Pack.UInt32_To_BE(result[1], output[4..]);
+
+            return BLOCK_SIZE;
+        }
+#else
+        internal virtual int EncryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             // process the input block
             // batch the units up into a 32 bit chunk and go for it
@@ -598,20 +642,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             return BLOCK_SIZE;
         }
 
-        /**
-        * Decrypt the given input starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        *
-        * @param src        The plaintext buffer
-        * @param srcIndex    An offset into src
-        * @param dst        The ciphertext buffer
-        * @param dstIndex    An offset into dst
-        */
-        internal virtual int DecryptBlock(
-            byte[] src,
-            int srcIndex,
-            byte[] dst,
-            int dstIndex)
+        internal virtual int DecryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             // process the input block
             // batch the units up into a 32 bit chunk and go for it
@@ -628,6 +659,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             return BLOCK_SIZE;
         }
+#endif
 
         /**
         * The first of the three processing functions for the
@@ -702,28 +734,28 @@ namespace Org.BouncyCastle.Crypto.Engines
                 Li = Rp;
                 switch (i)
                 {
-                    case  1:
-                    case  4:
-                    case  7:
-                    case 10:
-                    case 13:
-                    case 16:
-                        Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]);
-                        break;
-                    case  2:
-                    case  5:
-                    case  8:
-                    case 11:
-                    case 14:
-                        Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]);
-                        break;
-                    case  3:
-                    case  6:
-                    case  9:
-                    case 12:
-                    case 15:
-                        Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]);
-                        break;
+                case  1:
+                case  4:
+                case  7:
+                case 10:
+                case 13:
+                case 16:
+                    Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]);
+                    break;
+                case  2:
+                case  5:
+                case  8:
+                case 11:
+                case 14:
+                    Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]);
+                    break;
+                case  3:
+                case  6:
+                case  9:
+                case 12:
+                case 15:
+                    Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]);
+                    break;
                 }
             }
 
@@ -752,28 +784,28 @@ namespace Org.BouncyCastle.Crypto.Engines
                 Li = Rp;
                 switch (i)
                 {
-                    case  1:
-                    case  4:
-                    case  7:
-                    case 10:
-                    case 13:
-                    case 16:
-                        Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]);
-                        break;
-                    case  2:
-                    case  5:
-                    case  8:
-                    case 11:
-                    case 14:
-                        Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]);
-                        break;
-                    case  3:
-                    case  6:
-                    case  9:
-                    case 12:
-                    case 15:
-                        Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]);
-                        break;
+                case  1:
+                case  4:
+                case  7:
+                case 10:
+                case 13:
+                case 16:
+                    Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]);
+                    break;
+                case  2:
+                case  5:
+                case  8:
+                case 11:
+                case 14:
+                    Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]);
+                    break;
+                case  3:
+                case  6:
+                case  9:
+                case 12:
+                case 15:
+                    Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]);
+                    break;
                 }
             }
 
diff --git a/crypto/src/crypto/engines/Cast6Engine.cs b/crypto/src/crypto/engines/Cast6Engine.cs
index c5c419b78..b73398191 100644
--- a/crypto/src/crypto/engines/Cast6Engine.cs
+++ b/crypto/src/crypto/engines/Cast6Engine.cs
@@ -46,10 +46,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "CAST6"; }
         }
 
-		public override void Reset()
-        {
-        }
-
 		public override int GetBlockSize()
         {
             return BLOCK_SIZE;
@@ -134,20 +130,44 @@ namespace Org.BouncyCastle.Crypto.Engines
             }
         }
 
-		/**
-        * Encrypt the given input starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        *
-        * @param src        The plaintext buffer
-        * @param srcIndex    An offset into src
-        * @param dst        The ciphertext buffer
-        * @param dstIndex    An offset into dst
-        */
-        internal override int EncryptBlock(
-            byte[]	src,
-            int		srcIndex,
-            byte[]	dst,
-            int		dstIndex)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            // process the input block
+            // batch the units up into 4x32 bit chunks and go for it
+            uint A = Pack.BE_To_UInt32(input);
+            uint B = Pack.BE_To_UInt32(input[4..]);
+            uint C = Pack.BE_To_UInt32(input[8..]);
+            uint D = Pack.BE_To_UInt32(input[12..]);
+            uint[] result = new uint[4];
+            CAST_Encipher(A, B, C, D, result);
+            // now stuff them into the destination block
+            Pack.UInt32_To_BE(result[0], output);
+            Pack.UInt32_To_BE(result[1], output[4..]);
+            Pack.UInt32_To_BE(result[2], output[8..]);
+            Pack.UInt32_To_BE(result[3], output[12..]);
+            return BLOCK_SIZE;
+        }
+
+        internal override int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            // process the input block
+            // batch the units up into 4x32 bit chunks and go for it
+            uint A = Pack.BE_To_UInt32(input);
+            uint B = Pack.BE_To_UInt32(input[4..]);
+            uint C = Pack.BE_To_UInt32(input[8..]);
+            uint D = Pack.BE_To_UInt32(input[12..]);
+            uint[] result = new uint[4];
+            CAST_Decipher(A, B, C, D, result);
+            // now stuff them into the destination block
+            Pack.UInt32_To_BE(result[0], output);
+            Pack.UInt32_To_BE(result[1], output[4..]);
+            Pack.UInt32_To_BE(result[2], output[8..]);
+            Pack.UInt32_To_BE(result[3], output[12..]);
+            return BLOCK_SIZE;
+        }
+#else
+        internal override int EncryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             // process the input block
             // batch the units up into 4x32 bit chunks and go for it
@@ -165,20 +185,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             return BLOCK_SIZE;
         }
 
-		/**
-        * Decrypt the given input starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        *
-        * @param src        The plaintext buffer
-        * @param srcIndex    An offset into src
-        * @param dst        The ciphertext buffer
-        * @param dstIndex    An offset into dst
-        */
-        internal override int DecryptBlock(
-            byte[]	src,
-            int		srcIndex,
-            byte[]	dst,
-            int		dstIndex)
+        internal override int DecryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             // process the input block
             // batch the units up into 4x32 bit chunks and go for it
@@ -195,8 +202,9 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_BE(result[3], dst, dstIndex + 12);
             return BLOCK_SIZE;
         }
+#endif
 
-		/**
+        /**
         * Does the 12 quad rounds rounds to encrypt the block.
         *
         * @param A    the 00-31  bits of the plaintext block
diff --git a/crypto/src/crypto/engines/ChaCha7539Engine.cs b/crypto/src/crypto/engines/ChaCha7539Engine.cs
index d1dd9755b..d9cdac541 100644
--- a/crypto/src/crypto/engines/ChaCha7539Engine.cs
+++ b/crypto/src/crypto/engines/ChaCha7539Engine.cs
@@ -4,6 +4,8 @@ using System.Diagnostics;
 using System.Runtime.CompilerServices;
 #endif
 #if NETCOREAPP3_0_OR_GREATER
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
 using System.Runtime.Intrinsics;
 using System.Runtime.Intrinsics.X86;
 #endif
@@ -81,16 +83,24 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			while (inLen >= 128)
             {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                ProcessBlocks2(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
+#else
 				ProcessBlocks2(inBuf, inOff, outBuf, outOff);
-				inOff += 128;
+#endif
+                inOff += 128;
 				inLen -= 128;
 				outOff += 128;
 			}
 
 			if (inLen >= 64)
 			{
-				ImplProcessBlock(inBuf, inOff, outBuf, outOff);
-				inOff += 64;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                ImplProcessBlock(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
+#else
+                ImplProcessBlock(inBuf, inOff, outBuf, outOff);
+#endif
+                inOff += 64;
 				inLen -= 64;
 				outOff += 64;
 			}
@@ -111,7 +121,8 @@ namespace Org.BouncyCastle.Crypto.Engines
 			// TODO Prevent re-use if encrypting
 		}
 
-		internal void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
             if (!initialised)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -120,10 +131,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             Debug.Assert(index == 0);
 
-			ImplProcessBlock(inBytes, inOff, outBytes, outOff);
+            ImplProcessBlock(input, output);
         }
 
-        internal void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        internal void ProcessBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
         {
             if (!initialised)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -135,18 +146,57 @@ namespace Org.BouncyCastle.Crypto.Engines
 #if NETCOREAPP3_0_OR_GREATER
             if (Avx2.IsSupported)
             {
-                ImplProcessBlocks2_X86_Avx2(rounds, engineState, inBytes.AsSpan(inOff), outBytes.AsSpan(outOff));
+                ImplProcessBlocks2_X86_Avx2(rounds, engineState, input, output);
                 return;
             }
 
             if (Sse2.IsSupported)
             {
-                ImplProcessBlocks2_X86_Sse2(rounds, engineState, inBytes.AsSpan(inOff), outBytes.AsSpan(outOff));
+                ImplProcessBlocks2_X86_Sse2(rounds, engineState, input, output);
                 return;
             }
 #endif
 
             {
+				ImplProcessBlock(input, output);
+				ImplProcessBlock(input[64..], output[64..]);
+			}
+		}
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal void ImplProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            ChaChaEngine.ChachaCore(rounds, engineState, keyStream);
+            AdvanceCounter();
+
+            for (int i = 0; i < 64; ++i)
+            {
+                output[i] = (byte)(keyStream[i] ^ input[i]);
+            }
+        }
+#else
+		internal void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+            if (LimitExceeded(64U))
+                throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
+
+            Debug.Assert(index == 0);
+
+			ImplProcessBlock(inBytes, inOff, outBytes, outOff);
+        }
+
+        internal void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+            if (LimitExceeded(128U))
+                throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
+
+            Debug.Assert(index == 0);
+
+            {
 				ImplProcessBlock(inBytes, inOff, outBytes, outOff);
 				ImplProcessBlock(inBytes, inOff + 64, outBytes, outOff + 64);
 			}
@@ -165,6 +215,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 				outBuf[outOff + i] = (byte)(keyStream[i] ^ inBuf[inOff + i]);
 			}
 		}
+#endif
 
 #if NETCOREAPP3_0_OR_GREATER
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -249,10 +300,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 			n2 = Avx2.Xor(n2, Load256_Byte(input[0x40..]));
 			n3 = Avx2.Xor(n3, Load256_Byte(input[0x60..]));
 
-			Store256_Byte(ref n0, output);
-			Store256_Byte(ref n1, output[0x20..]);
-			Store256_Byte(ref n2, output[0x40..]);
-			Store256_Byte(ref n3, output[0x60..]);
+			Store256_Byte(n0, output);
+			Store256_Byte(n1, output[0x20..]);
+			Store256_Byte(n2, output[0x40..]);
+			Store256_Byte(n3, output[0x60..]);
 		}
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -330,10 +381,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 			n2 = Sse2.Xor(n2, v2.AsByte());
 			n3 = Sse2.Xor(n3, v3.AsByte());
 
-			Store128_Byte(ref n0, output);
-			Store128_Byte(ref n1, output[0x10..]);
-			Store128_Byte(ref n2, output[0x20..]);
-			Store128_Byte(ref n3, output[0x30..]);
+			Store128_Byte(n0, output);
+			Store128_Byte(n1, output[0x10..]);
+			Store128_Byte(n2, output[0x20..]);
+			Store128_Byte(n3, output[0x30..]);
 
 			x3 = Load128_UInt32(state.AsSpan(12));
 			++state[12];
@@ -395,27 +446,29 @@ namespace Org.BouncyCastle.Crypto.Engines
 			n2 = Sse2.Xor(n2, v2.AsByte());
 			n3 = Sse2.Xor(n3, v3.AsByte());
 
-			Store128_Byte(ref n0, output[0x40..]);
-			Store128_Byte(ref n1, output[0x50..]);
-			Store128_Byte(ref n2, output[0x60..]);
-			Store128_Byte(ref n3, output[0x70..]);
+			Store128_Byte(n0, output[0x40..]);
+			Store128_Byte(n1, output[0x50..]);
+			Store128_Byte(n2, output[0x60..]);
+			Store128_Byte(n3, output[0x70..]);
 		}
 
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		private static Vector128<byte> Load128_Byte(ReadOnlySpan<byte> t)
 		{
-			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
-				return Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(t[0]));
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+                return MemoryMarshal.Read<Vector128<byte>>(t);
 
-			return Vector128.Create(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12],
-				t[13], t[14], t[15]);
-		}
+            return Vector128.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[..8]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[8..])
+            ).AsByte();
+        }
 
-		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 		private static Vector128<uint> Load128_UInt32(ReadOnlySpan<uint> t)
 		{
 			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
-				return Unsafe.ReadUnaligned<Vector128<uint>>(ref Unsafe.As<uint, byte>(ref Unsafe.AsRef(t[0])));
+                return MemoryMarshal.Read<Vector128<uint>>(MemoryMarshal.AsBytes(t));
 
 			return Vector128.Create(t[0], t[1], t[2], t[3]);
 		}
@@ -423,42 +476,45 @@ namespace Org.BouncyCastle.Crypto.Engines
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		private static Vector256<byte> Load256_Byte(ReadOnlySpan<byte> t)
         {
-			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector256<byte>>() == 32)
-				return Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AsRef(t[0]));
-
-			return Vector256.Create(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12],
-				t[13], t[14], t[15], t[16], t[17], t[18], t[19], t[20], t[21], t[22], t[23], t[24], t[25], t[26], t[27],
-				t[28], t[29], t[30], t[31]);
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector256<byte>>() == 32)
+                return MemoryMarshal.Read<Vector256<byte>>(t);
+
+            return Vector256.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[ 0.. 8]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[ 8..16]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[16..24]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[24..32])
+            ).AsByte();
 		}
 
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
-		private static void Store128_Byte(ref Vector128<byte> s, Span<byte> t)
+		private static void Store128_Byte(Vector128<byte> s, Span<byte> t)
 		{
-			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
-			{
-				Unsafe.WriteUnaligned(ref t[0], s);
-				return;
-			}
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                MemoryMarshal.Write(t, ref s);
+                return;
+            }
 
-			var u = s.AsUInt64();
-			Pack.UInt64_To_LE(u.GetElement(0), t);
-			Pack.UInt64_To_LE(u.GetElement(1), t[8..]);
+            var u = s.AsUInt64();
+            BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
 		}
 
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
-		private static void Store256_Byte(ref Vector256<byte> s, Span<byte> t)
+		private static void Store256_Byte(Vector256<byte> s, Span<byte> t)
 		{
-			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector256<byte>>() == 32)
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector256<byte>>() == 32)
 			{
-				Unsafe.WriteUnaligned(ref t[0], s);
+                MemoryMarshal.Write(t, ref s);
 				return;
 			}
 
 			var u = s.AsUInt64();
-			Pack.UInt64_To_LE(u.GetElement(0), t);
-			Pack.UInt64_To_LE(u.GetElement(1), t[8..]);
-			Pack.UInt64_To_LE(u.GetElement(2), t[16..]);
-			Pack.UInt64_To_LE(u.GetElement(3), t[24..]);
+            BinaryPrimitives.WriteUInt64LittleEndian(t[ 0.. 8], u.GetElement(0));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[ 8..16], u.GetElement(1));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[16..24], u.GetElement(2));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[24..32], u.GetElement(3));
 		}
 #endif
 	}
diff --git a/crypto/src/crypto/engines/ChaChaEngine.cs b/crypto/src/crypto/engines/ChaChaEngine.cs
index 646a6976c..093e1a250 100644
--- a/crypto/src/crypto/engines/ChaChaEngine.cs
+++ b/crypto/src/crypto/engines/ChaChaEngine.cs
@@ -1,7 +1,9 @@
 using System;
 using System.Diagnostics;
 #if NETCOREAPP3_0_OR_GREATER
+using System.Buffers.Binary;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using System.Runtime.Intrinsics;
 using System.Runtime.Intrinsics.X86;
 #endif
@@ -135,10 +137,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 				v2 = Sse2.Add(v2, x2);
 				v3 = Sse2.Add(v3, x3);
 
-				Store128_UInt32(ref v0, output.AsSpan());
-				Store128_UInt32(ref v1, output.AsSpan(0x10));
-				Store128_UInt32(ref v2, output.AsSpan(0x20));
-				Store128_UInt32(ref v3, output.AsSpan(0x30));
+				Store128_UInt32(v0, output.AsSpan());
+				Store128_UInt32(v1, output.AsSpan(0x10));
+				Store128_UInt32(v2, output.AsSpan(0x20));
+				Store128_UInt32(v3, output.AsSpan(0x30));
 				return;
 			}
 #endif
@@ -215,24 +217,24 @@ namespace Org.BouncyCastle.Crypto.Engines
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		private static Vector128<uint> Load128_UInt32(ReadOnlySpan<uint> t)
 		{
-			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
-				return Unsafe.ReadUnaligned<Vector128<uint>>(ref Unsafe.As<uint, byte>(ref Unsafe.AsRef(t[0])));
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
+                return MemoryMarshal.Read<Vector128<uint>>(MemoryMarshal.AsBytes(t));
 
-			return Vector128.Create(t[0], t[1], t[2], t[3]);
+            return Vector128.Create(t[0], t[1], t[2], t[3]);
 		}
 
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
-		private static void Store128_UInt32(ref Vector128<uint> s, Span<byte> t)
+		private static void Store128_UInt32(Vector128<uint> s, Span<byte> t)
 		{
 			if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16)
 			{
-				Unsafe.WriteUnaligned(ref t[0], s);
+				MemoryMarshal.Write(t, ref s);
 				return;
 			}
 
 			var u = s.AsUInt64();
-			Pack.UInt64_To_LE(u.GetElement(0), t);
-			Pack.UInt64_To_LE(u.GetElement(1), t[8..]);
+            BinaryPrimitives.WriteUInt64LittleEndian(t[..8], u.GetElement(0));
+            BinaryPrimitives.WriteUInt64LittleEndian(t[8..], u.GetElement(1));
 		}
 #endif
 	}
diff --git a/crypto/src/crypto/engines/DesEdeEngine.cs b/crypto/src/crypto/engines/DesEdeEngine.cs
index 2fac24ac0..a9185d295 100644
--- a/crypto/src/crypto/engines/DesEdeEngine.cs
+++ b/crypto/src/crypto/engines/DesEdeEngine.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
@@ -63,11 +64,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             return BLOCK_SIZE;
         }
 
-        public override int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public override int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             if (workingKey1 == null)
                 throw new InvalidOperationException("DESede engine not initialised");
@@ -75,26 +72,58 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
-            byte[] temp = new byte[BLOCK_SIZE];
+            uint hi32 = Pack.BE_To_UInt32(input, inOff);
+            uint lo32 = Pack.BE_To_UInt32(input, inOff + 4);
 
             if (forEncryption)
             {
-                DesFunc(workingKey1, input, inOff, temp, 0);
-                DesFunc(workingKey2, temp, 0, temp, 0);
-                DesFunc(workingKey3, temp, 0, output, outOff);
+                DesFunc(workingKey1, ref hi32, ref lo32);
+                DesFunc(workingKey2, ref hi32, ref lo32);
+                DesFunc(workingKey3, ref hi32, ref lo32);
             }
             else
             {
-                DesFunc(workingKey3, input, inOff, temp, 0);
-                DesFunc(workingKey2, temp, 0, temp, 0);
-                DesFunc(workingKey1, temp, 0, output, outOff);
+                DesFunc(workingKey3, ref hi32, ref lo32);
+                DesFunc(workingKey2, ref hi32, ref lo32);
+                DesFunc(workingKey1, ref hi32, ref lo32);
             }
 
+            Pack.UInt32_To_BE(hi32, output, outOff);
+            Pack.UInt32_To_BE(lo32, output, outOff + 4);
+
             return BLOCK_SIZE;
         }
 
-        public override void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (workingKey1 == null)
+                throw new InvalidOperationException("DESede engine not initialised");
+
+            Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+            Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+            uint hi32 = Pack.BE_To_UInt32(input);
+            uint lo32 = Pack.BE_To_UInt32(input[4..]);
+
+            if (forEncryption)
+            {
+                DesFunc(workingKey1, ref hi32, ref lo32);
+                DesFunc(workingKey2, ref hi32, ref lo32);
+                DesFunc(workingKey3, ref hi32, ref lo32);
+            }
+            else
+            {
+                DesFunc(workingKey3, ref hi32, ref lo32);
+                DesFunc(workingKey2, ref hi32, ref lo32);
+                DesFunc(workingKey1, ref hi32, ref lo32);
+            }
+
+            Pack.UInt32_To_BE(hi32, output);
+            Pack.UInt32_To_BE(lo32, output[4..]);
+
+            return BLOCK_SIZE;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/DesEdeWrapEngine.cs b/crypto/src/crypto/engines/DesEdeWrapEngine.cs
index 43100a9bd..e05f9f555 100644
--- a/crypto/src/crypto/engines/DesEdeWrapEngine.cs
+++ b/crypto/src/crypto/engines/DesEdeWrapEngine.cs
@@ -60,15 +60,14 @@ namespace Org.BouncyCastle.Crypto.Engines
             this.engine = new CbcBlockCipher(new DesEdeEngine());
 
 			SecureRandom sr;
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom pr)
 			{
-				ParametersWithRandom pr = (ParametersWithRandom) parameters;
 				parameters = pr.Parameters;
 				sr = pr.Random;
 			}
 			else
 			{
-				sr = new SecureRandom();
+				sr = CryptoServicesRegistrar.GetSecureRandom();
 			}
 
 			if (parameters is KeyParameter)
diff --git a/crypto/src/crypto/engines/DesEngine.cs b/crypto/src/crypto/engines/DesEngine.cs
index cfd50681e..084fa1049 100644
--- a/crypto/src/crypto/engines/DesEngine.cs
+++ b/crypto/src/crypto/engines/DesEngine.cs
@@ -31,10 +31,10 @@ namespace Org.BouncyCastle.Crypto.Engines
             bool				forEncryption,
             ICipherParameters	parameters)
         {
-            if (!(parameters is KeyParameter))
+            if (!(parameters is KeyParameter keyParameter))
 				throw new ArgumentException("invalid parameter passed to DES init - " + Platform.GetTypeName(parameters));
 
-			workingKey = GenerateWorkingKey(forEncryption, ((KeyParameter)parameters).GetKey());
+			workingKey = GenerateWorkingKey(forEncryption, keyParameter.GetKey());
         }
 
 		public virtual string AlgorithmName
@@ -42,21 +42,12 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "DES"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
 		public virtual int GetBlockSize()
         {
             return BLOCK_SIZE;
         }
 
-        public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[]	output, int outOff)
         {
             if (workingKey == null)
                 throw new InvalidOperationException("DES engine not initialised");
@@ -64,14 +55,37 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
-            DesFunc(workingKey, input, inOff, output, outOff);
+            uint hi32 = Pack.BE_To_UInt32(input, inOff);
+            uint lo32 = Pack.BE_To_UInt32(input, inOff + 4);
+
+            DesFunc(workingKey, ref hi32, ref lo32);
+
+            Pack.UInt32_To_BE(hi32, output, outOff);
+            Pack.UInt32_To_BE(lo32, output, outOff + 4);
 
 			return BLOCK_SIZE;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (workingKey == null)
+                throw new InvalidOperationException("DES engine not initialised");
+
+            Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+            Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+            uint hi32 = Pack.BE_To_UInt32(input);
+            uint lo32 = Pack.BE_To_UInt32(input[4..]);
+
+            DesFunc(workingKey, ref hi32, ref lo32);
+
+            Pack.UInt32_To_BE(hi32, output);
+            Pack.UInt32_To_BE(lo32, output[4..]);
+
+            return BLOCK_SIZE;
         }
+#endif
 
         /**
         * what follows is mainly taken from "Applied Cryptography", by
@@ -388,19 +402,11 @@ namespace Org.BouncyCastle.Crypto.Engines
             return newKey;
         }
 
-        /**
-        * the DES engine.
-        */
-        internal static void DesFunc(
-            int[]	wKey,
-            byte[]	input,
-            int		inOff,
-            byte[]	outBytes,
-            int		outOff)
+        internal static void DesFunc(int[] wKey, ref uint hi32, ref uint lo32)
         {
-			uint left = Pack.BE_To_UInt32(input, inOff);
-			uint right = Pack.BE_To_UInt32(input, inOff + 4);
-			uint work;
+            uint left = hi32;
+            uint right = lo32;
+            uint work;
 
             work = ((left >> 4) ^ right) & 0x0f0f0f0f;
             right ^= work;
@@ -468,8 +474,8 @@ namespace Org.BouncyCastle.Crypto.Engines
             left ^= work;
             right ^= (work << 4);
 
-			Pack.UInt32_To_BE(right, outBytes, outOff);
-			Pack.UInt32_To_BE(left, outBytes, outOff + 4);
+            hi32 = right;
+            lo32 = left;
         }
     }
 }
diff --git a/crypto/src/crypto/engines/Dstu7624Engine.cs b/crypto/src/crypto/engines/Dstu7624Engine.cs
index 844a873a8..26c3ee586 100644
--- a/crypto/src/crypto/engines/Dstu7624Engine.cs
+++ b/crypto/src/crypto/engines/Dstu7624Engine.cs
@@ -268,7 +268,11 @@ namespace Org.BouncyCastle.Crypto.Engines
                 {
                 case 2:
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    EncryptBlock_128(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
                     EncryptBlock_128(input, inOff, output, outOff);
+#endif
                     break;
                 }
                 default:
@@ -288,6 +292,7 @@ namespace Org.BouncyCastle.Crypto.Engines
                     }
                     AddRoundKey(roundsAmount);
                     Pack.UInt64_To_LE(internalState, output, outOff);
+                    Array.Clear(internalState, 0, internalState.Length);
                     break;
                 }
                 }
@@ -299,7 +304,11 @@ namespace Org.BouncyCastle.Crypto.Engines
                 {
                 case 2:
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    DecryptBlock_128(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
                     DecryptBlock_128(input, inOff, output, outOff);
+#endif
                     break;
                 }
                 default:
@@ -319,6 +328,7 @@ namespace Org.BouncyCastle.Crypto.Engines
                     }
                     SubRoundKey(0);
                     Pack.UInt64_To_LE(internalState, output, outOff);
+                    Array.Clear(internalState, 0, internalState.Length);
                     break;
                 }
                 }
@@ -327,6 +337,84 @@ namespace Org.BouncyCastle.Crypto.Engines
             return GetBlockSize();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (workingKey == null)
+                throw new InvalidOperationException("Dstu7624Engine not initialised");
+
+            Check.DataLength(input, GetBlockSize(), "input buffer too short");
+            Check.OutputLength(output, GetBlockSize(), "output buffer too short");
+
+            if (forEncryption)
+            {
+                /* Encrypt */
+                switch (wordsInBlock)
+                {
+                case 2:
+                {
+                    EncryptBlock_128(input, output);
+                    break;
+                }
+                default:
+                {
+                    Pack.LE_To_UInt64(input, internalState);
+                    AddRoundKey(0);
+                    for (int round = 0;;)
+                    {
+                        EncryptionRound();
+
+                        if (++round == roundsAmount)
+                        {
+                            break;
+                        }
+
+                        XorRoundKey(round);
+                    }
+                    AddRoundKey(roundsAmount);
+                    Pack.UInt64_To_LE(internalState, output);
+                    Array.Clear(internalState, 0, internalState.Length);
+                    break;
+                }
+                }
+            }
+            else
+            {
+                /* Decrypt */
+                switch (wordsInBlock)
+                {
+                case 2:
+                {
+                    DecryptBlock_128(input, output);
+                    break;
+                }
+                default:
+                {
+                    Pack.LE_To_UInt64(input, internalState);
+                    SubRoundKey(roundsAmount);
+                    for (int round = roundsAmount;;)
+                    {
+                        DecryptionRound();
+
+                        if (--round == 0)
+                        {
+                            break;
+                        }
+    
+                        XorRoundKey(round);
+                    }
+                    SubRoundKey(0);
+                    Pack.UInt64_To_LE(internalState, output);
+                    Array.Clear(internalState, 0, internalState.Length);
+                    break;
+                }
+                }
+            }
+
+            return GetBlockSize();
+        }
+#endif
+
         private void EncryptionRound()
         {
             SubBytes();
@@ -341,6 +429,133 @@ namespace Org.BouncyCastle.Crypto.Engines
             InvSubBytes();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void DecryptBlock_128(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            ulong c0 = Pack.LE_To_UInt64(input);
+            ulong c1 = Pack.LE_To_UInt64(input[8..]);
+
+            ulong[] roundKey = roundKeys[roundsAmount];
+            c0 -= roundKey[0];
+            c1 -= roundKey[1];
+
+            for (int round = roundsAmount; ;)
+            {
+                c0 = MixColumnInv(c0);
+                c1 = MixColumnInv(c1);
+
+                uint lo0 = (uint)c0, hi0 = (uint)(c0 >> 32);
+                uint lo1 = (uint)c1, hi1 = (uint)(c1 >> 32);
+
+                {
+                    byte t0 = T0[lo0 & 0xFF];
+                    byte t1 = T1[(lo0 >> 8) & 0xFF];
+                    byte t2 = T2[(lo0 >> 16) & 0xFF];
+                    byte t3 = T3[lo0 >> 24];
+                    lo0 = (uint)t0 | ((uint)t1 << 8) | ((uint)t2 << 16) | ((uint)t3 << 24);
+                    byte t4 = T0[hi1 & 0xFF];
+                    byte t5 = T1[(hi1 >> 8) & 0xFF];
+                    byte t6 = T2[(hi1 >> 16) & 0xFF];
+                    byte t7 = T3[hi1 >> 24];
+                    hi1 = (uint)t4 | ((uint)t5 << 8) | ((uint)t6 << 16) | ((uint)t7 << 24);
+                    c0 = (ulong)lo0 | ((ulong)hi1 << 32);
+                }
+
+                {
+                    byte t0 = T0[lo1 & 0xFF];
+                    byte t1 = T1[(lo1 >> 8) & 0xFF];
+                    byte t2 = T2[(lo1 >> 16) & 0xFF];
+                    byte t3 = T3[lo1 >> 24];
+                    lo1 = (uint)t0 | ((uint)t1 << 8) | ((uint)t2 << 16) | ((uint)t3 << 24);
+                    byte t4 = T0[hi0 & 0xFF];
+                    byte t5 = T1[(hi0 >> 8) & 0xFF];
+                    byte t6 = T2[(hi0 >> 16) & 0xFF];
+                    byte t7 = T3[hi0 >> 24];
+                    hi0 = (uint)t4 | ((uint)t5 << 8) | ((uint)t6 << 16) | ((uint)t7 << 24);
+                    c1 = (ulong)lo1 | ((ulong)hi0 << 32);
+                }
+
+                if (--round == 0)
+                {
+                    break;
+                }
+
+                roundKey = roundKeys[round];
+                c0 ^= roundKey[0];
+                c1 ^= roundKey[1];
+            }
+
+            roundKey = roundKeys[0];
+            c0 -= roundKey[0];
+            c1 -= roundKey[1];
+
+            Pack.UInt64_To_LE(c0, output);
+            Pack.UInt64_To_LE(c1, output[8..]);
+        }
+
+        private void EncryptBlock_128(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            ulong c0 = Pack.LE_To_UInt64(input);
+            ulong c1 = Pack.LE_To_UInt64(input[8..]);
+
+            ulong[] roundKey = roundKeys[0];
+            c0 += roundKey[0];
+            c1 += roundKey[1];
+
+            for (int round = 0; ;)
+            {
+                uint lo0 = (uint)c0, hi0 = (uint)(c0 >> 32);
+                uint lo1 = (uint)c1, hi1 = (uint)(c1 >> 32);
+
+                {
+                    byte t0 = S0[lo0 & 0xFF];
+                    byte t1 = S1[(lo0 >> 8) & 0xFF];
+                    byte t2 = S2[(lo0 >> 16) & 0xFF];
+                    byte t3 = S3[lo0 >> 24];
+                    lo0 = (uint)t0 | ((uint)t1 << 8) | ((uint)t2 << 16) | ((uint)t3 << 24);
+                    byte t4 = S0[hi1 & 0xFF];
+                    byte t5 = S1[(hi1 >> 8) & 0xFF];
+                    byte t6 = S2[(hi1 >> 16) & 0xFF];
+                    byte t7 = S3[hi1 >> 24];
+                    hi1 = (uint)t4 | ((uint)t5 << 8) | ((uint)t6 << 16) | ((uint)t7 << 24);
+                    c0 = (ulong)lo0 | ((ulong)hi1 << 32);
+                }
+
+                {
+                    byte t0 = S0[lo1 & 0xFF];
+                    byte t1 = S1[(lo1 >> 8) & 0xFF];
+                    byte t2 = S2[(lo1 >> 16) & 0xFF];
+                    byte t3 = S3[lo1 >> 24];
+                    lo1 = (uint)t0 | ((uint)t1 << 8) | ((uint)t2 << 16) | ((uint)t3 << 24);
+                    byte t4 = S0[hi0 & 0xFF];
+                    byte t5 = S1[(hi0 >> 8) & 0xFF];
+                    byte t6 = S2[(hi0 >> 16) & 0xFF];
+                    byte t7 = S3[hi0 >> 24];
+                    hi0 = (uint)t4 | ((uint)t5 << 8) | ((uint)t6 << 16) | ((uint)t7 << 24);
+                    c1 = (ulong)lo1 | ((ulong)hi0 << 32);
+                }
+
+                c0 = MixColumn(c0);
+                c1 = MixColumn(c1);
+
+                if (++round == roundsAmount)
+                {
+                    break;
+                }
+
+                roundKey = roundKeys[round];
+                c0 ^= roundKey[0];
+                c1 ^= roundKey[1];
+            }
+
+            roundKey = roundKeys[roundsAmount];
+            c0 += roundKey[0];
+            c1 += roundKey[1];
+
+            Pack.UInt64_To_LE(c0, output);
+            Pack.UInt64_To_LE(c1, output[8..]);
+        }
+#else
         private void DecryptBlock_128(byte[] input, int inOff, byte[] output, int outOff)
         {
             ulong c0 = Pack.LE_To_UInt64(input, inOff);
@@ -466,6 +681,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt64_To_LE(c0, output, outOff);
             Pack.UInt64_To_LE(c1, output, outOff + 8);
         }
+#endif
 
         private void SubBytes()
         {
@@ -900,7 +1116,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             }
         }
 
-        #region TABLES AND S-BOXES
+#region TABLES AND S-BOXES
 
         private const ulong mdsMatrix = 0x0407060801050101UL;
         private const ulong mdsInvMatrix = 0xCAD7492FA87695ADUL;
@@ -1057,7 +1273,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             0xf3, 0x83, 0x28, 0x32, 0x45, 0x1e, 0xa4, 0xd3, 0xa2, 0x46, 0x6e, 0x9c, 0xdd, 0x63, 0xd4, 0x9d
         };
 
-        #endregion
+#endregion
 
         public virtual string AlgorithmName
         {
@@ -1068,15 +1284,5 @@ namespace Org.BouncyCastle.Crypto.Engines
         {
             return wordsInBlock << 3;
         }
-
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
-        public virtual void Reset()
-        {
-            Array.Clear(internalState, 0, internalState.Length);
-        }
     }
 }
diff --git a/crypto/src/crypto/engines/ElGamalEngine.cs b/crypto/src/crypto/engines/ElGamalEngine.cs
index 197d7bc15..ea5e5bc30 100644
--- a/crypto/src/crypto/engines/ElGamalEngine.cs
+++ b/crypto/src/crypto/engines/ElGamalEngine.cs
@@ -28,22 +28,18 @@ namespace Org.BouncyCastle.Crypto.Engines
 		* @param forEncryption true if we are encrypting, false otherwise.
 		* @param param the necessary ElGamal key parameters.
 		*/
-        public virtual void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
 		{
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom withRandom)
 			{
-				ParametersWithRandom p = (ParametersWithRandom) parameters;
-
-				this.key = (ElGamalKeyParameters) p.Parameters;
-				this.random = p.Random;
+				this.key = (ElGamalKeyParameters)withRandom.Parameters;
+				this.random = withRandom.Random;
 			}
 			else
 			{
-				this.key = (ElGamalKeyParameters) parameters;
-				this.random = new SecureRandom();
-			}
+				this.key = (ElGamalKeyParameters)parameters;
+				this.random = CryptoServicesRegistrar.GetSecureRandom();
+            }
 
 			this.forEncryption = forEncryption;
 			this.bitSize = key.Parameters.P.BitLength;
@@ -51,16 +47,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 			if (forEncryption)
 			{
 				if (!(key is ElGamalPublicKeyParameters))
-				{
 					throw new ArgumentException("ElGamalPublicKeyParameters are required for encryption.");
-				}
 			}
 			else
 			{
 				if (!(key is ElGamalPrivateKeyParameters))
-				{
 					throw new ArgumentException("ElGamalPrivateKeyParameters are required for decryption.");
-				}
 			}
 		}
 
diff --git a/crypto/src/crypto/engines/GOST28147Engine.cs b/crypto/src/crypto/engines/GOST28147Engine.cs
index 8ef8aeb13..ef566618f 100644
--- a/crypto/src/crypto/engines/GOST28147Engine.cs
+++ b/crypto/src/crypto/engines/GOST28147Engine.cs
@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
@@ -151,14 +152,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 		* @param parameters the parameters required to set up the cipher.
 		* @exception ArgumentException if the parameters argument is inappropriate.
 		*/
-        public virtual void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
 		{
-			if (parameters is ParametersWithSBox)
+			if (parameters is ParametersWithSBox param)
 			{
-				ParametersWithSBox   param = (ParametersWithSBox)parameters;
-
 				//
 				// Set the S-Box
 				//
@@ -173,14 +170,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 				//
 				if (param.Parameters != null)
 				{
-					workingKey = generateWorkingKey(forEncryption,
-							((KeyParameter)param.Parameters).GetKey());
+					workingKey = GenerateWorkingKey(forEncryption, ((KeyParameter)param.Parameters).GetKey());
 				}
 			}
-			else if (parameters is KeyParameter)
+			else if (parameters is KeyParameter keyParameter)
 			{
-				workingKey = generateWorkingKey(forEncryption,
-									((KeyParameter)parameters).GetKey());
+				workingKey = GenerateWorkingKey(forEncryption, keyParameter.GetKey());
 			}
 			else if (parameters != null)
 			{
@@ -194,21 +189,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "Gost28147"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return BlockSize;
 		}
 
-        public virtual int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-			byte[]	output,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			if (workingKey == null)
 				throw new InvalidOperationException("Gost28147 engine not initialised");
@@ -216,30 +202,41 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BlockSize, "input buffer too short");
             Check.OutputLength(output, outOff, BlockSize, "output buffer too short");
 
-            Gost28147Func(workingKey, input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			Gost28147Func(workingKey, input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+			Gost28147Func(workingKey, input, inOff, output, outOff);
+#endif
 
 			return BlockSize;
 		}
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
+			if (workingKey == null)
+				throw new InvalidOperationException("Gost28147 engine not initialised");
+
+			Check.DataLength(input, BlockSize, "input buffer too short");
+			Check.OutputLength(output, BlockSize, "output buffer too short");
+
+			Gost28147Func(workingKey, input, output);
+
+			return BlockSize;
 		}
+#endif
 
-		private int[] generateWorkingKey(
-			bool forEncryption,
-			byte[]  userKey)
+		private int[] GenerateWorkingKey(bool forEncryption, byte[] userKey)
 		{
 			this.forEncryption = forEncryption;
 
 			if (userKey.Length != 32)
-			{
 				throw new ArgumentException("Key length invalid. Key needs to be 32 byte - 256 bit!!!");
-			}
 
 			int[] key = new int[8];
-			for(int i=0; i!=8; i++)
+			for(int i=0; i != 8; i++)
 			{
-				key[i] = bytesToint(userKey,i*4);
+				key[i] = (int)Pack.LE_To_UInt32(userKey, i * 4);
 			}
 
 			return key;
@@ -267,16 +264,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 			return omLeft | omRight;
 		}
 
-		private void Gost28147Func(
-			int[]   workingKey,
-			byte[]  inBytes,
-			int     inOff,
-			byte[]  outBytes,
-			int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private void Gost28147Func(int[] workingKey, ReadOnlySpan<byte> input, Span<byte> output)
 		{
-			int N1, N2, tmp;  //tmp -> for saving N1
-			N1 = bytesToint(inBytes, inOff);
-			N2 = bytesToint(inBytes, inOff + 4);
+			int N1 = (int)Pack.LE_To_UInt32(input);
+			int N2 = (int)Pack.LE_To_UInt32(input[4..]);
+			int tmp;  //tmp -> for saving N1
 
 			if (this.forEncryption)
 			{
@@ -322,30 +315,64 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			N2 = N2 ^ Gost28147_mainStep(N1, workingKey[0]);  // 32 step (N1=N1)
 
-			intTobytes(N1, outBytes, outOff);
-			intTobytes(N2, outBytes, outOff + 4);
+			Pack.UInt32_To_LE((uint)N1, output);
+			Pack.UInt32_To_LE((uint)N2, output[4..]);
 		}
-
-		//array of bytes to type int
-		private static int bytesToint(
-			byte[]  inBytes,
-			int     inOff)
+#else
+		private void Gost28147Func(int[] workingKey, byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
-			return  (int)((inBytes[inOff + 3] << 24) & 0xff000000) + ((inBytes[inOff + 2] << 16) & 0xff0000) +
-					((inBytes[inOff + 1] << 8) & 0xff00) + (inBytes[inOff] & 0xff);
-		}
+			int N1 = (int)Pack.LE_To_UInt32(inBytes, inOff);
+			int N2 = (int)Pack.LE_To_UInt32(inBytes, inOff + 4);
+			int tmp;  //tmp -> for saving N1
 
-		//int to array of bytes
-		private static void intTobytes(
-				int     num,
-				byte[]  outBytes,
-				int     outOff)
-		{
-				outBytes[outOff + 3] = (byte)(num >> 24);
-				outBytes[outOff + 2] = (byte)(num >> 16);
-				outBytes[outOff + 1] = (byte)(num >> 8);
-				outBytes[outOff] =     (byte)num;
+			if (this.forEncryption)
+			{
+			for(int k = 0; k < 3; k++)  // 1-24 steps
+			{
+				for(int j = 0; j < 8; j++)
+				{
+					tmp = N1;
+					int step = Gost28147_mainStep(N1, workingKey[j]);
+					N1 = N2 ^ step; // CM2
+					N2 = tmp;
+				}
+			}
+			for(int j = 7; j > 0; j--)  // 25-31 steps
+			{
+				tmp = N1;
+				N1 = N2 ^ Gost28147_mainStep(N1, workingKey[j]); // CM2
+				N2 = tmp;
+			}
+			}
+			else //decrypt
+			{
+			for(int j = 0; j < 8; j++)  // 1-8 steps
+			{
+				tmp = N1;
+				N1 = N2 ^ Gost28147_mainStep(N1, workingKey[j]); // CM2
+				N2 = tmp;
+			}
+			for(int k = 0; k < 3; k++)  //9-31 steps
+			{
+				for(int j = 7; j >= 0; j--)
+				{
+					if ((k == 2) && (j==0))
+					{
+						break; // break 32 step
+					}
+					tmp = N1;
+					N1 = N2 ^ Gost28147_mainStep(N1, workingKey[j]); // CM2
+					N2 = tmp;
+				}
+			}
+			}
+
+			N2 = N2 ^ Gost28147_mainStep(N1, workingKey[0]);  // 32 step (N1=N1)
+
+			Pack.UInt32_To_LE((uint)N1, outBytes, outOff);
+			Pack.UInt32_To_LE((uint)N2, outBytes, outOff + 4);
 		}
+#endif
 
 		/**
 		* Return the S-Box associated with SBoxName
diff --git a/crypto/src/crypto/engines/Grain128AEADEngine.cs b/crypto/src/crypto/engines/Grain128AEADEngine.cs
new file mode 100644
index 000000000..c05cb0115
--- /dev/null
+++ b/crypto/src/crypto/engines/Grain128AEADEngine.cs
@@ -0,0 +1,564 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Engines
+{
+    public sealed class Grain128AeadEngine
+        : IAeadCipher
+    {
+        /**
+         * Constants
+         */
+        private static readonly int STATE_SIZE = 4;
+
+        /**
+         * Variables to hold the state of the engine during encryption and
+         * decryption
+         */
+        private byte[] workingKey;
+        private byte[] workingIV;
+        private uint[] lfsr;
+        private uint[] nfsr;
+        private uint[] authAcc;
+        private uint[] authSr;
+
+        private bool initialised = false;
+        private bool aadFinished = false;
+        private MemoryStream aadData = new MemoryStream();
+
+        private byte[] mac;
+
+        public string AlgorithmName => "Grain-128AEAD";
+
+        /**
+         * Initialize a Grain-128AEAD cipher.
+         *
+         * @param forEncryption Whether or not we are for encryption.
+         * @param param        The parameters required to set up the cipher.
+         * @throws ArgumentException If the params argument is inappropriate.
+         */
+        public void Init(bool forEncryption, ICipherParameters param)
+        {
+            /*
+             * Grain encryption and decryption is completely symmetrical, so the
+             * 'forEncryption' is irrelevant.
+             */
+            if (!(param is ParametersWithIV ivParams))
+                throw new ArgumentException("Grain-128AEAD Init parameters must include an IV");
+
+            byte[] iv = ivParams.GetIV();
+
+            if (iv == null || iv.Length != 12)
+                throw new ArgumentException("Grain-128AEAD requires exactly 12 bytes of IV");
+
+            if (!(ivParams.Parameters is KeyParameter key))
+                throw new ArgumentException("Grain-128AEAD Init parameters must include a key");
+
+            byte[] keyBytes = key.GetKey();
+            if (keyBytes.Length != 16)
+                throw new ArgumentException("Grain-128AEAD key must be 128 bits long");
+
+            /*
+             * Initialize variables.
+             */
+            workingIV = new byte[keyBytes.Length];
+            workingKey = keyBytes;
+            lfsr = new uint[STATE_SIZE];
+            nfsr = new uint[STATE_SIZE];
+            authAcc = new uint[2];
+            authSr = new uint[2];
+
+            Array.Copy(iv, 0, workingIV, 0, iv.Length);
+
+            Reset();
+        }
+
+        /**
+         * 320 clocks initialization phase.
+         */
+        private void InitGrain()
+        {
+            for (int i = 0; i < 320; ++i)
+            {
+                uint outputZ = GetOutput();
+                nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0] ^ outputZ) & 1);
+                lfsr = Shift(lfsr, (GetOutputLFSR() ^ outputZ) & 1);
+            }
+            for (int quotient = 0; quotient < 8; ++quotient)
+            {
+                for (int remainder = 0; remainder < 8; ++remainder)
+                {
+                    uint outputZ = GetOutput();
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0] ^ outputZ ^ (uint)((workingKey[quotient]) >> remainder)) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR() ^ outputZ ^ (uint)((workingKey[quotient + 8]) >> remainder)) & 1);
+                }
+            }
+            for (int quotient = 0; quotient < 2; ++quotient)
+            {
+                for (int remainder = 0; remainder < 32; ++remainder)
+                {
+                    uint outputZ = GetOutput();
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+                    authAcc[quotient] |= outputZ << remainder;
+                }
+            }
+            for (int quotient = 0; quotient < 2; ++quotient)
+            {
+                for (int remainder = 0; remainder < 32; ++remainder)
+                {
+                    uint outputZ = GetOutput();
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+                    authSr[quotient] |= outputZ << remainder;
+                }
+            }
+            initialised = true;
+        }
+
+        /**
+         * Get output from non-linear function g(x).
+         *
+         * @return Output from NFSR.
+         */
+        private uint GetOutputNFSR()
+        {
+            uint b0 = nfsr[0];
+            uint b3 = nfsr[0] >> 3;
+            uint b11 = nfsr[0] >> 11;
+            uint b13 = nfsr[0] >> 13;
+            uint b17 = nfsr[0] >> 17;
+            uint b18 = nfsr[0] >> 18;
+            uint b22 = nfsr[0] >> 22;
+            uint b24 = nfsr[0] >> 24;
+            uint b25 = nfsr[0] >> 25;
+            uint b26 = nfsr[0] >> 26;
+            uint b27 = nfsr[0] >> 27;
+            uint b40 = nfsr[1] >> 8;
+            uint b48 = nfsr[1] >> 16;
+            uint b56 = nfsr[1] >> 24;
+            uint b59 = nfsr[1] >> 27;
+            uint b61 = nfsr[1] >> 29;
+            uint b65 = nfsr[2] >> 1;
+            uint b67 = nfsr[2] >> 3;
+            uint b68 = nfsr[2] >> 4;
+            uint b70 = nfsr[2] >> 6;
+            uint b78 = nfsr[2] >> 14;
+            uint b82 = nfsr[2] >> 18;
+            uint b84 = nfsr[2] >> 20;
+            uint b88 = nfsr[2] >> 24;
+            uint b91 = nfsr[2] >> 27;
+            uint b92 = nfsr[2] >> 28;
+            uint b93 = nfsr[2] >> 29;
+            uint b95 = nfsr[2] >> 31;
+            uint b96 = nfsr[3];
+
+            return (b0 ^ b26 ^ b56 ^ b91 ^ b96 ^ b3 & b67 ^ b11 & b13 ^ b17 & b18
+                ^ b27 & b59 ^ b40 & b48 ^ b61 & b65 ^ b68 & b84 ^ b22 & b24 & b25 ^ b70 & b78 & b82 ^ b88 & b92 & b93 & b95) & 1;
+        }
+
+        /**
+         * Get output from linear function f(x).
+         *
+         * @return Output from LFSR.
+         */
+        private uint GetOutputLFSR()
+        {
+            uint s0 = lfsr[0];
+            uint s7 = lfsr[0] >> 7;
+            uint s38 = lfsr[1] >> 6;
+            uint s70 = lfsr[2] >> 6;
+            uint s81 = lfsr[2] >> 17;
+            uint s96 = lfsr[3];
+
+            return (s0 ^ s7 ^ s38 ^ s70 ^ s81 ^ s96) & 1;
+        }
+
+        /**
+         * Get output from output function h(x).
+         *
+         * @return y_t.
+         */
+        private uint GetOutput()
+        {
+            uint b2 = nfsr[0] >> 2;
+            uint b12 = nfsr[0] >> 12;
+            uint b15 = nfsr[0] >> 15;
+            uint b36 = nfsr[1] >> 4;
+            uint b45 = nfsr[1] >> 13;
+            uint b64 = nfsr[2];
+            uint b73 = nfsr[2] >> 9;
+            uint b89 = nfsr[2] >> 25;
+            uint b95 = nfsr[2] >> 31;
+            uint s8 = lfsr[0] >> 8;
+            uint s13 = lfsr[0] >> 13;
+            uint s20 = lfsr[0] >> 20;
+            uint s42 = lfsr[1] >> 10;
+            uint s60 = lfsr[1] >> 28;
+            uint s79 = lfsr[2] >> 15;
+            uint s93 = lfsr[2] >> 29;
+            uint s94 = lfsr[2] >> 30;
+
+            return ((b12 & s8) ^ (s13 & s20) ^ (b95 & s42) ^ (s60 & s79) ^ (b12 & b95 & s94) ^ s93
+                ^ b2 ^ b15 ^ b36 ^ b45 ^ b64 ^ b73 ^ b89) & 1;
+        }
+
+        /**
+         * Shift array 1 bit and add val to index.Length - 1.
+         *
+         * @param array The array to shift.
+         * @param val   The value to shift in.
+         * @return The shifted array with val added to index.Length - 1.
+         */
+        private uint[] Shift(uint[] array, uint val)
+        {
+            array[0] = (array[0] >> 1) | (array[1] << 31);
+            array[1] = (array[1] >> 1) | (array[2] << 31);
+            array[2] = (array[2] >> 1) | (array[3] << 31);
+            array[3] = (array[3] >> 1) | (val << 31);
+            return array;
+        }
+
+        /**
+         * Set keys, reset cipher.
+         *
+         * @param keyBytes The key.
+         * @param ivBytes  The IV.
+         */
+        private void SetKey(byte[] keyBytes, byte[] ivBytes)
+        {
+            ivBytes[12] = 0xFF;
+            ivBytes[13] = 0xFF;
+            ivBytes[14] = 0xFF;
+            ivBytes[15] = 0x7F;
+            workingKey = keyBytes;
+            workingIV = ivBytes;
+
+            /*
+             * Load NFSR and LFSR
+             */
+            Pack.LE_To_UInt32(workingKey, 0, nfsr);
+            Pack.LE_To_UInt32(workingIV, 0, lfsr);
+        }
+
+        public int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
+        {
+            Check.DataLength(input, inOff, len, "input buffer too short");
+            Check.OutputLength(output, outOff, len, "output buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(input.AsSpan(inOff, len), output.AsSpan(outOff));
+#else
+            if (!initialised)
+                throw new ArgumentException(AlgorithmName + " not initialised");
+
+            if (!aadFinished)
+            {
+                DoProcessAADBytes(aadData.GetBuffer(), 0, (int)aadData.Length);
+                aadFinished = true;
+            }
+
+            GetKeyStream(input, inOff, len, output, outOff);
+            return len;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            if (!initialised)
+                throw new ArgumentException(AlgorithmName + " not initialised");
+
+            if (!aadFinished)
+            {
+                DoProcessAADBytes(aadData.GetBuffer(), 0, (int)aadData.Length);
+                aadFinished = true;
+            }
+
+            GetKeyStream(input, output);
+            return input.Length;
+        }
+#endif
+
+        public void Reset()
+        {
+            Reset(true);
+        }
+
+        private void Reset(bool clearMac)
+        {
+            if (clearMac)
+            {
+                this.mac = null;
+            }
+            this.aadData.SetLength(0);
+            this.aadFinished = false;
+
+            SetKey(workingKey, workingIV);
+            InitGrain();
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void GetKeyStream(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int len = input.Length;
+            for (int i = 0; i < len; ++i)
+            {
+                uint cc = 0, input_i = input[i];
+                for (int j = 0; j < 8; ++j)
+                {
+                    uint outputZ = GetOutput();
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+
+                    uint input_i_j = (input_i >> j) & 1U;
+                    cc |= (input_i_j ^ outputZ) << j;
+
+                    //if (input_i_j != 0)
+                    //{
+                    //    Accumulate();
+                    //}
+                    uint mask = 0U - input_i_j;
+                    authAcc[0] ^= authSr[0] & mask;
+                    authAcc[1] ^= authSr[1] & mask;
+
+                    AuthShift(GetOutput());
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+                }
+                output[i] = (byte)cc;
+            }
+        }
+#else
+        private void GetKeyStream(byte[] input, int inOff, int len, byte[] ciphertext, int outOff)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                uint cc = 0, input_i = input[inOff + i];
+                for (int j = 0; j < 8; ++j)
+                {
+                    uint outputZ = GetOutput();
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+
+                    uint input_i_j = (input_i >> j) & 1U;
+                    cc |= (input_i_j ^ outputZ) << j;
+
+                    //if (input_i_j != 0)
+                    //{
+                    //    Accumulate();
+                    //}
+                    uint mask = 0U - input_i_j;
+                    authAcc[0] ^= authSr[0] & mask;
+                    authAcc[1] ^= authSr[1] & mask;
+
+                    AuthShift(GetOutput());
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+                }
+                ciphertext[outOff + i] = (byte)cc;
+            }
+        }
+#endif
+
+        public byte ReturnByte(byte input)
+        {
+            if (!initialised)
+                throw new ArgumentException(AlgorithmName + " not initialised");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> plaintext = stackalloc byte[1]{ input };
+            Span<byte> ciphertext = stackalloc byte[1];
+            GetKeyStream(plaintext, ciphertext);
+            return ciphertext[0];
+#else
+            byte[] plaintext = new byte[1]{ input };
+            byte[] ciphertext = new byte[1];
+            GetKeyStream(plaintext, 0, 1, ciphertext, 0);
+            return ciphertext[0];
+#endif
+        }
+
+        public void ProcessAadByte(byte input)
+        {
+            if (aadFinished)
+                throw new ArgumentException("associated data must be added before plaintext/ciphertext");
+
+            aadData.WriteByte(input);
+        }
+
+        public void ProcessAadBytes(byte[] input, int inOff, int len)
+        {
+            if (aadFinished)
+                throw new ArgumentException("associated data must be added before plaintext/ciphertext");
+
+            aadData.Write(input, inOff, len);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            if (aadFinished)
+                throw new ArgumentException("associated data must be added before plaintext/ciphertext");
+
+            aadData.Write(input);
+        }
+#endif
+
+        private void Accumulate()
+        {
+            authAcc[0] ^= authSr[0];
+            authAcc[1] ^= authSr[1];
+        }
+
+        private void AuthShift(uint val)
+        {
+            authSr[0] = (authSr[0] >> 1) | (authSr[1] << 31);
+            authSr[1] = (authSr[1] >> 1) | (val << 31);
+        }
+
+        public int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            return ProcessBytes(new byte[]{ input }, 0, 1, output, outOff);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessByte(byte input, Span<byte> output)
+        {
+            return ProcessBytes(stackalloc byte[1]{ input }, output);
+        }
+#endif
+
+        private void DoProcessAADBytes(byte[] input, int inOff, int len)
+        {
+            byte[] ader;
+            int aderlen;
+            //encodeDer
+            if (len < 128)
+            {
+                ader = new byte[1 + len];
+                ader[0] = (byte)len;
+                aderlen = 0;
+            }
+            else
+            {
+                // aderlen is the highest bit position divided by 8
+                aderlen = LenLength(len);
+                ader = new byte[aderlen + 1 + len];
+                ader[0] = (byte)(0x80 | (uint)aderlen);
+                uint tmp = (uint)len;
+                for (int i = 0; i < aderlen; ++i)
+                {
+                    ader[1 + i] = (byte)tmp;
+                    tmp >>= 8;
+                }
+            }
+            for (int i = 0; i < len; ++i)
+            {
+                ader[1 + aderlen + i] = input[inOff + i];
+            }
+
+            for (int i = 0; i < ader.Length; ++i)
+            {
+                uint ader_i = ader[i];
+                for (int j = 0; j < 8; ++j)
+                {
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+
+                    uint ader_i_j = (ader_i >> j) & 1U;
+                    //if (ader_i_j != 0)
+                    //{
+                    //    Accumulate();
+                    //}
+                    uint mask = 0U - ader_i_j;
+                    authAcc[0] ^= authSr[0] & mask;
+                    authAcc[1] ^= authSr[1] & mask;
+
+                    AuthShift(GetOutput());
+                    nfsr = Shift(nfsr, (GetOutputNFSR() ^ lfsr[0]) & 1);
+                    lfsr = Shift(lfsr, (GetOutputLFSR()) & 1);
+                }
+            }
+        }
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
+            if (!aadFinished)
+            {
+                DoProcessAADBytes(aadData.GetBuffer(), 0, (int)aadData.Length);
+                aadFinished = true;
+            }
+
+            Accumulate();
+
+            this.mac = Pack.UInt32_To_LE(authAcc);
+
+            Array.Copy(mac, 0, output, outOff, mac.Length);
+
+            Reset(false);
+
+            return mac.Length;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            if (!aadFinished)
+            {
+                DoProcessAADBytes(aadData.GetBuffer(), 0, (int)aadData.Length);
+                aadFinished = true;
+            }
+
+            Accumulate();
+
+            this.mac = Pack.UInt32_To_LE(authAcc);
+
+            mac.CopyTo(output);
+
+            Reset(false);
+
+            return mac.Length;
+        }
+#endif
+
+        public byte[] GetMac()
+        {
+            return mac;
+        }
+
+        public int GetUpdateOutputSize(int len)
+        {
+            return len;
+        }
+
+        public int GetOutputSize(int len)
+        {
+            return len + 8;
+        }
+
+        private static int LenLength(int v)
+        {
+            if ((v & 0xff) == v)
+                return 1;
+
+            if ((v & 0xffff) == v)
+                return 2;
+
+            if ((v & 0xffffff) == v)
+                return 3;
+
+            return 4;
+        }
+    }
+}
diff --git a/crypto/src/crypto/engines/HC128Engine.cs b/crypto/src/crypto/engines/HC128Engine.cs
index b83eb7083..6971361dd 100644
--- a/crypto/src/crypto/engines/HC128Engine.cs
+++ b/crypto/src/crypto/engines/HC128Engine.cs
@@ -222,6 +222,21 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                output[i] = (byte)(input[i] ^ GetByte());
+            }
+        }
+#endif
+
         public virtual void Reset()
 		{
 			Init();
diff --git a/crypto/src/crypto/engines/HC256Engine.cs b/crypto/src/crypto/engines/HC256Engine.cs
index d8d83a634..8a17af433 100644
--- a/crypto/src/crypto/engines/HC256Engine.cs
+++ b/crypto/src/crypto/engines/HC256Engine.cs
@@ -206,6 +206,21 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                output[i] = (byte)(input[i] ^ GetByte());
+            }
+        }
+#endif
+
         public virtual void Reset()
 		{
 			Init();
diff --git a/crypto/src/crypto/engines/ISAACEngine.cs b/crypto/src/crypto/engines/ISAACEngine.cs
index b94ee6ed9..b0ab30263 100644
--- a/crypto/src/crypto/engines/ISAACEngine.cs
+++ b/crypto/src/crypto/engines/ISAACEngine.cs
@@ -94,6 +94,27 @@ namespace Org.BouncyCastle.Crypto.Engines
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                if (index == 0)
+                {
+                    isaac();
+                    keyStream = Pack.UInt32_To_BE(results);
+                }
+                output[i] = (byte)(keyStream[index++] ^ input[i]);
+                index &= 1023;
+            }
+        }
+#endif
+
         public virtual string AlgorithmName
         {
             get { return "ISAAC"; }
diff --git a/crypto/src/crypto/engines/IdeaEngine.cs b/crypto/src/crypto/engines/IdeaEngine.cs
index 6c0379174..a61ba03ab 100644
--- a/crypto/src/crypto/engines/IdeaEngine.cs
+++ b/crypto/src/crypto/engines/IdeaEngine.cs
@@ -54,21 +54,12 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "IDEA"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
         public virtual int GetBlockSize()
         {
             return BLOCK_SIZE;
         }
 
-        public virtual int ProcessBlock(
-            byte[] input,
-            int inOff,
-            byte[] output,
-            int outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             if (workingKey == null)
                 throw new InvalidOperationException("IDEA engine not initialised");
@@ -76,28 +67,55 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            IdeaFunc(workingKey, input.AsSpan(inOff), output.AsSpan(outOff));
+#else
             IdeaFunc(workingKey, input, inOff, output, outOff);
+#endif
             return BLOCK_SIZE;
         }
-        public virtual void Reset()
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (workingKey == null)
+                throw new InvalidOperationException("IDEA engine not initialised");
+
+            Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+            Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+            IdeaFunc(workingKey, input, output);
+            return BLOCK_SIZE;
         }
-        private static readonly int    MASK = 0xffff;
-        private static readonly int    BASE = 0x10001;
-        private int BytesToWord(
-            byte[]  input,
-            int     inOff)
+#endif
+
+        private static readonly int MASK = 0xffff;
+        private static readonly int BASE = 0x10001;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int BytesToWord(ReadOnlySpan<byte> input)
+        {
+            return ((input[0] << 8) & 0xff00) + (input[1] & 0xff);
+        }
+
+        private void WordToBytes(int word, Span<byte> output)
+        {
+            output[0] = (byte)((uint)word >> 8);
+            output[1] = (byte)word;
+        }
+#else
+        private int BytesToWord(byte[] input, int inOff)
         {
             return ((input[inOff] << 8) & 0xff00) + (input[inOff + 1] & 0xff);
         }
-        private void WordToBytes(
-            int     word,
-            byte[]  outBytes,
-            int     outOff)
+
+        private void WordToBytes(int word, byte[] outBytes, int outOff)
         {
             outBytes[outOff] = (byte)((uint) word >> 8);
             outBytes[outOff + 1] = (byte)word;
         }
+#endif
+
         /**
         * return x = x * y where the multiplication is done modulo
         * 65537 (0x10001) (as defined in the IDEA specification) and
@@ -128,19 +146,51 @@ namespace Org.BouncyCastle.Crypto.Engines
             }
             return x & MASK;
         }
-        private void IdeaFunc(
-            int[]   workingKey,
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void IdeaFunc(int[] workingKey, ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int x0 = BytesToWord(input);
+            int x1 = BytesToWord(input[2..]);
+            int x2 = BytesToWord(input[4..]);
+            int x3 = BytesToWord(input[6..]);
+            int keyOff = 0, t0, t1;
+            for (int round = 0; round < 8; round++)
+            {
+                x0 = Mul(x0, workingKey[keyOff++]);
+                x1 += workingKey[keyOff++];
+                x1 &= MASK;
+                x2 += workingKey[keyOff++];
+                x2 &= MASK;
+                x3 = Mul(x3, workingKey[keyOff++]);
+                t0 = x1;
+                t1 = x2;
+                x2 ^= x0;
+                x1 ^= x3;
+                x2 = Mul(x2, workingKey[keyOff++]);
+                x1 += x2;
+                x1 &= MASK;
+                x1 = Mul(x1, workingKey[keyOff++]);
+                x2 += x1;
+                x2 &= MASK;
+                x0 ^= x1;
+                x3 ^= x2;
+                x1 ^= t1;
+                x2 ^= t0;
+            }
+            WordToBytes(Mul(x0, workingKey[keyOff++]), output);
+            WordToBytes(x2 + workingKey[keyOff++], output[2..]);  /* NB: Order */
+            WordToBytes(x1 + workingKey[keyOff++], output[4..]);
+            WordToBytes(Mul(x3, workingKey[keyOff]), output[6..]);
+        }
+#else
+        private void IdeaFunc(int[] workingKey, byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            int     x0, x1, x2, x3, t0, t1;
-            int     keyOff = 0;
-            x0 = BytesToWord(input, inOff);
-            x1 = BytesToWord(input, inOff + 2);
-            x2 = BytesToWord(input, inOff + 4);
-            x3 = BytesToWord(input, inOff + 6);
+            int x0 = BytesToWord(input, inOff);
+            int x1 = BytesToWord(input, inOff + 2);
+            int x2 = BytesToWord(input, inOff + 4);
+            int x3 = BytesToWord(input, inOff + 6);
+            int keyOff = 0, t0, t1;
             for (int round = 0; round < 8; round++)
             {
                 x0 = Mul(x0, workingKey[keyOff++]);
@@ -169,16 +219,17 @@ namespace Org.BouncyCastle.Crypto.Engines
             WordToBytes(x1 + workingKey[keyOff++], outBytes, outOff + 4);
             WordToBytes(Mul(x3, workingKey[keyOff]), outBytes, outOff + 6);
         }
+#endif
+
         /**
         * The following function is used to expand the user key to the encryption
         * subkey. The first 16 bytes are the user key, and the rest of the subkey
         * is calculated by rotating the previous 16 bytes by 25 bits to the left,
         * and so on until the subkey is completed.
         */
-        private int[] ExpandKey(
-            byte[]  uKey)
+        private int[] ExpandKey(byte[] uKey)
         {
-            int[]   key = new int[52];
+            int[] key = new int[52];
             if (uKey.Length < 16)
             {
                 byte[]  tmp = new byte[16];
@@ -187,7 +238,11 @@ namespace Org.BouncyCastle.Crypto.Engines
             }
             for (int i = 0; i < 8; i++)
             {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                key[i] = BytesToWord(uKey[(i * 2)..]);
+#else
                 key[i] = BytesToWord(uKey, i * 2);
+#endif
             }
             for (int i = 8; i < 52; i++)
             {
diff --git a/crypto/src/crypto/engines/NoekeonEngine.cs b/crypto/src/crypto/engines/NoekeonEngine.cs
index 838a40339..eca1ee04b 100644
--- a/crypto/src/crypto/engines/NoekeonEngine.cs
+++ b/crypto/src/crypto/engines/NoekeonEngine.cs
@@ -36,11 +36,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "Noekeon"; }
 		}
 
-		public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return Size;
@@ -92,11 +87,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             this._initialised = true;
         }
 
-        public virtual int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-			byte[]	output,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			if (!_initialised)
 				throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -104,15 +95,175 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, Size, "input buffer too short");
             Check.OutputLength(output, outOff, Size, "output buffer too short");
 
-            return _forEncryption
-				?	EncryptBlock(input, inOff, output, outOff)
-				:	DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return _forEncryption
+				? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+				: DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+			return _forEncryption
+				? EncryptBlock(input, inOff, output, outOff)
+				: DecryptBlock(input, inOff, output, outOff);
+#endif
 		}
 
-		public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
+			if (!_initialised)
+				throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+			Check.DataLength(input, Size, "input buffer too short");
+			Check.OutputLength(output, Size, "output buffer too short");
+
+			return _forEncryption
+				? EncryptBlock(input, output)
+				: DecryptBlock(input, output);
+		}
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint a0 = Pack.BE_To_UInt32(input);
+			uint a1 = Pack.BE_To_UInt32(input[4..]);
+			uint a2 = Pack.BE_To_UInt32(input[8..]);
+			uint a3 = Pack.BE_To_UInt32(input[12..]);
+
+			uint k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
+
+			int round = 0;
+			for (;;)
+			{
+				a0 ^= RoundConstants[round];
+
+				// theta(a, k);
+				{
+					uint t02 = a0 ^ a2;
+					t02 ^= Integers.RotateLeft(t02, 8) ^ Integers.RotateLeft(t02, 24);
+
+					a0 ^= k0;
+					a1 ^= k1;
+					a2 ^= k2;
+					a3 ^= k3;
+
+                    uint t13 = a1 ^ a3;
+                    t13 ^= Integers.RotateLeft(t13, 8) ^ Integers.RotateLeft(t13, 24);
+
+                    a0 ^= t13;
+                    a1 ^= t02;
+                    a2 ^= t13;
+                    a3 ^= t02;
+				}
+
+				if (++round > Size)
+					break;
+
+				// pi1(a);
+				{
+					a1 = Integers.RotateLeft(a1, 1);
+					a2 = Integers.RotateLeft(a2, 5);
+					a3 = Integers.RotateLeft(a3, 2);
+				}
+
+				// gamma(a);
+				{
+                    uint t = a3;
+                    a1 ^= a3 | a2;
+                    a3 = a0 ^ (a2 & ~a1);
+
+                    a2 = t ^ ~a1 ^ a2 ^ a3;
+
+                    a1 ^= a3 | a2;
+                    a0 = t ^ (a2 & a1);
+				}
+
+				// pi2(a);
+				{
+					a1 = Integers.RotateLeft(a1, 31);
+					a2 = Integers.RotateLeft(a2, 27);
+					a3 = Integers.RotateLeft(a3, 30);
+				}
+			}
+
+			Pack.UInt32_To_BE(a0, output);
+			Pack.UInt32_To_BE(a1, output[4..]);
+			Pack.UInt32_To_BE(a2, output[8..]);
+			Pack.UInt32_To_BE(a3, output[12..]);
+
+			return Size;
 		}
 
+		private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			uint a0 = Pack.BE_To_UInt32(input);
+			uint a1 = Pack.BE_To_UInt32(input[4..]);
+			uint a2 = Pack.BE_To_UInt32(input[8..]);
+			uint a3 = Pack.BE_To_UInt32(input[12..]);
+
+			uint k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
+
+			int round = Size;
+			for (;;)
+			{
+				// theta(a, k);
+				{
+                    uint t02 = a0 ^ a2;
+                    t02 ^= Integers.RotateLeft(t02, 8) ^ Integers.RotateLeft(t02, 24);
+
+                    a0 ^= k0;
+                    a1 ^= k1;
+                    a2 ^= k2;
+                    a3 ^= k3;
+
+                    uint t13 = a1 ^ a3;
+                    t13 ^= Integers.RotateLeft(t13, 8) ^ Integers.RotateLeft(t13, 24);
+
+                    a0 ^= t13;
+                    a1 ^= t02;
+                    a2 ^= t13;
+                    a3 ^= t02;
+                }
+
+                a0 ^= RoundConstants[round];
+
+				if (--round < 0)
+					break;
+
+				// pi1(a);
+				{
+					a1 = Integers.RotateLeft(a1, 1);
+					a2 = Integers.RotateLeft(a2, 5);
+					a3 = Integers.RotateLeft(a3, 2);
+				}
+
+				// gamma(a);
+				{
+                    uint t = a3;
+                    a1 ^= a3 | a2;
+                    a3 = a0 ^ (a2 & ~a1);
+
+                    a2 = t ^ ~a1 ^ a2 ^ a3;
+
+                    a1 ^= a3 | a2;
+                    a0 = t ^ (a2 & a1);
+                }
+
+                // pi2(a);
+                {
+					a1 = Integers.RotateLeft(a1, 31);
+					a2 = Integers.RotateLeft(a2, 27);
+					a3 = Integers.RotateLeft(a3, 30);
+				}
+			}
+
+			Pack.UInt32_To_BE(a0, output);
+			Pack.UInt32_To_BE(a1, output[4..]);
+			Pack.UInt32_To_BE(a2, output[8..]);
+			Pack.UInt32_To_BE(a3, output[12..]);
+
+			return Size;
+		}
+#else
 		private int EncryptBlock(byte[]	input, int inOff, byte[] output, int outOff)
 		{
 			uint a0 = Pack.BE_To_UInt32(input, inOff);
@@ -254,5 +405,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			return Size;
 		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/engines/NullEngine.cs b/crypto/src/crypto/engines/NullEngine.cs
deleted file mode 100644
index f883b7c29..000000000
--- a/crypto/src/crypto/engines/NullEngine.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System;
-
-using Org.BouncyCastle.Crypto.Parameters;
-
-namespace Org.BouncyCastle.Crypto.Engines
-{
-	/**
-	* The no-op engine that just copies bytes through, irrespective of whether encrypting and decrypting.
-	* Provided for the sake of completeness.
-	*/
-	public class NullEngine
-		: IBlockCipher
-	{
-		private bool initialised;
-		private const int BlockSize = 1;
-
-		public NullEngine()
-		{
-		}
-
-        public virtual void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
-		{
-			// we don't mind any parameters that may come in
-			initialised = true;
-		}
-
-        public virtual string AlgorithmName
-		{
-			get { return "Null"; }
-		}
-
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return true; }
-		}
-
-        public virtual int GetBlockSize()
-		{
-			return BlockSize;
-		}
-
-        public virtual int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-			byte[]	output,
-			int		outOff)
-		{
-			if (!initialised)
-				throw new InvalidOperationException("Null engine not initialised");
-
-            Check.DataLength(input, inOff, BlockSize, "input buffer too short");
-            Check.OutputLength(output, outOff, BlockSize, "output buffer too short");
-
-            for (int i = 0; i < BlockSize; ++i)
-			{
-				output[outOff + i] = input[inOff + i];
-			}
-
-			return BlockSize;
-		}
-
-        public virtual void Reset()
-		{
-			// nothing needs to be done
-		}
-	}
-}
diff --git a/crypto/src/crypto/engines/RC2Engine.cs b/crypto/src/crypto/engines/RC2Engine.cs
index 4aca1894f..42b29c9b0 100644
--- a/crypto/src/crypto/engines/RC2Engine.cs
+++ b/crypto/src/crypto/engines/RC2Engine.cs
@@ -140,30 +140,17 @@ namespace Org.BouncyCastle.Crypto.Engines
             }
         }
 
-        public virtual void Reset()
-        {
-        }
-
         public virtual string AlgorithmName
         {
             get { return "RC2"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
         {
             return BLOCK_SIZE;
         }
 
-        public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[]	output, int outOff)
         {
             if (workingKey == null)
                 throw new InvalidOperationException("RC2 engine not initialised");
@@ -171,6 +158,16 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (encrypting)
+            {
+                EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+            else
+            {
+                DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+#else
             if (encrypting)
             {
                 EncryptBlock(input, inOff, output, outOff);
@@ -179,26 +176,150 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff);
             }
+#endif
+
+            return BLOCK_SIZE;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (workingKey == null)
+                throw new InvalidOperationException("RC2 engine not initialised");
+
+            Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+            Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+            if (encrypting)
+            {
+                EncryptBlock(input, output);
+            }
+            else
+            {
+                DecryptBlock(input, output);
+            }
 
             return BLOCK_SIZE;
         }
+#endif
 
         /**
         * return the result rotating the 16 bit number in x left by y
         */
-        private int RotateWordLeft(
-            int x,
-            int y)
+        private static int RotateWordLeft(int x, int y)
         {
             x &= 0xffff;
             return (x << y) | (x >> (16 - y));
         }
 
-        private void EncryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int x76, x54, x32, x10;
+
+            x76 = ((input[7] & 0xff) << 8) + (input[6] & 0xff);
+            x54 = ((input[5] & 0xff) << 8) + (input[4] & 0xff);
+            x32 = ((input[3] & 0xff) << 8) + (input[2] & 0xff);
+            x10 = ((input[1] & 0xff) << 8) + (input[0] & 0xff);
+
+            for (int i = 0; i <= 16; i += 4)
+            {
+                x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i], 1);
+                x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i + 1], 2);
+                x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i + 2], 3);
+                x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i + 3], 5);
+            }
+
+            x10 += workingKey[x76 & 63];
+            x32 += workingKey[x10 & 63];
+            x54 += workingKey[x32 & 63];
+            x76 += workingKey[x54 & 63];
+
+            for (int i = 20; i <= 40; i += 4)
+            {
+                x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i], 1);
+                x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i + 1], 2);
+                x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i + 2], 3);
+                x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i + 3], 5);
+            }
+
+            x10 += workingKey[x76 & 63];
+            x32 += workingKey[x10 & 63];
+            x54 += workingKey[x32 & 63];
+            x76 += workingKey[x54 & 63];
+
+            for (int i = 44; i < 64; i += 4)
+            {
+                x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i], 1);
+                x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i + 1], 2);
+                x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i + 2], 3);
+                x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i + 3], 5);
+            }
+
+            output[0] = (byte)x10;
+            output[1] = (byte)(x10 >> 8);
+            output[2] = (byte)x32;
+            output[3] = (byte)(x32 >> 8);
+            output[4] = (byte)x54;
+            output[5] = (byte)(x54 >> 8);
+            output[6] = (byte)x76;
+            output[7] = (byte)(x76 >> 8);
+        }
+
+        private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int x76, x54, x32, x10;
+
+            x76 = ((input[7] & 0xff) << 8) + (input[6] & 0xff);
+            x54 = ((input[5] & 0xff) << 8) + (input[4] & 0xff);
+            x32 = ((input[3] & 0xff) << 8) + (input[2] & 0xff);
+            x10 = ((input[1] & 0xff) << 8) + (input[0] & 0xff);
+
+            for (int i = 60; i >= 44; i -= 4)
+            {
+                x76 = RotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i + 3]);
+                x54 = RotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i + 2]);
+                x32 = RotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i + 1]);
+                x10 = RotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i]);
+            }
+
+            x76 -= workingKey[x54 & 63];
+            x54 -= workingKey[x32 & 63];
+            x32 -= workingKey[x10 & 63];
+            x10 -= workingKey[x76 & 63];
+
+            for (int i = 40; i >= 20; i -= 4)
+            {
+                x76 = RotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i + 3]);
+                x54 = RotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i + 2]);
+                x32 = RotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i + 1]);
+                x10 = RotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i]);
+            }
+
+            x76 -= workingKey[x54 & 63];
+            x54 -= workingKey[x32 & 63];
+            x32 -= workingKey[x10 & 63];
+            x10 -= workingKey[x76 & 63];
+
+            for (int i = 16; i >= 0; i -= 4)
+            {
+                x76 = RotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i + 3]);
+                x54 = RotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i + 2]);
+                x32 = RotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i + 1]);
+                x10 = RotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i]);
+            }
+
+            output[0] = (byte)x10;
+            output[1] = (byte)(x10 >> 8);
+            output[2] = (byte)x32;
+            output[3] = (byte)(x32 >> 8);
+            output[4] = (byte)x54;
+            output[5] = (byte)(x54 >> 8);
+            output[6] = (byte)x76;
+            output[7] = (byte)(x76 >> 8);
+        }
+#else
+        private void EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
             int x76, x54, x32, x10;
 
@@ -209,10 +330,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int i = 0; i <= 16; i += 4)
             {
-                    x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i  ], 1);
-                    x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2);
-                    x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3);
-                    x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5);
+                x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i  ], 1);
+                x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2);
+                x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3);
+                x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5);
             }
 
             x10 += workingKey[x76 & 63];
@@ -222,10 +343,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int i = 20; i <= 40; i += 4)
             {
-                    x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i  ], 1);
-                    x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2);
-                    x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3);
-                    x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5);
+                x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i  ], 1);
+                x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2);
+                x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3);
+                x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5);
             }
 
             x10 += workingKey[x76 & 63];
@@ -235,10 +356,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int i = 44; i < 64; i += 4)
             {
-                    x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i  ], 1);
-                    x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2);
-                    x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3);
-                    x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5);
+                x10 = RotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i  ], 1);
+                x32 = RotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2);
+                x54 = RotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3);
+                x76 = RotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5);
             }
 
             outBytes[outOff + 0] = (byte)x10;
@@ -251,11 +372,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             outBytes[outOff + 7] = (byte)(x76 >> 8);
         }
 
-        private void DecryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+        private void DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
             int x76, x54, x32, x10;
 
@@ -307,5 +424,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             outBytes[outOff + 6] = (byte)x76;
             outBytes[outOff + 7] = (byte)(x76 >> 8);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/RC2WrapEngine.cs b/crypto/src/crypto/engines/RC2WrapEngine.cs
index 5742aa8b7..91ac7ded2 100644
--- a/crypto/src/crypto/engines/RC2WrapEngine.cs
+++ b/crypto/src/crypto/engines/RC2WrapEngine.cs
@@ -42,8 +42,8 @@ namespace Org.BouncyCastle.Crypto.Engines
 		//
 		// checksum digest
 		//
-		IDigest sha1 = new Sha1Digest();
-		byte[] digest = new byte[20];
+		private readonly IDigest sha1 = new Sha1Digest();
+		private readonly byte[] digest = new byte[20];
 
 		/**
 			* Method init
@@ -51,22 +51,19 @@ namespace Org.BouncyCastle.Crypto.Engines
 			* @param forWrapping
 			* @param param
 			*/
-        public virtual void Init(
-			bool				forWrapping,
-			ICipherParameters	parameters)
+        public virtual void Init(bool forWrapping, ICipherParameters parameters)
 		{
 			this.forWrapping = forWrapping;
 			this.engine = new CbcBlockCipher(new RC2Engine());
 
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom pWithR)
 			{
-				ParametersWithRandom pWithR = (ParametersWithRandom)parameters;
 				sr = pWithR.Random;
 				parameters = pWithR.Parameters;
 			}
 			else
 			{
-				sr = new SecureRandom();
+				sr = CryptoServicesRegistrar.GetSecureRandom();
 			}
 
 			if (parameters is ParametersWithIV)
diff --git a/crypto/src/crypto/engines/RC4Engine.cs b/crypto/src/crypto/engines/RC4Engine.cs
index a515bb04e..5ee07c766 100644
--- a/crypto/src/crypto/engines/RC4Engine.cs
+++ b/crypto/src/crypto/engines/RC4Engine.cs
@@ -83,16 +83,40 @@ namespace Org.BouncyCastle.Crypto.Engines
                 x = (x + 1) & 0xff;
                 y = (engineState[x] + y) & 0xff;
 
+                byte sx = engineState[x];
+                byte sy = engineState[y];
+
+                // swap
+                engineState[x] = sy;
+                engineState[y] = sx;
+
+                // xor
+                output[i+outOff] = (byte)(input[i + inOff] ^ engineState[(sx + sy) & 0xff]);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                x = (x + 1) & 0xff;
+                y = (engineState[x] + y) & 0xff;
+
+                byte sx = engineState[x];
+                byte sy = engineState[y];
+
                 // swap
-                byte tmp = engineState[x];
-                engineState[x] = engineState[y];
-                engineState[y] = tmp;
+                engineState[x] = sy;
+                engineState[y] = sx;
 
                 // xor
-                output[i+outOff] = (byte)(input[i + inOff]
-                        ^ engineState[(engineState[x] + engineState[y]) & 0xff]);
+                output[i] = (byte)(input[i] ^ engineState[(sx + sy) & 0xff]);
             }
         }
+#endif
 
         public virtual void Reset()
         {
diff --git a/crypto/src/crypto/engines/RC532Engine.cs b/crypto/src/crypto/engines/RC532Engine.cs
index d1c29e624..e3d0708a8 100644
--- a/crypto/src/crypto/engines/RC532Engine.cs
+++ b/crypto/src/crypto/engines/RC532Engine.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
@@ -46,7 +47,6 @@ namespace Org.BouncyCastle.Crypto.Engines
         public RC532Engine()
         {
             _noRounds     = 12;         // the default
-//            _S            = null;
         }
 
         public virtual string AlgorithmName
@@ -54,11 +54,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "RC5-32"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
         {
             return 2 * 4;
@@ -72,23 +67,17 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
         {
-            if (typeof(RC5Parameters).IsInstanceOfType(parameters))
+            if (parameters is RC5Parameters rc5Parameters)
             {
-                RC5Parameters p = (RC5Parameters)parameters;
-
-                _noRounds = p.Rounds;
+                _noRounds = rc5Parameters.Rounds;
 
-                SetKey(p.GetKey());
+                SetKey(rc5Parameters.GetKey());
             }
-            else if (typeof(KeyParameter).IsInstanceOfType(parameters))
+            else if (parameters is KeyParameter keyParameter)
             {
-                KeyParameter p = (KeyParameter)parameters;
-
-                SetKey(p.GetKey());
+                SetKey(keyParameter.GetKey());
             }
             else
             {
@@ -98,28 +87,34 @@ namespace Org.BouncyCastle.Crypto.Engines
             this.forEncryption = forEncryption;
         }
 
-        public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[]	output, int outOff)
         {
-            return (forEncryption)
-				?	EncryptBlock(input, inOff, output, outOff)
-				:	DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return forEncryption
+                ? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+                : DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+            return forEncryption
+				? EncryptBlock(input, inOff, output, outOff)
+				: DecryptBlock(input, inOff, output, outOff);
+#endif
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            return forEncryption
+                ? EncryptBlock(input, output)
+                : DecryptBlock(input, output);
         }
+#endif
 
         /**
         * Re-key the cipher.
         *
         * @param  key  the key to be used
         */
-        private void SetKey(
-            byte[] key)
+        private void SetKey(byte[] key)
         {
             //
             // KEY EXPANSION:
@@ -133,7 +128,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             //   of K. Any unfilled byte positions in L are zeroed. In the
             //   case that b = c = 0, set c = 1 and L[0] = 0.
             //
-            int[]   L = new int[(key.Length + (4 - 1)) / 4];
+            int[]   L = new int[(key.Length + 3) / 4];
 
             for (int i = 0; i != key.Length; i++)
             {
@@ -175,120 +170,81 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int k = 0; k < iter; k++)
             {
-                A = _S[ii] = RotateLeft(_S[ii] + A + B, 3);
-                B =  L[jj] = RotateLeft( L[jj] + A + B, A+B);
+                A = _S[ii] = Integers.RotateLeft(_S[ii] + A + B, 3);
+                B =  L[jj] = Integers.RotateLeft(L[jj] + A + B, A + B);
                 ii = (ii+1) % _S.Length;
                 jj = (jj+1) %  L.Length;
             }
         }
 
-        /**
-        * Encrypt the given block starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        *
-        * @param  in     in byte buffer containing data to encrypt
-        * @param  inOff  offset into src buffer
-        * @param  out     out buffer where encrypted data is written
-        * @param  outOff  offset into out buffer
-        */
-        private int EncryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            int A = BytesToWord(input, inOff) + _S[0];
-            int B = BytesToWord(input, inOff + 4) + _S[1];
+            int A = (int)Pack.LE_To_UInt32(input) + _S[0];
+            int B = (int)Pack.LE_To_UInt32(input[4..]) + _S[1];
 
             for (int i = 1; i <= _noRounds; i++)
             {
-                A = RotateLeft(A ^ B, B) + _S[2*i];
-                B = RotateLeft(B ^ A, A) + _S[2*i+1];
+                A = Integers.RotateLeft(A ^ B, B) + _S[2*i];
+                B = Integers.RotateLeft(B ^ A, A) + _S[2*i+1];
             }
 
-            WordToBytes(A, outBytes, outOff);
-            WordToBytes(B, outBytes, outOff + 4);
+            Pack.UInt32_To_LE((uint)A, output);
+            Pack.UInt32_To_LE((uint)B, output[4..]);
 
-            return 2 * 4;
+            return 8;
         }
 
-        private int DecryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            int A = BytesToWord(input, inOff);
-            int B = BytesToWord(input, inOff + 4);
+            int A = (int)Pack.LE_To_UInt32(input);
+            int B = (int)Pack.LE_To_UInt32(input[4..]);
 
             for (int i = _noRounds; i >= 1; i--)
             {
-                B = RotateRight(B - _S[2*i+1], A) ^ A;
-                A = RotateRight(A - _S[2*i],   B) ^ B;
+                B = Integers.RotateRight(B - _S[2*i+1], A) ^ A;
+                A = Integers.RotateRight(A - _S[2*i],   B) ^ B;
             }
 
-            WordToBytes(A - _S[0], outBytes, outOff);
-            WordToBytes(B - _S[1], outBytes, outOff + 4);
+            Pack.UInt32_To_LE((uint)(A - _S[0]), output);
+            Pack.UInt32_To_LE((uint)(B - _S[1]), output[4..]);
 
-            return 2 * 4;
+            return 8;
         }
+#else
+        private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
+        {
+            int A = (int)Pack.LE_To_UInt32(input, inOff) + _S[0];
+            int B = (int)Pack.LE_To_UInt32(input, inOff + 4) + _S[1];
 
+            for (int i = 1; i <= _noRounds; i++)
+            {
+                A = Integers.RotateLeft(A ^ B, B) + _S[2*i];
+                B = Integers.RotateLeft(B ^ A, A) + _S[2*i+1];
+            }
 
-        //////////////////////////////////////////////////////////////
-        //
-        // PRIVATE Helper Methods
-        //
-        //////////////////////////////////////////////////////////////
+            Pack.UInt32_To_LE((uint)A, outBytes, outOff);
+            Pack.UInt32_To_LE((uint)B, outBytes, outOff + 4);
 
-        /**
-        * Perform a left "spin" of the word. The rotation of the given
-        * word <em>x</em> is rotated left by <em>y</em> bits.
-        * Only the <em>lg(32)</em> low-order bits of <em>y</em>
-        * are used to determine the rotation amount. Here it is
-        * assumed that the wordsize used is a power of 2.
-        *
-        * @param  x  word to rotate
-        * @param  y    number of bits to rotate % 32
-        */
-        private int RotateLeft(int x, int y) {
-            return ((int)  (  (uint) (x << (y & (32-1))) |
-                              ((uint) x >> (32 - (y & (32-1)))) )
-                   );
+            return 8;
         }
 
-        /**
-        * Perform a right "spin" of the word. The rotation of the given
-        * word <em>x</em> is rotated left by <em>y</em> bits.
-        * Only the <em>lg(32)</em> low-order bits of <em>y</em>
-        * are used to determine the rotation amount. Here it is
-        * assumed that the wordsize used is a power of 2.
-        *
-        * @param  x  word to rotate
-        * @param  y    number of bits to rotate % 32
-        */
-        private int RotateRight(int x, int y) {
-            return ((int) (     ((uint) x >> (y & (32-1))) |
-                                (uint) (x << (32 - (y & (32-1))))   )
-                   );
-        }
-
-        private int BytesToWord(
-            byte[]  src,
-            int     srcOff)
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            return (src[srcOff] & 0xff) | ((src[srcOff + 1] & 0xff) << 8)
-                | ((src[srcOff + 2] & 0xff) << 16) | ((src[srcOff + 3] & 0xff) << 24);
-        }
+            int A = (int)Pack.LE_To_UInt32(input, inOff);
+            int B = (int)Pack.LE_To_UInt32(input, inOff + 4);
 
-        private void WordToBytes(
-            int    word,
-            byte[]  dst,
-            int     dstOff)
-        {
-            dst[dstOff] = (byte)word;
-            dst[dstOff + 1] = (byte)(word >> 8);
-            dst[dstOff + 2] = (byte)(word >> 16);
-            dst[dstOff + 3] = (byte)(word >> 24);
+            for (int i = _noRounds; i >= 1; i--)
+            {
+                B = Integers.RotateRight(B - _S[2*i+1], A) ^ A;
+                A = Integers.RotateRight(A - _S[2*i],   B) ^ B;
+            }
+
+            Pack.UInt32_To_LE((uint)(A - _S[0]), outBytes, outOff);
+            Pack.UInt32_To_LE((uint)(B - _S[1]), outBytes, outOff + 4);
+
+            return 8;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/RC564Engine.cs b/crypto/src/crypto/engines/RC564Engine.cs
index 097fd60ba..8dffc3f43 100644
--- a/crypto/src/crypto/engines/RC564Engine.cs
+++ b/crypto/src/crypto/engines/RC564Engine.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
@@ -15,9 +16,6 @@ namespace Org.BouncyCastle.Crypto.Engines
     public class RC564Engine
 		: IBlockCipher
     {
-        private static readonly int wordSize = 64;
-        private static readonly int bytesPerWord = wordSize / 8;
-
         /*
         * the number of rounds to perform
         */
@@ -57,14 +55,9 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "RC5-64"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
         {
-            return 2 * bytesPerWord;
+            return 16;
         }
 
         /**
@@ -75,45 +68,46 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public virtual void Init(
-            bool             forEncryption,
-            ICipherParameters    parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
         {
-            if (!(typeof(RC5Parameters).IsInstanceOfType(parameters)))
-            {
+            if (!(parameters is RC5Parameters rc5Parameters))
                 throw new ArgumentException("invalid parameter passed to RC564 init - " + Platform.GetTypeName(parameters));
-            }
-
-            RC5Parameters       p = (RC5Parameters)parameters;
 
             this.forEncryption = forEncryption;
 
-            _noRounds     = p.Rounds;
+            _noRounds = rc5Parameters.Rounds;
 
-            SetKey(p.GetKey());
+            SetKey(rc5Parameters.GetKey());
         }
 
-        public virtual int ProcessBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  output,
-            int     outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-            return (forEncryption) ? EncryptBlock(input, inOff, output, outOff)
-                                        : DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return forEncryption
+                ? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+                : DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+            return forEncryption
+                ? EncryptBlock(input, inOff, output, outOff)
+                : DecryptBlock(input, inOff, output, outOff);
+#endif
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            return forEncryption
+                ? EncryptBlock(input, output)
+                : DecryptBlock(input, output);
         }
+#endif
 
         /**
         * Re-key the cipher.
         *
         * @param  key  the key to be used
         */
-        private void SetKey(
-            byte[]      key)
+        private void SetKey(byte[] key)
         {
             //
             // KEY EXPANSION:
@@ -127,11 +121,11 @@ namespace Org.BouncyCastle.Crypto.Engines
             //   of K. Any unfilled byte positions in L are zeroed. In the
             //   case that b = c = 0, set c = 1 and L[0] = 0.
             //
-            long[]   L = new long[(key.Length + (bytesPerWord - 1)) / bytesPerWord];
+            long[] L = new long[(key.Length + 7) / 8];
 
             for (int i = 0; i != key.Length; i++)
             {
-                L[i / bytesPerWord] += (long)(key[i] & 0xff) << (8 * (i % bytesPerWord));
+                L[i / 8] += (long)(key[i] & 0xff) << (8 * (i % 8));
             }
 
             //
@@ -169,127 +163,81 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int k = 0; k < iter; k++)
             {
-                A = _S[ii] = RotateLeft(_S[ii] + A + B, 3);
-                B =  L[jj] = RotateLeft( L[jj] + A + B, A+B);
+                A = _S[ii] = Longs.RotateLeft(_S[ii] + A + B, 3);
+                B =  L[jj] = Longs.RotateLeft(L[jj] + A + B, (int)(A + B));
                 ii = (ii+1) % _S.Length;
                 jj = (jj+1) %  L.Length;
             }
         }
 
-        /**
-        * Encrypt the given block starting at the given offset and place
-        * the result in the provided buffer starting at the given offset.
-        *
-        * @param  in      in byte buffer containing data to encrypt
-        * @param  inOff   offset into src buffer
-        * @param  out     out buffer where encrypted data is written
-        * @param  outOff  offset into out buffer
-        */
-        private int EncryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            long A = BytesToWord(input, inOff) + _S[0];
-            long B = BytesToWord(input, inOff + bytesPerWord) + _S[1];
+            long A = (long)Pack.LE_To_UInt64(input) + _S[0];
+            long B = (long)Pack.LE_To_UInt64(input[8..]) + _S[1];
 
             for (int i = 1; i <= _noRounds; i++)
             {
-                A = RotateLeft(A ^ B, B) + _S[2*i];
-                B = RotateLeft(B ^ A, A) + _S[2*i+1];
+                A = Longs.RotateLeft(A ^ B, (int)B) + _S[2*i];
+                B = Longs.RotateLeft(B ^ A, (int)A) + _S[2*i+1];
             }
 
-            WordToBytes(A, outBytes, outOff);
-            WordToBytes(B, outBytes, outOff + bytesPerWord);
+            Pack.UInt64_To_LE((ulong)A, output);
+            Pack.UInt64_To_LE((ulong)B, output[8..]);
 
-            return 2 * bytesPerWord;
+            return 16;
         }
 
-        private int DecryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            long A = BytesToWord(input, inOff);
-            long B = BytesToWord(input, inOff + bytesPerWord);
+            long A = (long)Pack.LE_To_UInt64(input);
+            long B = (long)Pack.LE_To_UInt64(input[8..]);
 
             for (int i = _noRounds; i >= 1; i--)
             {
-                B = RotateRight(B - _S[2*i+1], A) ^ A;
-                A = RotateRight(A - _S[2*i],   B) ^ B;
+                B = Longs.RotateRight(B - _S[2*i+1], (int)A) ^ A;
+                A = Longs.RotateRight(A - _S[2*i], (int)B) ^ B;
             }
 
-            WordToBytes(A - _S[0], outBytes, outOff);
-            WordToBytes(B - _S[1], outBytes, outOff + bytesPerWord);
+            Pack.UInt64_To_LE((ulong)(A - _S[0]), output);
+            Pack.UInt64_To_LE((ulong)(B - _S[1]), output[8..]);
 
-            return 2 * bytesPerWord;
+            return 16;
         }
+#else
+        private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
+        {
+            long A = (long)Pack.LE_To_UInt64(input, inOff) + _S[0];
+            long B = (long)Pack.LE_To_UInt64(input, inOff + 8) + _S[1];
 
+            for (int i = 1; i <= _noRounds; i++)
+            {
+                A = Longs.RotateLeft(A ^ B, (int)B) + _S[2*i];
+                B = Longs.RotateLeft(B ^ A, (int)A) + _S[2*i+1];
+            }
 
-        //////////////////////////////////////////////////////////////
-        //
-        // PRIVATE Helper Methods
-        //
-        //////////////////////////////////////////////////////////////
+            Pack.UInt64_To_LE((ulong)A, outBytes, outOff);
+            Pack.UInt64_To_LE((ulong)B, outBytes, outOff + 8);
 
-        /**
-        * Perform a left "spin" of the word. The rotation of the given
-        * word <em>x</em> is rotated left by <em>y</em> bits.
-        * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em>
-        * are used to determine the rotation amount. Here it is
-        * assumed that the wordsize used is a power of 2.
-        *
-        * @param  x  word to rotate
-        * @param  y    number of bits to rotate % wordSize
-        */
-        private long RotateLeft(long x, long y) {
-            return ((long) (    (ulong) (x << (int) (y & (wordSize-1))) |
-                                ((ulong) x >> (int) (wordSize - (y & (wordSize-1)))))
-                   );
-        }
-
-        /**
-        * Perform a right "spin" of the word. The rotation of the given
-        * word <em>x</em> is rotated left by <em>y</em> bits.
-        * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em>
-        * are used to determine the rotation amount. Here it is
-        * assumed that the wordsize used is a power of 2.
-        *
-        * @param x word to rotate
-        * @param y number of bits to rotate % wordSize
-        */
-        private long RotateRight(long x, long y) {
-            return ((long) (    ((ulong) x >> (int) (y & (wordSize-1))) |
-                                (ulong) (x << (int) (wordSize - (y & (wordSize-1)))))
-                   );
+            return 16;
         }
 
-        private long BytesToWord(
-            byte[]  src,
-            int     srcOff)
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            long    word = 0;
+            long A = (long)Pack.LE_To_UInt64(input, inOff);
+            long B = (long)Pack.LE_To_UInt64(input, inOff + 8);
 
-            for (int i = bytesPerWord - 1; i >= 0; i--)
+            for (int i = _noRounds; i >= 1; i--)
             {
-                word = (word << 8) + (src[i + srcOff] & 0xff);
+                B = Longs.RotateRight(B - _S[2*i+1], (int)A) ^ A;
+                A = Longs.RotateRight(A - _S[2*i], (int)B) ^ B;
             }
 
-            return word;
-        }
+            Pack.UInt64_To_LE((ulong)(A - _S[0]), outBytes, outOff);
+            Pack.UInt64_To_LE((ulong)(B - _S[1]), outBytes, outOff + 8);
 
-        private void WordToBytes(
-            long    word,
-            byte[]  dst,
-            int     dstOff)
-        {
-            for (int i = 0; i < bytesPerWord; i++)
-            {
-                dst[i + dstOff] = (byte)word;
-                word = (long) ((ulong) word >> 8);
-            }
+            return 16;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/RC6Engine.cs b/crypto/src/crypto/engines/RC6Engine.cs
index 9aeb1e7cb..e6a3f8176 100644
--- a/crypto/src/crypto/engines/RC6Engine.cs
+++ b/crypto/src/crypto/engines/RC6Engine.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
@@ -11,9 +12,6 @@ namespace Org.BouncyCastle.Crypto.Engines
     public class RC6Engine
 		: IBlockCipher
     {
-        private static readonly int wordSize = 32;
-        private static readonly int bytesPerWord = wordSize / 8;
-
         /*
         * the number of rounds to perform
         */
@@ -46,7 +44,6 @@ namespace Org.BouncyCastle.Crypto.Engines
         */
         public RC6Engine()
         {
-//            _S            = null;
         }
 
         public virtual string AlgorithmName
@@ -54,14 +51,9 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "RC6"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
         {
-            return 4 * bytesPerWord;
+            return 16;
         }
 
         /**
@@ -72,40 +64,51 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
         {
-            if (!(parameters is KeyParameter))
+            if (!(parameters is KeyParameter keyParameter))
                 throw new ArgumentException("invalid parameter passed to RC6 init - " + Platform.GetTypeName(parameters));
 
             this.forEncryption = forEncryption;
 
-			KeyParameter p = (KeyParameter)parameters;
-			SetKey(p.GetKey());
+			SetKey(keyParameter.GetKey());
         }
 
-        public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-			int blockSize = GetBlockSize();
 			if (_S == null)
 				throw new InvalidOperationException("RC6 engine not initialised");
 
+            int blockSize = GetBlockSize();
             Check.DataLength(input, inOff, blockSize, "input buffer too short");
             Check.OutputLength(output, outOff, blockSize, "output buffer too short");
 
-            return (forEncryption)
-				?	EncryptBlock(input, inOff, output, outOff)
-				:	DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return forEncryption
+                ? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+                : DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+            return forEncryption
+				? EncryptBlock(input, inOff, output, outOff)
+				: DecryptBlock(input, inOff, output, outOff);
+#endif
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (_S == null)
+                throw new InvalidOperationException("RC6 engine not initialised");
+
+            int blockSize = GetBlockSize();
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            return forEncryption
+                ? EncryptBlock(input, output)
+                : DecryptBlock(input, output);
         }
+#endif
 
         /**
         * Re-key the cipher.
@@ -128,17 +131,17 @@ namespace Org.BouncyCastle.Crypto.Engines
             //   case that b = c = 0, set c = 1 and L[0] = 0.
             //
             // compute number of dwords
-            int c = (key.Length + (bytesPerWord - 1)) / bytesPerWord;
+            int c = (key.Length + 3) / 4;
             if (c == 0)
             {
                 c = 1;
             }
-            int[]   L = new int[(key.Length + bytesPerWord - 1) / bytesPerWord];
+            int[]   L = new int[(key.Length + 3) / 4];
 
             // load all key bytes into array of key dwords
             for (int i = key.Length - 1; i >= 0; i--)
             {
-                L[i / bytesPerWord] = (L[i / bytesPerWord] << 8) + (key[i] & 0xff);
+                L[i / 4] = (L[i / 4] << 8) + (key[i] & 0xff);
             }
 
             //
@@ -178,24 +181,21 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int k = 0; k < iter; k++)
             {
-                A = _S[ii] = RotateLeft(_S[ii] + A + B, 3);
-                B =  L[jj] = RotateLeft( L[jj] + A + B, A+B);
+                A = _S[ii] = Integers.RotateLeft(_S[ii] + A + B, 3);
+                B =  L[jj] = Integers.RotateLeft( L[jj] + A + B, A + B);
                 ii = (ii+1) % _S.Length;
                 jj = (jj+1) %  L.Length;
             }
         }
 
-        private int EncryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
             // load A,B,C and D registers from in.
-            int A = BytesToWord(input, inOff);
-            int B = BytesToWord(input, inOff + bytesPerWord);
-            int C = BytesToWord(input, inOff + bytesPerWord*2);
-            int D = BytesToWord(input, inOff + bytesPerWord*3);
+            int A = (int)Pack.LE_To_UInt32(input);
+            int B = (int)Pack.LE_To_UInt32(input[4..]);
+            int C = (int)Pack.LE_To_UInt32(input[8..]);
+            int D = (int)Pack.LE_To_UInt32(input[12..]);
 
             // Do pseudo-round #0: pre-whitening of B and D
             B += _S[0];
@@ -204,21 +204,21 @@ namespace Org.BouncyCastle.Crypto.Engines
             // perform round #1,#2 ... #ROUNDS of encryption
             for (int i = 1; i <= _noRounds; i++)
             {
-                int t = 0,u = 0;
+                int t = 0, u = 0;
 
-                t = B*(2*B+1);
-                t = RotateLeft(t,5);
+                t = B * (2 * B + 1);
+                t = Integers.RotateLeft(t, 5);
 
-                u = D*(2*D+1);
-                u = RotateLeft(u,5);
+                u = D * (2 * D + 1);
+                u = Integers.RotateLeft(u, 5);
 
                 A ^= t;
-                A = RotateLeft(A,u);
-                A += _S[2*i];
+                A = Integers.RotateLeft(A, u);
+                A += _S[2 * i];
 
                 C ^= u;
-                C = RotateLeft(C,t);
-                C += _S[2*i+1];
+                C = Integers.RotateLeft(C, t);
+                C += _S[2 * i + 1];
 
                 int temp = A;
                 A = B;
@@ -226,39 +226,36 @@ namespace Org.BouncyCastle.Crypto.Engines
                 C = D;
                 D = temp;
             }
+
             // do pseudo-round #(ROUNDS+1) : post-whitening of A and C
-            A += _S[2*_noRounds+2];
-            C += _S[2*_noRounds+3];
+            A += _S[2 * _noRounds + 2];
+            C += _S[2 * _noRounds + 3];
 
             // store A, B, C and D registers to out
-            WordToBytes(A, outBytes, outOff);
-            WordToBytes(B, outBytes, outOff + bytesPerWord);
-            WordToBytes(C, outBytes, outOff + bytesPerWord*2);
-            WordToBytes(D, outBytes, outOff + bytesPerWord*3);
+            Pack.UInt32_To_LE((uint)A, output);
+            Pack.UInt32_To_LE((uint)B, output[4..]);
+            Pack.UInt32_To_LE((uint)C, output[8..]);
+            Pack.UInt32_To_LE((uint)D, output[12..]);
 
-            return 4 * bytesPerWord;
+            return 16;
         }
 
-        private int DecryptBlock(
-            byte[]  input,
-            int     inOff,
-            byte[]  outBytes,
-            int     outOff)
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
             // load A,B,C and D registers from out.
-            int A = BytesToWord(input, inOff);
-            int B = BytesToWord(input, inOff + bytesPerWord);
-            int C = BytesToWord(input, inOff + bytesPerWord*2);
-            int D = BytesToWord(input, inOff + bytesPerWord*3);
+            int A = (int)Pack.LE_To_UInt32(input);
+            int B = (int)Pack.LE_To_UInt32(input[4..]);
+            int C = (int)Pack.LE_To_UInt32(input[8..]);
+            int D = (int)Pack.LE_To_UInt32(input[12..]);
 
             // Undo pseudo-round #(ROUNDS+1) : post whitening of A and C
-            C -= _S[2*_noRounds+3];
-            A -= _S[2*_noRounds+2];
+            C -= _S[2 * _noRounds + 3];
+            A -= _S[2 * _noRounds + 2];
 
             // Undo round #ROUNDS, .., #2,#1 of encryption
             for (int i = _noRounds; i >= 1; i--)
             {
-                int t=0,u = 0;
+                int t = 0, u = 0;
 
                 int temp = D;
                 D = C;
@@ -266,96 +263,133 @@ namespace Org.BouncyCastle.Crypto.Engines
                 B = A;
                 A = temp;
 
-                t = B*(2*B+1);
-                t = RotateLeft(t, LGW);
+                t = B * (2 * B + 1);
+                t = Integers.RotateLeft(t, LGW);
 
-                u = D*(2*D+1);
-                u = RotateLeft(u, LGW);
+                u = D * (2 * D + 1);
+                u = Integers.RotateLeft(u, LGW);
 
-                C -= _S[2*i+1];
-                C = RotateRight(C,t);
+                C -= _S[2 * i + 1];
+                C = Integers.RotateRight(C, t);
                 C ^= u;
 
-                A -= _S[2*i];
-                A = RotateRight(A,u);
+                A -= _S[2 * i];
+                A = Integers.RotateRight(A, u);
                 A ^= t;
-
             }
+
             // Undo pseudo-round #0: pre-whitening of B and D
             D -= _S[1];
             B -= _S[0];
 
-            WordToBytes(A, outBytes, outOff);
-            WordToBytes(B, outBytes, outOff + bytesPerWord);
-            WordToBytes(C, outBytes, outOff + bytesPerWord*2);
-            WordToBytes(D, outBytes, outOff + bytesPerWord*3);
+            Pack.UInt32_To_LE((uint)A, output);
+            Pack.UInt32_To_LE((uint)B, output[4..]);
+            Pack.UInt32_To_LE((uint)C, output[8..]);
+            Pack.UInt32_To_LE((uint)D, output[12..]);
 
-            return 4 * bytesPerWord;
+            return 16;
         }
+#else
+        private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
+        {
+            // load A,B,C and D registers from in.
+            int A = (int)Pack.LE_To_UInt32(input, inOff);
+            int B = (int)Pack.LE_To_UInt32(input, inOff + 4);
+            int C = (int)Pack.LE_To_UInt32(input, inOff + 8);
+            int D = (int)Pack.LE_To_UInt32(input, inOff + 12);
 
+            // Do pseudo-round #0: pre-whitening of B and D
+            B += _S[0];
+            D += _S[1];
 
-        //////////////////////////////////////////////////////////////
-        //
-        // PRIVATE Helper Methods
-        //
-        //////////////////////////////////////////////////////////////
+            // perform round #1,#2 ... #ROUNDS of encryption
+            for (int i = 1; i <= _noRounds; i++)
+            {
+                int t = 0,u = 0;
 
-        /**
-        * Perform a left "spin" of the word. The rotation of the given
-        * word <em>x</em> is rotated left by <em>y</em> bits.
-        * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em>
-        * are used to determine the rotation amount. Here it is
-        * assumed that the wordsize used is a power of 2.
-        *
-        * @param x word to rotate
-        * @param y number of bits to rotate % wordSize
-        */
-        private int RotateLeft(int x, int y)
-        {
-            return ((int)((uint)(x << (y & (wordSize-1)))
-				| ((uint) x >> (wordSize - (y & (wordSize-1))))));
-        }
+                t = B*(2*B+1);
+                t = Integers.RotateLeft(t,5);
 
-        /**
-        * Perform a right "spin" of the word. The rotation of the given
-        * word <em>x</em> is rotated left by <em>y</em> bits.
-        * Only the <em>lg(wordSize)</em> low-order bits of <em>y</em>
-        * are used to determine the rotation amount. Here it is
-        * assumed that the wordsize used is a power of 2.
-        *
-        * @param x word to rotate
-        * @param y number of bits to rotate % wordSize
-        */
-        private int RotateRight(int x, int y) 
-		{
-            return ((int)(((uint) x >> (y & (wordSize-1)))
-				| (uint)(x << (wordSize - (y & (wordSize-1))))));
-        }
+                u = D*(2*D+1);
+                u = Integers.RotateLeft(u,5);
 
-        private int BytesToWord(
-            byte[]	src,
-            int		srcOff)
-        {
-            int word = 0;
+                A ^= t;
+                A = Integers.RotateLeft(A,u);
+                A += _S[2*i];
 
-            for (int i = bytesPerWord - 1; i >= 0; i--)
-            {
-                word = (word << 8) + (src[i + srcOff] & 0xff);
+                C ^= u;
+                C = Integers.RotateLeft(C,t);
+                C += _S[2*i+1];
+
+                int temp = A;
+                A = B;
+                B = C;
+                C = D;
+                D = temp;
             }
 
-            return word;
+            // do pseudo-round #(ROUNDS+1) : post-whitening of A and C
+            A += _S[2*_noRounds+2];
+            C += _S[2*_noRounds+3];
+
+            // store A, B, C and D registers to out
+            Pack.UInt32_To_LE((uint)A, outBytes, outOff);
+            Pack.UInt32_To_LE((uint)B, outBytes, outOff + 4);
+            Pack.UInt32_To_LE((uint)C, outBytes, outOff + 8);
+            Pack.UInt32_To_LE((uint)D, outBytes, outOff + 12);
+
+            return 16;
         }
 
-        private void WordToBytes(
-            int		word,
-            byte[]	dst,
-            int		dstOff)
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            for (int i = 0; i < bytesPerWord; i++)
+            // load A,B,C and D registers from out.
+            int A = (int)Pack.LE_To_UInt32(input, inOff);
+            int B = (int)Pack.LE_To_UInt32(input, inOff + 4);
+            int C = (int)Pack.LE_To_UInt32(input, inOff + 8);
+            int D = (int)Pack.LE_To_UInt32(input, inOff + 12);
+
+            // Undo pseudo-round #(ROUNDS+1) : post whitening of A and C
+            C -= _S[2*_noRounds+3];
+            A -= _S[2*_noRounds+2];
+
+            // Undo round #ROUNDS, .., #2,#1 of encryption
+            for (int i = _noRounds; i >= 1; i--)
             {
-                dst[i + dstOff] = (byte)word;
-                word = (int) ((uint) word >> 8);
+                int t=0,u = 0;
+
+                int temp = D;
+                D = C;
+                C = B;
+                B = A;
+                A = temp;
+
+                t = B*(2*B+1);
+                t = Integers.RotateLeft(t, LGW);
+
+                u = D*(2*D+1);
+                u = Integers.RotateLeft(u, LGW);
+
+                C -= _S[2*i+1];
+                C = Integers.RotateRight(C,t);
+                C ^= u;
+
+                A -= _S[2*i];
+                A = Integers.RotateRight(A,u);
+                A ^= t;
             }
+
+            // Undo pseudo-round #0: pre-whitening of B and D
+            D -= _S[1];
+            B -= _S[0];
+
+            Pack.UInt32_To_LE((uint)A, outBytes, outOff);
+            Pack.UInt32_To_LE((uint)B, outBytes, outOff + 4);
+            Pack.UInt32_To_LE((uint)C, outBytes, outOff + 8);
+            Pack.UInt32_To_LE((uint)D, outBytes, outOff + 12);
+
+            return 16;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/RFC3211WrapEngine.cs b/crypto/src/crypto/engines/RFC3211WrapEngine.cs
index 86480145c..42027cf25 100644
--- a/crypto/src/crypto/engines/RFC3211WrapEngine.cs
+++ b/crypto/src/crypto/engines/RFC3211WrapEngine.cs
@@ -24,24 +24,20 @@ namespace Org.BouncyCastle.Crypto.Engines
 			this.engine = new CbcBlockCipher(engine);
 		}
 
-        public virtual void Init(
-			bool				forWrapping,
-			ICipherParameters	param)
+        public virtual void Init(bool forWrapping, ICipherParameters param)
 		{
 			this.forWrapping = forWrapping;
 
-			if (param is ParametersWithRandom)
+			if (param is ParametersWithRandom withRandom)
 			{
-				ParametersWithRandom p = (ParametersWithRandom)param;
-
-                this.rand = p.Random;
-                this.param = p.Parameters as ParametersWithIV;
+                this.rand = withRandom.Random;
+                this.param = withRandom.Parameters as ParametersWithIV;
 			}
 			else
 			{
 				if (forWrapping)
 				{
-					rand = new SecureRandom();
+					rand = CryptoServicesRegistrar.GetSecureRandom();
 				}
 
                 this.param = param as ParametersWithIV;
@@ -53,7 +49,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
         public virtual string AlgorithmName
 		{
-			get { return engine.GetUnderlyingCipher().AlgorithmName + "/RFC3211Wrap"; }
+			get { return engine.UnderlyingCipher.AlgorithmName + "/RFC3211Wrap"; }
 		}
 
         public virtual byte[] Wrap(
diff --git a/crypto/src/crypto/engines/RSABlindedEngine.cs b/crypto/src/crypto/engines/RSABlindedEngine.cs
index 637bf3cc0..cdc0a7844 100644
--- a/crypto/src/crypto/engines/RSABlindedEngine.cs
+++ b/crypto/src/crypto/engines/RSABlindedEngine.cs
@@ -39,16 +39,12 @@ namespace Org.BouncyCastle.Crypto.Engines
          * @param forEncryption true if we are encrypting, false otherwise.
          * @param param the necessary RSA key parameters.
          */
-        public virtual void Init(
-            bool forEncryption,
-            ICipherParameters param)
+        public virtual void Init(bool forEncryption, ICipherParameters param)
         {
             core.Init(forEncryption, param);
 
-            if (param is ParametersWithRandom)
+            if (param is ParametersWithRandom rParam)
             {
-                ParametersWithRandom rParam = (ParametersWithRandom)param;
-
                 this.key = (RsaKeyParameters)rParam.Parameters;
 
                 if (key is RsaPrivateCrtKeyParameters)
@@ -66,7 +62,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
                 if (key is RsaPrivateCrtKeyParameters)
                 {
-                    this.random = new SecureRandom();
+                    this.random = CryptoServicesRegistrar.GetSecureRandom();
                 }
                 else
                 {
diff --git a/crypto/src/crypto/engines/RSACoreEngine.cs b/crypto/src/crypto/engines/RSACoreEngine.cs
index 5f6e98eea..bd3d62f7c 100644
--- a/crypto/src/crypto/engines/RSACoreEngine.cs
+++ b/crypto/src/crypto/engines/RSACoreEngine.cs
@@ -3,6 +3,7 @@ using System;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
 {
@@ -103,28 +104,13 @@ namespace Org.BouncyCastle.Crypto.Engines
 			return input;
 		}
 
-        public virtual byte[] ConvertOutput(
-			BigInteger result)
+        public virtual byte[] ConvertOutput(BigInteger result)
 		{
             CheckInitialised();
 
-            byte[] output = result.ToByteArrayUnsigned();
-
-			if (forEncryption)
-			{
-				int outSize = GetOutputBlockSize();
-
-				// TODO To avoid this, create version of BigInteger.ToByteArray that
-				// writes to an existing array
-				if (output.Length < outSize) // have ended up with less bytes than normal, lengthen
-				{
-					byte[] tmp = new byte[outSize];
-					output.CopyTo(tmp, tmp.Length - output.Length);
-					output = tmp;
-				}
-			}
-
-			return output;
+			return forEncryption
+				? BigIntegers.AsUnsignedByteArray(GetOutputBlockSize(), result)
+				: BigIntegers.AsUnsignedByteArray(result);
 		}
 
         public virtual BigInteger ProcessBlock(
diff --git a/crypto/src/crypto/engines/RijndaelEngine.cs b/crypto/src/crypto/engines/RijndaelEngine.cs
index 7025cb5dc..422664ce0 100644
--- a/crypto/src/crypto/engines/RijndaelEngine.cs
+++ b/crypto/src/crypto/engines/RijndaelEngine.cs
@@ -591,21 +591,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "Rijndael"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return BC / 2;
 		}
 
-        public virtual int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-			byte[]	output,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
 		{
 			if (workingKey == null)
 				throw new InvalidOperationException("Rijndael engine not initialised");
@@ -613,7 +604,11 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, (BC / 2), "input buffer too short");
             Check.OutputLength(output, outOff, (BC / 2), "output buffer too short");
 
-            UnPackBlock(input, inOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			UnPackBlock(input.AsSpan(inOff));
+#else
+			UnPackBlock(input, inOff);
+#endif
 
 			if (forEncryption)
 			{
@@ -624,20 +619,76 @@ namespace Org.BouncyCastle.Crypto.Engines
 				DecryptBlock(workingKey);
 			}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			PackBlock(output.AsSpan(outOff));
+#else
 			PackBlock(output, outOff);
+#endif
 
 			return BC / 2;
 		}
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
+			if (workingKey == null)
+				throw new InvalidOperationException("Rijndael engine not initialised");
+
+			Check.DataLength(input, (BC / 2), "input buffer too short");
+			Check.OutputLength(output, (BC / 2), "output buffer too short");
+
+			UnPackBlock(input);
+
+			if (forEncryption)
+			{
+				EncryptBlock(workingKey);
+			}
+			else
+			{
+				DecryptBlock(workingKey);
+			}
+
+			PackBlock(output);
+
+			return BC / 2;
 		}
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private void UnPackBlock(ReadOnlySpan<byte> input)
+		{
+			int index = 0;
+
+			A0 = (long)(input[index++] & 0xff);
+			A1 = (long)(input[index++] & 0xff);
+			A2 = (long)(input[index++] & 0xff);
+			A3 = (long)(input[index++] & 0xff);
 
-		private void UnPackBlock(
-			byte[]      bytes,
-			int         off)
+			for (int j = 8; j != BC; j += 8)
+			{
+				A0 |= (long)(input[index++] & 0xff) << j;
+				A1 |= (long)(input[index++] & 0xff) << j;
+				A2 |= (long)(input[index++] & 0xff) << j;
+				A3 |= (long)(input[index++] & 0xff) << j;
+			}
+		}
+
+		private void PackBlock(Span<byte> output)
+		{
+			int index = 0;
+
+			for (int j = 0; j != BC; j += 8)
+			{
+				output[index++] = (byte)(A0 >> j);
+				output[index++] = (byte)(A1 >> j);
+				output[index++] = (byte)(A2 >> j);
+				output[index++] = (byte)(A3 >> j);
+			}
+		}
+#else
+		private void UnPackBlock(byte[] bytes, int off)
 		{
-			int     index = off;
+			int index = off;
 
 			A0 = (long)(bytes[index++] & 0xff);
 			A1 = (long)(bytes[index++] & 0xff);
@@ -653,11 +704,9 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
-		private  void PackBlock(
-			byte[]      bytes,
-			int         off)
+		private void PackBlock(byte[] bytes, int off)
 		{
-			int     index = off;
+			int index = off;
 
 			for (int j = 0; j != BC; j += 8)
 			{
@@ -667,8 +716,9 @@ namespace Org.BouncyCastle.Crypto.Engines
 				bytes[index++] = (byte)(A3 >> j);
 			}
 		}
+#endif
 
-		private  void EncryptBlock(
+		private void EncryptBlock(
 			long[][] rk)
 		{
 			int r;
diff --git a/crypto/src/crypto/engines/SEEDEngine.cs b/crypto/src/crypto/engines/SEEDEngine.cs
index d4142c867..07ffe06bb 100644
--- a/crypto/src/crypto/engines/SEEDEngine.cs
+++ b/crypto/src/crypto/engines/SEEDEngine.cs
@@ -1,5 +1,7 @@
 using System;
+
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
 {
@@ -168,12 +170,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 		private int[] wKey;
 		private bool forEncryption;
 
-        public virtual void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
 		{
 			this.forEncryption = forEncryption;
-			wKey = createWorkingKey(((KeyParameter)parameters).GetKey());
+			wKey = CreateWorkingKey(((KeyParameter)parameters).GetKey());
 		}
 
         public virtual string AlgorithmName
@@ -181,21 +181,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "SEED"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return BlockSize;
 		}
 
-        public virtual int ProcessBlock(
-			byte[]	inBuf,
-			int		inOff,
-			byte[]	outBuf,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
 		{
 			if (wKey == null)
 				throw new InvalidOperationException("SEED engine not initialised");
@@ -203,8 +194,8 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(inBuf, inOff, BlockSize, "input buffer too short");
             Check.OutputLength(outBuf, outOff, BlockSize, "output buffer too short");
 
-            long l = bytesToLong(inBuf, inOff + 0);
-			long r = bytesToLong(inBuf, inOff + 8);
+            long l = (long)Pack.BE_To_UInt64(inBuf, inOff + 0);
+			long r = (long)Pack.BE_To_UInt64(inBuf, inOff + 8);
 
 			if (forEncryption)
 			{
@@ -227,25 +218,60 @@ namespace Org.BouncyCastle.Crypto.Engines
 				}
 			}
 
-			longToBytes(outBuf, outOff + 0, r);
-			longToBytes(outBuf, outOff + 8, l);
+			Pack.UInt64_To_BE((ulong)r, outBuf, outOff + 0);
+			Pack.UInt64_To_BE((ulong)l, outBuf, outOff + 8);
 
 			return BlockSize;
 		}
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
+			if (wKey == null)
+				throw new InvalidOperationException("SEED engine not initialised");
+
+			Check.DataLength(input, BlockSize, "input buffer too short");
+			Check.OutputLength(output, BlockSize, "output buffer too short");
+
+			long l = (long)Pack.BE_To_UInt64(input);
+			long r = (long)Pack.BE_To_UInt64(input[8..]);
+
+			if (forEncryption)
+			{
+				for (int i = 0; i < 16; i++)
+				{
+					long nl = r;
+
+					r = l ^ F(wKey[2 * i], wKey[(2 * i) + 1], r);
+					l = nl;
+				}
+			}
+			else
+			{
+				for (int i = 15; i >= 0; i--)
+				{
+					long nl = r;
+
+					r = l ^ F(wKey[2 * i], wKey[(2 * i) + 1], r);
+					l = nl;
+				}
+			}
+
+			Pack.UInt64_To_BE((ulong)r, output);
+			Pack.UInt64_To_BE((ulong)l, output[8..]);
+
+			return BlockSize;
 		}
+#endif
 
-		private int[] createWorkingKey(
-			byte[] inKey)
+		private int[] CreateWorkingKey(byte[] inKey)
 		{
 			if (inKey.Length != 16)
 				throw new ArgumentException("key size must be 128 bits");
 
 			int[] key = new int[32];
-			long lower = bytesToLong(inKey, 0);
-			long upper = bytesToLong(inKey, 8);
+			long lower = (long)Pack.BE_To_UInt64(inKey, 0);
+			long upper = (long)Pack.BE_To_UInt64(inKey, 8);
 
 			int key0 = extractW0(lower);
 			int key1 = extractW1(lower);
@@ -298,31 +324,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			return ((long)((ulong) x >> 8)) | (x << 56);
 		}
 
-		private long bytesToLong(
-			byte[]	src,
-			int		srcOff)
-		{
-			long word = 0;
-
-			for (int i = 0; i <= 7; i++)
-			{
-				word = (word << 8) + (src[i + srcOff] & 0xff);
-			}
-
-			return word;
-		}
-
-		private void longToBytes(
-			byte[]	dest,
-			int		destOff,
-			long	value)
-		{
-			for (int i = 0; i < 8; i++)
-			{
-				dest[i + destOff] = (byte)(value >> ((7 - i) * 8));
-			}
-		}
-
 		private int G(
 			int x)
 		{
diff --git a/crypto/src/crypto/engines/SM2Engine.cs b/crypto/src/crypto/engines/SM2Engine.cs
index ab7e9cd17..e0734d424 100644
--- a/crypto/src/crypto/engines/SM2Engine.cs
+++ b/crypto/src/crypto/engines/SM2Engine.cs
@@ -16,7 +16,13 @@ namespace Org.BouncyCastle.Crypto.Engines
     /// </summary>
     public class SM2Engine
     {
+        public enum Mode
+        {
+            C1C2C3, C1C3C2
+        }
+
         private readonly IDigest mDigest;
+        private readonly Mode mMode;
 
         private bool mForEncryption;
         private ECKeyParameters mECKey;
@@ -29,9 +35,20 @@ namespace Org.BouncyCastle.Crypto.Engines
         {
         }
 
+        public SM2Engine(Mode mode)
+            : this(new SM3Digest(), mode)
+        {
+        }
+
         public SM2Engine(IDigest digest)
+            : this(digest, Mode.C1C2C3)
+        {
+        }
+
+        public SM2Engine(IDigest digest, Mode mode)
         {
-            this.mDigest = digest;
+            mDigest = digest;
+            mMode = mode;
         }
 
         public virtual void Init(bool forEncryption, ICipherParameters param)
@@ -62,6 +79,12 @@ namespace Org.BouncyCastle.Crypto.Engines
 
         public virtual byte[] ProcessBlock(byte[] input, int inOff, int inLen)
         {
+            if ((inOff + inLen) > input.Length || inLen == 0)
+                throw new DataLengthException("input buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBlock(input.AsSpan(inOff, inLen));
+#else
             if (mForEncryption)
             {
                 return Encrypt(input, inOff, inLen);
@@ -70,13 +93,149 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 return Decrypt(input, inOff, inLen);
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual byte[] ProcessBlock(ReadOnlySpan<byte> input)
+        {
+            if (input.Length == 0)
+                throw new DataLengthException("input buffer too short");
+
+            if (mForEncryption)
+            {
+                return Encrypt(input);
+            }
+            else
+            {
+                return Decrypt(input);
+            }
+        }
+#endif
+
         protected virtual ECMultiplier CreateBasePointMultiplier()
         {
             return new FixedPointCombMultiplier();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private byte[] Encrypt(ReadOnlySpan<byte> input)
+        {
+            byte[] c2 = input.ToArray();
+
+            ECMultiplier multiplier = CreateBasePointMultiplier();
+
+            BigInteger k;
+            ECPoint kPB;
+            do
+            {
+                k = NextK();
+                kPB = ((ECPublicKeyParameters)mECKey).Q.Multiply(k).Normalize();
+
+                Kdf(mDigest, kPB, c2);
+            }
+            while (NotEncrypted(c2, input));
+
+            ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize();
+
+            int c1PEncodedLength = c1P.GetEncodedLength(false);
+            Span<byte> c1 = c1PEncodedLength <= 512
+                ? stackalloc byte[c1PEncodedLength]
+                : new byte[c1PEncodedLength];
+            c1P.EncodeTo(false, c1);
+
+            AddFieldElement(mDigest, kPB.AffineXCoord);
+            mDigest.BlockUpdate(input);
+            AddFieldElement(mDigest, kPB.AffineYCoord);
+
+            int digestSize = mDigest.GetDigestSize();
+            Span<byte> c3 = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+            mDigest.DoFinal(c3);
+
+            switch (mMode)
+            {
+            case Mode.C1C3C2:
+                return Arrays.Concatenate(c1, c3, c2);
+            default:
+                return Arrays.Concatenate(c1, c2, c3);
+            }
+        }
+
+        private byte[] Decrypt(ReadOnlySpan<byte> input)
+        {
+            int c1Length = mCurveLength * 2 + 1;
+            ECPoint c1P = mECParams.Curve.DecodePoint(input[..c1Length]);
+
+            ECPoint s = c1P.Multiply(mECParams.H);
+            if (s.IsInfinity)
+                throw new InvalidCipherTextException("[h]C1 at infinity");
+
+            c1P = c1P.Multiply(((ECPrivateKeyParameters)mECKey).D).Normalize();
+
+            int digestSize = mDigest.GetDigestSize();
+            int c2Length = input.Length - c1Length - digestSize;
+            byte[] c2 = new byte[c2Length];
+
+            if (mMode == Mode.C1C3C2)
+            {
+                input[(c1Length + digestSize)..].CopyTo(c2);
+            }
+            else
+            {
+                input[c1Length..(c1Length + c2Length)].CopyTo(c2);
+            }
+
+            Kdf(mDigest, c1P, c2);
+
+            AddFieldElement(mDigest, c1P.AffineXCoord);
+            mDigest.BlockUpdate(c2);
+            AddFieldElement(mDigest, c1P.AffineYCoord);
+
+            Span<byte> c3 = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+            mDigest.DoFinal(c3);
+
+            int check = 0;
+            if (mMode == Mode.C1C3C2)
+            {
+                for (int i = 0; i != c3.Length; i++)
+                {
+                    check |= c3[i] ^ input[c1Length + i];
+                }
+            }
+            else
+            {
+                for (int i = 0; i != c3.Length; i++)
+                {
+                    check |= c3[i] ^ input[c1Length + c2.Length + i];
+                }
+            }
+
+            c3.Fill(0);
+
+            if (check != 0)
+            {
+                Arrays.Fill(c2, 0);
+                throw new InvalidCipherTextException("invalid cipher text");
+            }
+
+            return c2;
+        }
+
+        private bool NotEncrypted(ReadOnlySpan<byte> encData, ReadOnlySpan<byte> input)
+        {
+            for (int i = 0; i != encData.Length; i++)
+            {
+                if (encData[i] != input[i])
+                    return false;
+            }
+
+            return true;
+        }
+#else
         private byte[] Encrypt(byte[] input, int inOff, int inLen)
         {
             byte[] c2 = new byte[inLen];
@@ -85,29 +244,34 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             ECMultiplier multiplier = CreateBasePointMultiplier();
 
-            byte[] c1;
+            BigInteger k;
             ECPoint kPB;
             do
             {
-                BigInteger k = NextK();
-
-                ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize();
-
-                c1 = c1P.GetEncoded(false);
-
+                k = NextK();
                 kPB = ((ECPublicKeyParameters)mECKey).Q.Multiply(k).Normalize();
 
                 Kdf(mDigest, kPB, c2);
             }
             while (NotEncrypted(c2, input, inOff));
 
+            ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize();
+
+            byte[] c1 = c1P.GetEncoded(false);
+
             AddFieldElement(mDigest, kPB.AffineXCoord);
             mDigest.BlockUpdate(input, inOff, inLen);
             AddFieldElement(mDigest, kPB.AffineYCoord);
 
             byte[] c3 = DigestUtilities.DoFinal(mDigest);
 
-            return Arrays.ConcatenateAll(c1, c2, c3);
+            switch (mMode)
+            {
+            case Mode.C1C3C2:
+                return Arrays.ConcatenateAll(c1, c3, c2);
+            default:
+                return Arrays.ConcatenateAll(c1, c2, c3);
+            }
         }
 
         private byte[] Decrypt(byte[] input, int inOff, int inLen)
@@ -124,9 +288,17 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             c1P = c1P.Multiply(((ECPrivateKeyParameters)mECKey).D).Normalize();
 
-            byte[] c2 = new byte[inLen - c1.Length - mDigest.GetDigestSize()];
+            int digestSize = mDigest.GetDigestSize();
+            byte[] c2 = new byte[inLen - c1.Length - digestSize];
 
-            Array.Copy(input, inOff + c1.Length, c2, 0, c2.Length);
+            if (mMode == Mode.C1C3C2)
+            {
+                Array.Copy(input, inOff + c1.Length + digestSize, c2, 0, c2.Length);
+            }
+            else
+            {
+                Array.Copy(input, inOff + c1.Length, c2, 0, c2.Length);
+            }
 
             Kdf(mDigest, c1P, c2);
 
@@ -137,9 +309,19 @@ namespace Org.BouncyCastle.Crypto.Engines
             byte[] c3 = DigestUtilities.DoFinal(mDigest);
 
             int check = 0;
-            for (int i = 0; i != c3.Length; i++)
+            if (mMode == Mode.C1C3C2)
+            {
+                for (int i = 0; i != c3.Length; i++)
+                {
+                    check |= c3[i] ^ input[inOff + c1.Length + i];
+                }
+            }
+            else
             {
-                check |= c3[i] ^ input[inOff + c1.Length + c2.Length + i];
+                for (int i = 0; i != c3.Length; i++)
+                {
+                    check |= c3[i] ^ input[inOff + c1.Length + c2.Length + i];
+                }
             }
 
             Arrays.Fill(c1, 0);
@@ -159,18 +341,24 @@ namespace Org.BouncyCastle.Crypto.Engines
             for (int i = 0; i != encData.Length; i++)
             {
                 if (encData[i] != input[inOff + i])
-                {
                     return false;
-                }
             }
 
             return true;
         }
+#endif
 
         private void Kdf(IDigest digest, ECPoint c1, byte[] encData)
         {
             int digestSize = digest.GetDigestSize();
-            byte[] buf = new byte[System.Math.Max(4, digestSize)];
+            int bufSize = System.Math.Max(4, digestSize);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> buf = bufSize <= 128
+                ? stackalloc byte[bufSize]
+                : new byte[bufSize];
+#else
+            byte[] buf = new byte[bufSize];
+#endif
             int off = 0;
 
             IMemoable memo = digest as IMemoable;
@@ -197,16 +385,32 @@ namespace Org.BouncyCastle.Crypto.Engines
                     AddFieldElement(digest, c1.AffineYCoord);
                 }
 
+                int xorLen = System.Math.Min(digestSize, encData.Length - off);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Pack.UInt32_To_BE(++ct, buf);
+                digest.BlockUpdate(buf[..4]);
+                digest.DoFinal(buf);
+                Xor(encData.AsSpan(off, xorLen), buf);
+#else
                 Pack.UInt32_To_BE(++ct, buf, 0);
                 digest.BlockUpdate(buf, 0, 4);
                 digest.DoFinal(buf, 0);
-
-                int xorLen = System.Math.Min(digestSize, encData.Length - off);
                 Xor(encData, buf, off, xorLen);
+#endif
                 off += xorLen;
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Xor(Span<byte> data, ReadOnlySpan<byte> kdfOut)
+        {
+            for (int i = 0; i != data.Length; i++)
+            {
+                data[i] ^= kdfOut[i];
+            }
+        }
+#else
         private void Xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining)
         {
             for (int i = 0; i != dRemaining; i++)
@@ -214,6 +418,7 @@ namespace Org.BouncyCastle.Crypto.Engines
                 data[dOff + i] ^= kdfOut[i];
             }
         }
+#endif
 
         private BigInteger NextK()
         {
@@ -231,8 +436,17 @@ namespace Org.BouncyCastle.Crypto.Engines
 
         private void AddFieldElement(IDigest digest, ECFieldElement v)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int encodedLength = v.GetEncodedLength();
+            Span<byte> p = encodedLength <= 128
+                ? stackalloc byte[encodedLength]
+                : new byte[encodedLength];
+            v.EncodeTo(p);
+            digest.BlockUpdate(p);
+#else
             byte[] p = v.GetEncoded();
             digest.BlockUpdate(p, 0, p.Length);
+#endif
         }
     }
 }
diff --git a/crypto/src/crypto/engines/SM4Engine.cs b/crypto/src/crypto/engines/SM4Engine.cs
index 7477b070e..ddfc76e51 100644
--- a/crypto/src/crypto/engines/SM4Engine.cs
+++ b/crypto/src/crypto/engines/SM4Engine.cs
@@ -143,11 +143,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "SM4"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
         public virtual int GetBlockSize()
         {
             return BlockSize;
@@ -182,8 +177,35 @@ namespace Org.BouncyCastle.Crypto.Engines
             return BlockSize;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (null == rk)
+                throw new InvalidOperationException("SM4 not initialised");
+
+            Check.DataLength(input, BlockSize, "input buffer too short");
+            Check.OutputLength(output, BlockSize, "output buffer too short");
+
+            uint X0 = Pack.BE_To_UInt32(input);
+            uint X1 = Pack.BE_To_UInt32(input[4..]);
+            uint X2 = Pack.BE_To_UInt32(input[8..]);
+            uint X3 = Pack.BE_To_UInt32(input[12..]);
+
+            for (int i = 0; i < 32; i += 4)
+            {
+                X0 ^= T(X1 ^ X2 ^ X3 ^ rk[i    ]);  // F0
+                X1 ^= T(X2 ^ X3 ^ X0 ^ rk[i + 1]);  // F1
+                X2 ^= T(X3 ^ X0 ^ X1 ^ rk[i + 2]);  // F2
+                X3 ^= T(X0 ^ X1 ^ X2 ^ rk[i + 3]);  // F3
+            }
+
+            Pack.UInt32_To_BE(X3, output);
+            Pack.UInt32_To_BE(X2, output[4..]);
+            Pack.UInt32_To_BE(X1, output[8..]);
+            Pack.UInt32_To_BE(X0, output[12..]);
+
+            return BlockSize;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/Salsa20Engine.cs b/crypto/src/crypto/engines/Salsa20Engine.cs
index 77b08f9fc..7c2c1e1f9 100644
--- a/crypto/src/crypto/engines/Salsa20Engine.cs
+++ b/crypto/src/crypto/engines/Salsa20Engine.cs
@@ -1,5 +1,12 @@
 using System;
-using System.Text;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
@@ -181,6 +188,30 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            if (LimitExceeded((uint)input.Length))
+                throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV");
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                if (index == 0)
+                {
+                    GenerateKeyStream(keyStream);
+                    AdvanceCounter();
+                }
+                output[i] = (byte)(keyStream[index++] ^ input[i]);
+                index &= 63;
+            }
+        }
+#endif
+
         public virtual void Reset()
 		{
 			index = 0;
@@ -221,16 +252,138 @@ namespace Org.BouncyCastle.Crypto.Engines
 			Pack.UInt32_To_LE(x, output, 0);
 		}
 
-		internal static void SalsaCore(int rounds, uint[] input, uint[] x)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void SalsaCore(int rounds, ReadOnlySpan<uint> input, Span<uint> output)
+		{
+			if (input.Length < 16)
+				throw new ArgumentException();
+			if (output.Length < 16)
+				throw new ArgumentException();
+			if (rounds % 2 != 0)
+				throw new ArgumentException("Number of rounds must be even");
+
+#if NETCOREAPP3_0_OR_GREATER
+            if (Sse41.IsSupported && BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<short>>() == 16)
+			{
+				Vector128<uint> b0, b1, b2, b3;
+				{
+                    var I = MemoryMarshal.AsBytes(input[..16]);
+					var t0 = MemoryMarshal.Read<Vector128<short>>(I[0x00..0x10]);
+                    var t1 = MemoryMarshal.Read<Vector128<short>>(I[0x10..0x20]);
+                    var t2 = MemoryMarshal.Read<Vector128<short>>(I[0x20..0x30]);
+                    var t3 = MemoryMarshal.Read<Vector128<short>>(I[0x30..0x40]);
+
+                    var u0 = Sse41.Blend(t0, t2, 0xF0);
+					var u1 = Sse41.Blend(t1, t3, 0xC3);
+					var u2 = Sse41.Blend(t0, t2, 0x0F);
+					var u3 = Sse41.Blend(t1, t3, 0x3C);
+
+					b0 = Sse41.Blend(u0, u1, 0xCC).AsUInt32();
+					b1 = Sse41.Blend(u0, u1, 0x33).AsUInt32();
+					b2 = Sse41.Blend(u2, u3, 0xCC).AsUInt32();
+					b3 = Sse41.Blend(u2, u3, 0x33).AsUInt32();
+				}
+
+                var c0 = b0;
+                var c1 = b1;
+                var c2 = b2;
+                var c3 = b3;
+
+                for (int i = rounds; i > 0; i -= 2)
+				{
+                    QuarterRound_Sse2(ref c0, ref c3, ref c2, ref c1);
+                    QuarterRound_Sse2(ref c0, ref c1, ref c2, ref c3);
+                }
+
+                b0 = Sse2.Add(b0, c0);
+                b1 = Sse2.Add(b1, c1);
+                b2 = Sse2.Add(b2, c2);
+                b3 = Sse2.Add(b3, c3);
+
+                {
+					var t0 = b0.AsUInt16();
+                    var t1 = b1.AsUInt16();
+                    var t2 = b2.AsUInt16();
+                    var t3 = b3.AsUInt16();
+
+					var u0 = Sse41.Blend(t0, t1, 0xCC);
+					var u1 = Sse41.Blend(t0, t1, 0x33);
+					var u2 = Sse41.Blend(t2, t3, 0xCC);
+					var u3 = Sse41.Blend(t2, t3, 0x33);
+
+					var v0 = Sse41.Blend(u0, u2, 0xF0);
+                    var v1 = Sse41.Blend(u1, u3, 0xC3);
+                    var v2 = Sse41.Blend(u0, u2, 0x0F);
+                    var v3 = Sse41.Blend(u1, u3, 0x3C);
+
+                    var X = MemoryMarshal.AsBytes(output[..16]);
+                    MemoryMarshal.Write(X[0x00..0x10], ref v0);
+                    MemoryMarshal.Write(X[0x10..0x20], ref v1);
+                    MemoryMarshal.Write(X[0x20..0x30], ref v2);
+                    MemoryMarshal.Write(X[0x30..0x40], ref v3);
+                }
+                return;
+			}
+#endif
+
+			uint x00 = input[ 0];
+			uint x01 = input[ 1];
+			uint x02 = input[ 2];
+			uint x03 = input[ 3];
+			uint x04 = input[ 4];
+			uint x05 = input[ 5];
+			uint x06 = input[ 6];
+			uint x07 = input[ 7];
+			uint x08 = input[ 8];
+			uint x09 = input[ 9];
+			uint x10 = input[10];
+			uint x11 = input[11];
+			uint x12 = input[12];
+			uint x13 = input[13];
+			uint x14 = input[14];
+			uint x15 = input[15];
+
+			for (int i = rounds; i > 0; i -= 2)
+			{
+				QuarterRound(ref x00, ref x04, ref x08, ref x12);
+                QuarterRound(ref x05, ref x09, ref x13, ref x01);
+                QuarterRound(ref x10, ref x14, ref x02, ref x06);
+                QuarterRound(ref x15, ref x03, ref x07, ref x11);
+
+                QuarterRound(ref x00, ref x01, ref x02, ref x03);
+                QuarterRound(ref x05, ref x06, ref x07, ref x04);
+                QuarterRound(ref x10, ref x11, ref x08, ref x09);
+                QuarterRound(ref x15, ref x12, ref x13, ref x14);
+			}
+
+			output[ 0] = x00 + input[ 0];
+			output[ 1] = x01 + input[ 1];
+			output[ 2] = x02 + input[ 2];
+			output[ 3] = x03 + input[ 3];
+			output[ 4] = x04 + input[ 4];
+			output[ 5] = x05 + input[ 5];
+			output[ 6] = x06 + input[ 6];
+			output[ 7] = x07 + input[ 7];
+			output[ 8] = x08 + input[ 8];
+			output[ 9] = x09 + input[ 9];
+			output[10] = x10 + input[10];
+			output[11] = x11 + input[11];
+			output[12] = x12 + input[12];
+			output[13] = x13 + input[13];
+			output[14] = x14 + input[14];
+			output[15] = x15 + input[15];
+		}
+#else
+		internal static void SalsaCore(int rounds, uint[] input, uint[] output)
 		{
-			if (input.Length != 16)
+			if (input.Length < 16)
 				throw new ArgumentException();
-			if (x.Length != 16)
+			if (output.Length < 16)
 				throw new ArgumentException();
 			if (rounds % 2 != 0)
 				throw new ArgumentException("Number of rounds must be even");
 
-            uint x00 = input[ 0];
+			uint x00 = input[ 0];
 			uint x01 = input[ 1];
 			uint x02 = input[ 2];
 			uint x03 = input[ 3];
@@ -249,58 +402,35 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			for (int i = rounds; i > 0; i -= 2)
 			{
-				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 ^= 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);
+				QuarterRound(ref x00, ref x04, ref x08, ref x12);
+                QuarterRound(ref x05, ref x09, ref x13, ref x01);
+                QuarterRound(ref x10, ref x14, ref x02, ref x06);
+                QuarterRound(ref x15, ref x03, ref x07, ref x11);
+
+                QuarterRound(ref x00, ref x01, ref x02, ref x03);
+                QuarterRound(ref x05, ref x06, ref x07, ref x04);
+                QuarterRound(ref x10, ref x11, ref x08, ref x09);
+                QuarterRound(ref x15, ref x12, ref x13, ref x14);
 			}
 
-			x[ 0] = x00 + input[ 0];
-			x[ 1] = x01 + input[ 1];
-			x[ 2] = x02 + input[ 2];
-			x[ 3] = x03 + input[ 3];
-			x[ 4] = x04 + input[ 4];
-			x[ 5] = x05 + input[ 5];
-			x[ 6] = x06 + input[ 6];
-			x[ 7] = x07 + input[ 7];
-			x[ 8] = x08 + input[ 8];
-			x[ 9] = x09 + input[ 9];
-			x[10] = x10 + input[10];
-			x[11] = x11 + input[11];
-			x[12] = x12 + input[12];
-			x[13] = x13 + input[13];
-			x[14] = x14 + input[14];
-			x[15] = x15 + input[15];
+			output[ 0] = x00 + input[ 0];
+			output[ 1] = x01 + input[ 1];
+			output[ 2] = x02 + input[ 2];
+			output[ 3] = x03 + input[ 3];
+			output[ 4] = x04 + input[ 4];
+			output[ 5] = x05 + input[ 5];
+			output[ 6] = x06 + input[ 6];
+			output[ 7] = x07 + input[ 7];
+			output[ 8] = x08 + input[ 8];
+			output[ 9] = x09 + input[ 9];
+			output[10] = x10 + input[10];
+			output[11] = x11 + input[11];
+			output[12] = x12 + input[12];
+			output[13] = x13 + input[13];
+			output[14] = x14 + input[14];
+			output[15] = x15 + input[15];
 		}
+#endif
 
 		internal void ResetLimitCounter()
 		{
@@ -340,5 +470,39 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			return false;
 		}
-	}
+
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+        private static void QuarterRound(ref uint a, ref uint b, ref uint c, ref uint d)
+		{
+            b ^= Integers.RotateLeft(a + d,  7);
+            c ^= Integers.RotateLeft(b + a,  9);
+            d ^= Integers.RotateLeft(c + b, 13);
+            a ^= Integers.RotateLeft(d + c, 18);
+        }
+
+#if NETCOREAPP3_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void QuarterRound_Sse2(ref Vector128<uint> a, ref Vector128<uint> b, ref Vector128<uint> c,
+			ref Vector128<uint> d)
+        {
+			b = Sse2.Xor(b, Rotate_Sse2(Sse2.Add(a, d), 7));
+			c = Sse2.Xor(c, Rotate_Sse2(Sse2.Add(b, a), 9));
+			d = Sse2.Xor(d, Rotate_Sse2(Sse2.Add(c, b), 13));
+			a = Sse2.Xor(a, Rotate_Sse2(Sse2.Add(d, c), 18));
+
+            b = Sse2.Shuffle(b, 0x93);
+			c = Sse2.Shuffle(c, 0x4E);
+			d = Sse2.Shuffle(d, 0x39);
+		}
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Vector128<uint> Rotate_Sse2(Vector128<uint> x, byte sl)
+        {
+			byte sr = (byte)(32 - sl);
+            return Sse2.Xor(Sse2.ShiftLeftLogical(x, sl), Sse2.ShiftRightLogical(x, sr));
+        }
+#endif
+    }
 }
diff --git a/crypto/src/crypto/engines/SerpentEngine.cs b/crypto/src/crypto/engines/SerpentEngine.cs
index 76799f045..47f714a64 100644
--- a/crypto/src/crypto/engines/SerpentEngine.cs
+++ b/crypto/src/crypto/engines/SerpentEngine.cs
@@ -1,7 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
 {
@@ -26,7 +26,7 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @param key  The user-key bytes (multiples of 4) to use.
         * @exception ArgumentException
         */
-        protected override int[] MakeWorkingKey(byte[] key)
+        internal override int[] MakeWorkingKey(byte[] key)
         {
             //
             // pad key to 256 bits
@@ -64,7 +64,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             //
             for (int i = 8; i < 16; i++)
             {
-                kPad[i] = RotateLeft(kPad[i - 8] ^ kPad[i - 5] ^ kPad[i - 3] ^ kPad[i - 1] ^ PHI ^ (i - 8), 11);
+                kPad[i] = Integers.RotateLeft(kPad[i - 8] ^ kPad[i - 5] ^ kPad[i - 3] ^ kPad[i - 1] ^ PHI ^ (i - 8), 11);
             }
 
             Array.Copy(kPad, 8, w, 0, 8);
@@ -74,7 +74,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             //
             for (int i = 8; i < amount; i++)
             {
-                w[i] = RotateLeft(w[i - 8] ^ w[i - 5] ^ w[i - 3] ^ w[i - 1] ^ PHI ^ i, 11);
+                w[i] = Integers.RotateLeft(w[i - 8] ^ w[i - 5] ^ w[i - 3] ^ w[i - 1] ^ PHI ^ i, 11);
             }
 
             //
@@ -150,15 +150,131 @@ namespace Org.BouncyCastle.Crypto.Engines
             return w;
         }
 
-        /**
-        * Encrypt one block of plaintext.
-        *
-        * @param input the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param output the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        */
-        protected override void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            X0 = (int)Pack.LE_To_UInt32(input);
+            X1 = (int)Pack.LE_To_UInt32(input[4..]);
+            X2 = (int)Pack.LE_To_UInt32(input[8..]);
+            X3 = (int)Pack.LE_To_UInt32(input[12..]);
+
+            Sb0(wKey[0] ^ X0, wKey[1] ^ X1, wKey[2] ^ X2, wKey[3] ^ X3); LT();
+            Sb1(wKey[4] ^ X0, wKey[5] ^ X1, wKey[6] ^ X2, wKey[7] ^ X3); LT();
+            Sb2(wKey[8] ^ X0, wKey[9] ^ X1, wKey[10] ^ X2, wKey[11] ^ X3); LT();
+            Sb3(wKey[12] ^ X0, wKey[13] ^ X1, wKey[14] ^ X2, wKey[15] ^ X3); LT();
+            Sb4(wKey[16] ^ X0, wKey[17] ^ X1, wKey[18] ^ X2, wKey[19] ^ X3); LT();
+            Sb5(wKey[20] ^ X0, wKey[21] ^ X1, wKey[22] ^ X2, wKey[23] ^ X3); LT();
+            Sb6(wKey[24] ^ X0, wKey[25] ^ X1, wKey[26] ^ X2, wKey[27] ^ X3); LT();
+            Sb7(wKey[28] ^ X0, wKey[29] ^ X1, wKey[30] ^ X2, wKey[31] ^ X3); LT();
+            Sb0(wKey[32] ^ X0, wKey[33] ^ X1, wKey[34] ^ X2, wKey[35] ^ X3); LT();
+            Sb1(wKey[36] ^ X0, wKey[37] ^ X1, wKey[38] ^ X2, wKey[39] ^ X3); LT();
+            Sb2(wKey[40] ^ X0, wKey[41] ^ X1, wKey[42] ^ X2, wKey[43] ^ X3); LT();
+            Sb3(wKey[44] ^ X0, wKey[45] ^ X1, wKey[46] ^ X2, wKey[47] ^ X3); LT();
+            Sb4(wKey[48] ^ X0, wKey[49] ^ X1, wKey[50] ^ X2, wKey[51] ^ X3); LT();
+            Sb5(wKey[52] ^ X0, wKey[53] ^ X1, wKey[54] ^ X2, wKey[55] ^ X3); LT();
+            Sb6(wKey[56] ^ X0, wKey[57] ^ X1, wKey[58] ^ X2, wKey[59] ^ X3); LT();
+            Sb7(wKey[60] ^ X0, wKey[61] ^ X1, wKey[62] ^ X2, wKey[63] ^ X3); LT();
+            Sb0(wKey[64] ^ X0, wKey[65] ^ X1, wKey[66] ^ X2, wKey[67] ^ X3); LT();
+            Sb1(wKey[68] ^ X0, wKey[69] ^ X1, wKey[70] ^ X2, wKey[71] ^ X3); LT();
+            Sb2(wKey[72] ^ X0, wKey[73] ^ X1, wKey[74] ^ X2, wKey[75] ^ X3); LT();
+            Sb3(wKey[76] ^ X0, wKey[77] ^ X1, wKey[78] ^ X2, wKey[79] ^ X3); LT();
+            Sb4(wKey[80] ^ X0, wKey[81] ^ X1, wKey[82] ^ X2, wKey[83] ^ X3); LT();
+            Sb5(wKey[84] ^ X0, wKey[85] ^ X1, wKey[86] ^ X2, wKey[87] ^ X3); LT();
+            Sb6(wKey[88] ^ X0, wKey[89] ^ X1, wKey[90] ^ X2, wKey[91] ^ X3); LT();
+            Sb7(wKey[92] ^ X0, wKey[93] ^ X1, wKey[94] ^ X2, wKey[95] ^ X3); LT();
+            Sb0(wKey[96] ^ X0, wKey[97] ^ X1, wKey[98] ^ X2, wKey[99] ^ X3); LT();
+            Sb1(wKey[100] ^ X0, wKey[101] ^ X1, wKey[102] ^ X2, wKey[103] ^ X3); LT();
+            Sb2(wKey[104] ^ X0, wKey[105] ^ X1, wKey[106] ^ X2, wKey[107] ^ X3); LT();
+            Sb3(wKey[108] ^ X0, wKey[109] ^ X1, wKey[110] ^ X2, wKey[111] ^ X3); LT();
+            Sb4(wKey[112] ^ X0, wKey[113] ^ X1, wKey[114] ^ X2, wKey[115] ^ X3); LT();
+            Sb5(wKey[116] ^ X0, wKey[117] ^ X1, wKey[118] ^ X2, wKey[119] ^ X3); LT();
+            Sb6(wKey[120] ^ X0, wKey[121] ^ X1, wKey[122] ^ X2, wKey[123] ^ X3); LT();
+            Sb7(wKey[124] ^ X0, wKey[125] ^ X1, wKey[126] ^ X2, wKey[127] ^ X3);
+
+            Pack.UInt32_To_LE((uint)(wKey[128] ^ X0), output);
+            Pack.UInt32_To_LE((uint)(wKey[129] ^ X1), output[4..]);
+            Pack.UInt32_To_LE((uint)(wKey[130] ^ X2), output[8..]);
+            Pack.UInt32_To_LE((uint)(wKey[131] ^ X3), output[12..]);
+        }
+
+        internal override void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            X0 = wKey[128] ^ (int)Pack.LE_To_UInt32(input);
+            X1 = wKey[129] ^ (int)Pack.LE_To_UInt32(input[4..]);
+            X2 = wKey[130] ^ (int)Pack.LE_To_UInt32(input[8..]);
+            X3 = wKey[131] ^ (int)Pack.LE_To_UInt32(input[12..]);
+
+            Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[124]; X1 ^= wKey[125]; X2 ^= wKey[126]; X3 ^= wKey[127];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[120]; X1 ^= wKey[121]; X2 ^= wKey[122]; X3 ^= wKey[123];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[116]; X1 ^= wKey[117]; X2 ^= wKey[118]; X3 ^= wKey[119];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[112]; X1 ^= wKey[113]; X2 ^= wKey[114]; X3 ^= wKey[115];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[108]; X1 ^= wKey[109]; X2 ^= wKey[110]; X3 ^= wKey[111];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[104]; X1 ^= wKey[105]; X2 ^= wKey[106]; X3 ^= wKey[107];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[100]; X1 ^= wKey[101]; X2 ^= wKey[102]; X3 ^= wKey[103];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+            X0 ^= wKey[96]; X1 ^= wKey[97]; X2 ^= wKey[98]; X3 ^= wKey[99];
+            InverseLT(); Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[92]; X1 ^= wKey[93]; X2 ^= wKey[94]; X3 ^= wKey[95];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[88]; X1 ^= wKey[89]; X2 ^= wKey[90]; X3 ^= wKey[91];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[84]; X1 ^= wKey[85]; X2 ^= wKey[86]; X3 ^= wKey[87];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[80]; X1 ^= wKey[81]; X2 ^= wKey[82]; X3 ^= wKey[83];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[76]; X1 ^= wKey[77]; X2 ^= wKey[78]; X3 ^= wKey[79];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[72]; X1 ^= wKey[73]; X2 ^= wKey[74]; X3 ^= wKey[75];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[68]; X1 ^= wKey[69]; X2 ^= wKey[70]; X3 ^= wKey[71];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+            X0 ^= wKey[64]; X1 ^= wKey[65]; X2 ^= wKey[66]; X3 ^= wKey[67];
+            InverseLT(); Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[60]; X1 ^= wKey[61]; X2 ^= wKey[62]; X3 ^= wKey[63];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[56]; X1 ^= wKey[57]; X2 ^= wKey[58]; X3 ^= wKey[59];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[52]; X1 ^= wKey[53]; X2 ^= wKey[54]; X3 ^= wKey[55];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[48]; X1 ^= wKey[49]; X2 ^= wKey[50]; X3 ^= wKey[51];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[44]; X1 ^= wKey[45]; X2 ^= wKey[46]; X3 ^= wKey[47];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[40]; X1 ^= wKey[41]; X2 ^= wKey[42]; X3 ^= wKey[43];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[36]; X1 ^= wKey[37]; X2 ^= wKey[38]; X3 ^= wKey[39];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+            X0 ^= wKey[32]; X1 ^= wKey[33]; X2 ^= wKey[34]; X3 ^= wKey[35];
+            InverseLT(); Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[28]; X1 ^= wKey[29]; X2 ^= wKey[30]; X3 ^= wKey[31];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[24]; X1 ^= wKey[25]; X2 ^= wKey[26]; X3 ^= wKey[27];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[20]; X1 ^= wKey[21]; X2 ^= wKey[22]; X3 ^= wKey[23];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[16]; X1 ^= wKey[17]; X2 ^= wKey[18]; X3 ^= wKey[19];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[12]; X1 ^= wKey[13]; X2 ^= wKey[14]; X3 ^= wKey[15];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[8]; X1 ^= wKey[9]; X2 ^= wKey[10]; X3 ^= wKey[11];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[4]; X1 ^= wKey[5]; X2 ^= wKey[6]; X3 ^= wKey[7];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+
+            Pack.UInt32_To_LE((uint)(X0 ^ wKey[0]), output);
+            Pack.UInt32_To_LE((uint)(X1 ^ wKey[1]), output[4..]);
+            Pack.UInt32_To_LE((uint)(X2 ^ wKey[2]), output[8..]);
+            Pack.UInt32_To_LE((uint)(X3 ^ wKey[3]), output[12..]);
+        }
+#else
+        internal override void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             X0 = (int)Pack.LE_To_UInt32(input, inOff);
             X1 = (int)Pack.LE_To_UInt32(input, inOff + 4);
@@ -204,15 +320,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_LE((uint)(wKey[131] ^ X3), output, outOff + 12);
         }
 
-        /**
-        * Decrypt one block of ciphertext.
-        *
-        * @param input the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param output the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        */
-        protected override void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff)
+        internal override void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             X0 = wKey[128] ^ (int)Pack.LE_To_UInt32(input, inOff);
             X1 = wKey[129] ^ (int)Pack.LE_To_UInt32(input, inOff + 4);
@@ -288,5 +396,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_LE((uint)(X2 ^ wKey[2]), output, outOff + 8);
             Pack.UInt32_To_LE((uint)(X3 ^ wKey[3]), output, outOff + 12);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/SerpentEngineBase.cs b/crypto/src/crypto/engines/SerpentEngineBase.cs
index 9de552233..44020f06f 100644
--- a/crypto/src/crypto/engines/SerpentEngineBase.cs
+++ b/crypto/src/crypto/engines/SerpentEngineBase.cs
@@ -18,7 +18,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
         protected int X0, X1, X2, X3;    // registers
 
-        protected SerpentEngineBase()
+        internal SerpentEngineBase()
         {
         }
 
@@ -44,11 +44,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "Serpent"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-        {
-            get { return false; }
-        }
-
         public virtual int GetBlockSize()
         {
             return BlockSize;
@@ -75,6 +70,16 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BlockSize, "input buffer too short");
             Check.OutputLength(output, outOff, BlockSize, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (encrypting)
+            {
+                EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+            else
+            {
+                DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+#else
             if (encrypting)
             {
                 EncryptBlock(input, inOff, output, outOff);
@@ -83,23 +88,32 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff);
             }
+#endif
 
             return BlockSize;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-        }
+            if (wKey == null)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
 
-        protected static int RotateLeft(int x, int bits)
-        {
-            return ((x << bits) | (int) ((uint)x >> (32 - bits)));
-        }
+            Check.DataLength(input, BlockSize, "input buffer too short");
+            Check.OutputLength(output, BlockSize, "output buffer too short");
 
-        private static int RotateRight(int x, int bits)
-        {
-            return ( (int)((uint)x >> bits) | (x << (32 - bits)));
+            if (encrypting)
+            {
+                EncryptBlock(input, output);
+            }
+            else
+            {
+                DecryptBlock(input, output);
+            }
+
+            return BlockSize;
         }
+#endif
 
         /*
          * The sboxes below are based on the work of Brian Gladman and
@@ -434,15 +448,15 @@ namespace Org.BouncyCastle.Crypto.Engines
         */
         protected void LT()
         {
-            int x0 = RotateLeft(X0, 13);
-            int x2 = RotateLeft(X2, 3);
+            int x0 = Integers.RotateLeft(X0, 13);
+            int x2 = Integers.RotateLeft(X2, 3);
             int x1 = X1 ^ x0 ^ x2;
             int x3 = X3 ^ x2 ^ x0 << 3;
 
-            X1 = RotateLeft(x1, 1);
-            X3 = RotateLeft(x3, 7);
-            X0 = RotateLeft(x0 ^ X1 ^ X3, 5);
-            X2 = RotateLeft(x2 ^ X3 ^ (X1 << 7), 22);
+            X1 = Integers.RotateLeft(x1, 1);
+            X3 = Integers.RotateLeft(x3, 7);
+            X0 = Integers.RotateLeft(x0 ^ X1 ^ X3, 5);
+            X2 = Integers.RotateLeft(x2 ^ X3 ^ (X1 << 7), 22);
         }
 
         /**
@@ -450,20 +464,24 @@ namespace Org.BouncyCastle.Crypto.Engines
         */
         protected void InverseLT()
         {
-            int x2 = RotateRight(X2, 22) ^ X3 ^ (X1 << 7);
-            int x0 = RotateRight(X0, 5) ^ X1 ^ X3;
-            int x3 = RotateRight(X3, 7);
-            int x1 = RotateRight(X1, 1);
+            int x2 = Integers.RotateRight(X2, 22) ^ X3 ^ (X1 << 7);
+            int x0 = Integers.RotateRight(X0, 5) ^ X1 ^ X3;
+            int x3 = Integers.RotateRight(X3, 7);
+            int x1 = Integers.RotateRight(X1, 1);
             X3 = x3 ^ x2 ^ x0 << 3;
             X1 = x1 ^ x0 ^ x2;
-            X2 = RotateRight(x2, 3);
-            X0 = RotateRight(x0, 13);
+            X2 = Integers.RotateRight(x2, 3);
+            X0 = Integers.RotateRight(x0, 13);
         }
 
-        protected abstract int[] MakeWorkingKey(byte[] key);
-
-        protected abstract void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff);
+        internal abstract int[] MakeWorkingKey(byte[] key);
 
-        protected abstract void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal abstract void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output);
+        internal abstract void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output);
+#else
+        internal abstract void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff);
+        internal abstract void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff);
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/SkipjackEngine.cs b/crypto/src/crypto/engines/SkipjackEngine.cs
index c90646cc4..c6a3e458e 100644
--- a/crypto/src/crypto/engines/SkipjackEngine.cs
+++ b/crypto/src/crypto/engines/SkipjackEngine.cs
@@ -8,12 +8,12 @@ namespace Org.BouncyCastle.Crypto.Engines
     /**
     * a class that provides a basic SKIPJACK engine.
     */
-    public class SkipjackEngine
+    public sealed class SkipjackEngine
 		: IBlockCipher
     {
-        const int BLOCK_SIZE = 8;
+        private const int BLOCK_SIZE = 8;
 
-        static readonly short [] ftable =
+        private static readonly short[] ftable =
         {
             0xa3, 0xd7, 0x09, 0x83, 0xf8, 0x48, 0xf6, 0xf4, 0xb3, 0x21, 0x15, 0x78, 0x99, 0xb1, 0xaf, 0xf9,
             0xe7, 0x2d, 0x4d, 0x8a, 0xce, 0x4c, 0xca, 0x2e, 0x52, 0x95, 0xd9, 0x1e, 0x4e, 0x38, 0x44, 0x28,
@@ -44,14 +44,12 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
-            if (!(parameters is KeyParameter))
+            if (!(parameters is KeyParameter keyParameter))
 	            throw new ArgumentException("invalid parameter passed to SKIPJACK init - " + Platform.GetTypeName(parameters));
 
-			byte[] keyBytes = ((KeyParameter)parameters).GetKey();
+			byte[] keyBytes = keyParameter.GetKey();
 
             this.encrypting = forEncryption;
             this.key0 = new int[32];
@@ -63,35 +61,26 @@ namespace Org.BouncyCastle.Crypto.Engines
             // expand the key to 128 bytes in 4 parts (saving us a modulo, multiply
             // and an addition).
             //
-            for (int i = 0; i < 32; i ++)
+            for (int i = 0; i < 32; i++)
             {
-                key0[i] = keyBytes[(i * 4) % 10] & 0xff;
-                key1[i] = keyBytes[(i * 4 + 1) % 10] & 0xff;
-                key2[i] = keyBytes[(i * 4 + 2) % 10] & 0xff;
-                key3[i] = keyBytes[(i * 4 + 3) % 10] & 0xff;
+                key0[i] = keyBytes[(i * 4 + 0) % 10];
+                key1[i] = keyBytes[(i * 4 + 1) % 10];
+                key2[i] = keyBytes[(i * 4 + 2) % 10];
+                key3[i] = keyBytes[(i * 4 + 3) % 10];
             }
         }
 
-        public virtual string AlgorithmName
+        public string AlgorithmName
         {
             get { return "SKIPJACK"; }
         }
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
-        public virtual int GetBlockSize()
+        public int GetBlockSize()
         {
             return BLOCK_SIZE;
         }
 
-        public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[]	output, int outOff)
         {
             if (key1 == null)
                 throw new InvalidOperationException("SKIPJACK engine not initialised");
@@ -99,6 +88,16 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (encrypting)
+            {
+                EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+            else
+            {
+                DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+#else
             if (encrypting)
             {
                 EncryptBlock(input, inOff, output, outOff);
@@ -107,13 +106,32 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff);
             }
+#endif
 
 			return BLOCK_SIZE;
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            if (key1 == null)
+                throw new InvalidOperationException("SKIPJACK engine not initialised");
+
+            Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+            Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+            if (encrypting)
+            {
+                EncryptBlock(input, output);
+            }
+            else
+            {
+                DecryptBlock(input, output);
+            }
+
+            return BLOCK_SIZE;
         }
+#endif
 
         /**
         * The G permutation
@@ -135,11 +153,97 @@ namespace Org.BouncyCastle.Crypto.Engines
             return ((g5 << 8) + g6);
         }
 
-        public virtual int EncryptBlock(
-            byte[]      input,
-            int         inOff,
-            byte[]      outBytes,
-            int         outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int w1 = (input[0] << 8) + (input[1] & 0xff);
+            int w2 = (input[2] << 8) + (input[3] & 0xff);
+            int w3 = (input[4] << 8) + (input[5] & 0xff);
+            int w4 = (input[6] << 8) + (input[7] & 0xff);
+
+            int k = 0;
+
+            for (int t = 0; t < 2; t++)
+            {
+                for (int i = 0; i < 8; i++)
+                {
+                    int tmp = w4;
+                    w4 = w3;
+                    w3 = w2;
+                    w2 = G(k, w1);
+                    w1 = w2 ^ tmp ^ (k + 1);
+                    k++;
+                }
+
+                for (int i = 0; i < 8; i++)
+                {
+                    int tmp = w4;
+                    w4 = w3;
+                    w3 = w1 ^ w2 ^ (k + 1);
+                    w2 = G(k, w1);
+                    w1 = tmp;
+                    k++;
+                }
+            }
+
+            output[0] = (byte)((w1 >> 8));
+            output[1] = (byte)(w1);
+            output[2] = (byte)((w2 >> 8));
+            output[3] = (byte)(w2);
+            output[4] = (byte)((w3 >> 8));
+            output[5] = (byte)(w3);
+            output[6] = (byte)((w4 >> 8));
+            output[7] = (byte)(w4);
+
+            return BLOCK_SIZE;
+        }
+
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int w2 = (input[0] << 8) + (input[1] & 0xff);
+            int w1 = (input[2] << 8) + (input[3] & 0xff);
+            int w4 = (input[4] << 8) + (input[5] & 0xff);
+            int w3 = (input[6] << 8) + (input[7] & 0xff);
+
+            int k = 31;
+
+            for (int t = 0; t < 2; t++)
+            {
+                for (int i = 0; i < 8; i++)
+                {
+                    int tmp = w4;
+                    w4 = w3;
+                    w3 = w2;
+                    w2 = H(k, w1);
+                    w1 = w2 ^ tmp ^ (k + 1);
+                    k--;
+                }
+
+                for (int i = 0; i < 8; i++)
+                {
+                    int tmp = w4;
+                    w4 = w3;
+                    w3 = w1 ^ w2 ^ (k + 1);
+                    w2 = H(k, w1);
+                    w1 = tmp;
+                    k--;
+                }
+            }
+
+            output[0] = (byte)((w2 >> 8));
+            output[1] = (byte)(w2);
+            output[2] = (byte)((w1 >> 8));
+            output[3] = (byte)(w1);
+            output[4] = (byte)((w4 >> 8));
+            output[5] = (byte)(w4);
+            output[6] = (byte)((w3 >> 8));
+            output[7] = (byte)(w3);
+
+            return BLOCK_SIZE;
+        }
+
+#else
+        private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
             int w1 = (input[inOff + 0] << 8) + (input[inOff + 1] & 0xff);
             int w2 = (input[inOff + 2] << 8) + (input[inOff + 3] & 0xff);
@@ -183,31 +287,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             return BLOCK_SIZE;
         }
 
-        /**
-        * the inverse of the G permutation.
-        */
-        private int H(
-            int     k,
-            int     w)
-        {
-            int h1, h2, h3, h4, h5, h6;
-
-            h1 = w & 0xff;
-            h2 = (w >> 8) & 0xff;
-
-            h3 = ftable[h2 ^ key3[k]] ^ h1;
-            h4 = ftable[h3 ^ key2[k]] ^ h2;
-            h5 = ftable[h4 ^ key1[k]] ^ h3;
-            h6 = ftable[h5 ^ key0[k]] ^ h4;
-
-            return ((h6 << 8) + h5);
-        }
-
-        public virtual int DecryptBlock(
-            byte[]      input,
-            int         inOff,
-            byte[]      outBytes,
-            int         outOff)
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
             int w2 = (input[inOff + 0] << 8) + (input[inOff + 1] & 0xff);
             int w1 = (input[inOff + 2] << 8) + (input[inOff + 3] & 0xff);
@@ -218,7 +298,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int t = 0; t < 2; t++)
             {
-                for(int i = 0; i < 8; i++)
+                for (int i = 0; i < 8; i++)
                 {
                     int tmp = w4;
                     w4 = w3;
@@ -228,7 +308,7 @@ namespace Org.BouncyCastle.Crypto.Engines
                     k--;
                 }
 
-                for(int i = 0; i < 8; i++)
+                for (int i = 0; i < 8; i++)
                 {
                     int tmp = w4;
                     w4 = w3;
@@ -250,5 +330,22 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             return BLOCK_SIZE;
         }
+#endif
+
+        /**
+        * the inverse of the G permutation.
+        */
+        private int H(int k, int w)
+        {
+            int h1 = w & 0xff;
+            int h2 = (w >> 8) & 0xff;
+
+            int h3 = ftable[h2 ^ key3[k]] ^ h1;
+            int h4 = ftable[h3 ^ key2[k]] ^ h2;
+            int h5 = ftable[h4 ^ key1[k]] ^ h3;
+            int h6 = ftable[h5 ^ key0[k]] ^ h4;
+
+            return (h6 << 8) + h5;
+        }
     }
 }
diff --git a/crypto/src/crypto/engines/TEAEngine.cs b/crypto/src/crypto/engines/TEAEngine.cs
index 7b700145e..062fd7b7a 100644
--- a/crypto/src/crypto/engines/TEAEngine.cs
+++ b/crypto/src/crypto/engines/TEAEngine.cs
@@ -42,11 +42,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "TEA"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return block_size;
@@ -60,11 +55,9 @@ namespace Org.BouncyCastle.Crypto.Engines
 		* @exception ArgumentException if the params argument is
 		* inappropriate.
 		*/
-        public virtual void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
 		{
-			if (!(parameters is KeyParameter))
+			if (!(parameters is KeyParameter keyParameter))
 			{
 				throw new ArgumentException("invalid parameter passed to TEA init - "
 					+ Platform.GetTypeName(parameters));
@@ -73,16 +66,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 			_forEncryption = forEncryption;
 			_initialised = true;
 
-			KeyParameter p = (KeyParameter) parameters;
-
-			setKey(p.GetKey());
+			SetKey(keyParameter.GetKey());
 		}
 
-        public virtual int ProcessBlock(
-			byte[]  inBytes,
-			int     inOff,
-			byte[]  outBytes,
-			int     outOff)
+        public virtual int ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
 			if (!_initialised)
 				throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -90,22 +77,38 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(inBytes, inOff, block_size, "input buffer too short");
             Check.OutputLength(outBytes, outOff, block_size, "output buffer too short");
 
-            return _forEncryption
-				?	encryptBlock(inBytes, inOff, outBytes, outOff)
-				:	decryptBlock(inBytes, inOff, outBytes, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return _forEncryption
+				? EncryptBlock(inBytes.AsSpan(inOff), outBytes.AsSpan(outOff))
+				: DecryptBlock(inBytes.AsSpan(inOff), outBytes.AsSpan(outOff));
+#else
+			return _forEncryption
+				? EncryptBlock(inBytes, inOff, outBytes, outOff)
+				: DecryptBlock(inBytes, inOff, outBytes, outOff);
+#endif
 		}
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
+			if (!_initialised)
+				throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+			Check.DataLength(input, block_size, "input buffer too short");
+			Check.OutputLength(output, block_size, "output buffer too short");
+
+			return _forEncryption
+				? EncryptBlock(input, output)
+				: DecryptBlock(input, output);
 		}
+#endif
 
 		/**
 		* Re-key the cipher.
 		*
 		* @param  key  the key to be used
 		*/
-		private void setKey(
-			byte[] key)
+		private void SetKey(byte[] key)
 		{
 			_a = Pack.BE_To_UInt32(key, 0);
 			_b = Pack.BE_To_UInt32(key, 4);
@@ -113,18 +116,57 @@ namespace Org.BouncyCastle.Crypto.Engines
 			_d = Pack.BE_To_UInt32(key, 12);
 		}
 
-		private int encryptBlock(
-			byte[]	inBytes,
-			int		inOff,
-			byte[]	outBytes,
-			int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			// Pack bytes into integers
+			uint v0 = Pack.BE_To_UInt32(input);
+			uint v1 = Pack.BE_To_UInt32(input[4..]);
+
+			uint sum = 0;
+
+			for (int i = 0; i != rounds; i++)
+			{
+				sum += delta;
+				v0  += ((v1 << 4) + _a) ^ (v1 + sum) ^ ((v1 >> 5) + _b);
+				v1  += ((v0 << 4) + _c) ^ (v0 + sum) ^ ((v0 >> 5) + _d);
+			}
+
+			Pack.UInt32_To_BE(v0, output);
+			Pack.UInt32_To_BE(v1, output[4..]);
+
+			return block_size;
+		}
+
+		private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			// Pack bytes into integers
+			uint v0 = Pack.BE_To_UInt32(input);
+			uint v1 = Pack.BE_To_UInt32(input[4..]);
+
+			uint sum = d_sum;
+
+			for (int i = 0; i != rounds; i++)
+			{
+				v1  -= ((v0 << 4) + _c) ^ (v0 + sum) ^ ((v0 >> 5) + _d);
+				v0  -= ((v1 << 4) + _a) ^ (v1 + sum) ^ ((v1 >> 5) + _b);
+				sum -= delta;
+			}
+
+			Pack.UInt32_To_BE(v0, output);
+			Pack.UInt32_To_BE(v1, output[4..]);
+
+			return block_size;
+		}
+#else
+		private int EncryptBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
 			// Pack bytes into integers
 			uint v0 = Pack.BE_To_UInt32(inBytes, inOff);
 			uint v1 = Pack.BE_To_UInt32(inBytes, inOff + 4);
-	        
+
 			uint sum = 0;
-	        
+
 			for (int i = 0; i != rounds; i++)
 			{
 				sum += delta;
@@ -138,11 +180,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 			return block_size;
 		}
 
-		private int decryptBlock(
-			byte[]	inBytes,
-			int		inOff,
-			byte[]	outBytes,
-			int		outOff)
+		private int DecryptBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
 			// Pack bytes into integers
 			uint v0 = Pack.BE_To_UInt32(inBytes, inOff);
@@ -162,5 +200,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			return block_size;
 		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/engines/ThreefishEngine.cs b/crypto/src/crypto/engines/ThreefishEngine.cs
index c5aee5395..d1adb2e61 100644
--- a/crypto/src/crypto/engines/ThreefishEngine.cs
+++ b/crypto/src/crypto/engines/ThreefishEngine.cs
@@ -269,31 +269,15 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "Threefish-" + (blocksizeBytes * 8); }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return blocksizeBytes;
 		}
 
-        public virtual void Reset()
-		{
-		}
-
         public virtual int ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
-			if ((outOff + blocksizeBytes) > outBytes.Length)
-			{
-				throw new DataLengthException("Output buffer too short");
-			}
-
-			if ((inOff + blocksizeBytes) > inBytes.Length)
-			{
-				throw new DataLengthException("Input buffer too short");
-			}
+			Check.DataLength(inBytes, inOff, blocksizeBytes, "input buffer too short");
+			Check.OutputLength(outBytes, outOff, blocksizeBytes, "output buffer too short");
 
 			Pack.LE_To_UInt64(inBytes, inOff, currentBlock);
 			ProcessBlock(this.currentBlock, this.currentBlock);
@@ -301,6 +285,19 @@ namespace Org.BouncyCastle.Crypto.Engines
 			return blocksizeBytes;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			Check.DataLength(input, blocksizeBytes, "input buffer too short");
+			Check.OutputLength(output, blocksizeBytes, "output buffer too short");
+
+			Pack.LE_To_UInt64(input, currentBlock);
+			ProcessBlock(this.currentBlock, this.currentBlock);
+			Pack.UInt64_To_LE(currentBlock, output);
+			return blocksizeBytes;
+		}
+#endif
+
 		/// <summary>
 		/// Process a block of data represented as 64 bit words.
 		/// </summary>
@@ -317,13 +314,9 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 
 			if (inWords.Length != blocksizeWords)
-			{
-				throw new DataLengthException("Input buffer too short");
-			}
+				throw new DataLengthException("input buffer too short");
 			if (outWords.Length != blocksizeWords)
-			{
-				throw new DataLengthException("Output buffer too short");
-			}
+				throw new OutputLengthException("output buffer too short");
 
 			if (forEncryption)
 			{
diff --git a/crypto/src/crypto/engines/TnepresEngine.cs b/crypto/src/crypto/engines/TnepresEngine.cs
index ce687d1e5..7751e20bc 100644
--- a/crypto/src/crypto/engines/TnepresEngine.cs
+++ b/crypto/src/crypto/engines/TnepresEngine.cs
@@ -1,7 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Engines
 {
@@ -33,7 +33,7 @@ namespace Org.BouncyCastle.Crypto.Engines
         * @param key  The user-key bytes (multiples of 4) to use.
         * @exception ArgumentException
         */
-        protected override int[] MakeWorkingKey(byte[] key)
+        internal override int[] MakeWorkingKey(byte[] key)
         {
             //
             // pad key to 256 bits
@@ -71,7 +71,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             //
             for (int i = 8; i < 16; i++)
             {
-                kPad[i] = RotateLeft(kPad[i - 8] ^ kPad[i - 5] ^ kPad[i - 3] ^ kPad[i - 1] ^ PHI ^ (i - 8), 11);
+                kPad[i] = Integers.RotateLeft(kPad[i - 8] ^ kPad[i - 5] ^ kPad[i - 3] ^ kPad[i - 1] ^ PHI ^ (i - 8), 11);
             }
 
             Array.Copy(kPad, 8, w, 0, 8);
@@ -81,7 +81,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             //
             for (int i = 8; i < amount; i++)
             {
-                w[i] = RotateLeft(w[i - 8] ^ w[i - 5] ^ w[i - 3] ^ w[i - 1] ^ PHI ^ i, 11);
+                w[i] = Integers.RotateLeft(w[i - 8] ^ w[i - 5] ^ w[i - 3] ^ w[i - 1] ^ PHI ^ i, 11);
             }
 
             //
@@ -157,15 +157,131 @@ namespace Org.BouncyCastle.Crypto.Engines
             return w;
         }
 
-        /**
-        * Encrypt one block of plaintext.
-        *
-        * @param input the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param output the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        */
-        protected override void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            X3 = (int)Pack.BE_To_UInt32(input);
+            X2 = (int)Pack.BE_To_UInt32(input[4..]);
+            X1 = (int)Pack.BE_To_UInt32(input[8..]);
+            X0 = (int)Pack.BE_To_UInt32(input[12..]);
+
+            Sb0(wKey[0] ^ X0, wKey[1] ^ X1, wKey[2] ^ X2, wKey[3] ^ X3); LT();
+            Sb1(wKey[4] ^ X0, wKey[5] ^ X1, wKey[6] ^ X2, wKey[7] ^ X3); LT();
+            Sb2(wKey[8] ^ X0, wKey[9] ^ X1, wKey[10] ^ X2, wKey[11] ^ X3); LT();
+            Sb3(wKey[12] ^ X0, wKey[13] ^ X1, wKey[14] ^ X2, wKey[15] ^ X3); LT();
+            Sb4(wKey[16] ^ X0, wKey[17] ^ X1, wKey[18] ^ X2, wKey[19] ^ X3); LT();
+            Sb5(wKey[20] ^ X0, wKey[21] ^ X1, wKey[22] ^ X2, wKey[23] ^ X3); LT();
+            Sb6(wKey[24] ^ X0, wKey[25] ^ X1, wKey[26] ^ X2, wKey[27] ^ X3); LT();
+            Sb7(wKey[28] ^ X0, wKey[29] ^ X1, wKey[30] ^ X2, wKey[31] ^ X3); LT();
+            Sb0(wKey[32] ^ X0, wKey[33] ^ X1, wKey[34] ^ X2, wKey[35] ^ X3); LT();
+            Sb1(wKey[36] ^ X0, wKey[37] ^ X1, wKey[38] ^ X2, wKey[39] ^ X3); LT();
+            Sb2(wKey[40] ^ X0, wKey[41] ^ X1, wKey[42] ^ X2, wKey[43] ^ X3); LT();
+            Sb3(wKey[44] ^ X0, wKey[45] ^ X1, wKey[46] ^ X2, wKey[47] ^ X3); LT();
+            Sb4(wKey[48] ^ X0, wKey[49] ^ X1, wKey[50] ^ X2, wKey[51] ^ X3); LT();
+            Sb5(wKey[52] ^ X0, wKey[53] ^ X1, wKey[54] ^ X2, wKey[55] ^ X3); LT();
+            Sb6(wKey[56] ^ X0, wKey[57] ^ X1, wKey[58] ^ X2, wKey[59] ^ X3); LT();
+            Sb7(wKey[60] ^ X0, wKey[61] ^ X1, wKey[62] ^ X2, wKey[63] ^ X3); LT();
+            Sb0(wKey[64] ^ X0, wKey[65] ^ X1, wKey[66] ^ X2, wKey[67] ^ X3); LT();
+            Sb1(wKey[68] ^ X0, wKey[69] ^ X1, wKey[70] ^ X2, wKey[71] ^ X3); LT();
+            Sb2(wKey[72] ^ X0, wKey[73] ^ X1, wKey[74] ^ X2, wKey[75] ^ X3); LT();
+            Sb3(wKey[76] ^ X0, wKey[77] ^ X1, wKey[78] ^ X2, wKey[79] ^ X3); LT();
+            Sb4(wKey[80] ^ X0, wKey[81] ^ X1, wKey[82] ^ X2, wKey[83] ^ X3); LT();
+            Sb5(wKey[84] ^ X0, wKey[85] ^ X1, wKey[86] ^ X2, wKey[87] ^ X3); LT();
+            Sb6(wKey[88] ^ X0, wKey[89] ^ X1, wKey[90] ^ X2, wKey[91] ^ X3); LT();
+            Sb7(wKey[92] ^ X0, wKey[93] ^ X1, wKey[94] ^ X2, wKey[95] ^ X3); LT();
+            Sb0(wKey[96] ^ X0, wKey[97] ^ X1, wKey[98] ^ X2, wKey[99] ^ X3); LT();
+            Sb1(wKey[100] ^ X0, wKey[101] ^ X1, wKey[102] ^ X2, wKey[103] ^ X3); LT();
+            Sb2(wKey[104] ^ X0, wKey[105] ^ X1, wKey[106] ^ X2, wKey[107] ^ X3); LT();
+            Sb3(wKey[108] ^ X0, wKey[109] ^ X1, wKey[110] ^ X2, wKey[111] ^ X3); LT();
+            Sb4(wKey[112] ^ X0, wKey[113] ^ X1, wKey[114] ^ X2, wKey[115] ^ X3); LT();
+            Sb5(wKey[116] ^ X0, wKey[117] ^ X1, wKey[118] ^ X2, wKey[119] ^ X3); LT();
+            Sb6(wKey[120] ^ X0, wKey[121] ^ X1, wKey[122] ^ X2, wKey[123] ^ X3); LT();
+            Sb7(wKey[124] ^ X0, wKey[125] ^ X1, wKey[126] ^ X2, wKey[127] ^ X3);
+
+            Pack.UInt32_To_BE((uint)(wKey[131] ^ X3), output);
+            Pack.UInt32_To_BE((uint)(wKey[130] ^ X2), output[4..]);
+            Pack.UInt32_To_BE((uint)(wKey[129] ^ X1), output[8..]);
+            Pack.UInt32_To_BE((uint)(wKey[128] ^ X0), output[12..]);
+        }
+
+        internal override void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            X3 = wKey[131] ^ (int)Pack.BE_To_UInt32(input);
+            X2 = wKey[130] ^ (int)Pack.BE_To_UInt32(input[4..]);
+            X1 = wKey[129] ^ (int)Pack.BE_To_UInt32(input[8..]);
+            X0 = wKey[128] ^ (int)Pack.BE_To_UInt32(input[12..]);
+
+            Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[124]; X1 ^= wKey[125]; X2 ^= wKey[126]; X3 ^= wKey[127];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[120]; X1 ^= wKey[121]; X2 ^= wKey[122]; X3 ^= wKey[123];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[116]; X1 ^= wKey[117]; X2 ^= wKey[118]; X3 ^= wKey[119];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[112]; X1 ^= wKey[113]; X2 ^= wKey[114]; X3 ^= wKey[115];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[108]; X1 ^= wKey[109]; X2 ^= wKey[110]; X3 ^= wKey[111];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[104]; X1 ^= wKey[105]; X2 ^= wKey[106]; X3 ^= wKey[107];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[100]; X1 ^= wKey[101]; X2 ^= wKey[102]; X3 ^= wKey[103];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+            X0 ^= wKey[96]; X1 ^= wKey[97]; X2 ^= wKey[98]; X3 ^= wKey[99];
+            InverseLT(); Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[92]; X1 ^= wKey[93]; X2 ^= wKey[94]; X3 ^= wKey[95];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[88]; X1 ^= wKey[89]; X2 ^= wKey[90]; X3 ^= wKey[91];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[84]; X1 ^= wKey[85]; X2 ^= wKey[86]; X3 ^= wKey[87];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[80]; X1 ^= wKey[81]; X2 ^= wKey[82]; X3 ^= wKey[83];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[76]; X1 ^= wKey[77]; X2 ^= wKey[78]; X3 ^= wKey[79];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[72]; X1 ^= wKey[73]; X2 ^= wKey[74]; X3 ^= wKey[75];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[68]; X1 ^= wKey[69]; X2 ^= wKey[70]; X3 ^= wKey[71];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+            X0 ^= wKey[64]; X1 ^= wKey[65]; X2 ^= wKey[66]; X3 ^= wKey[67];
+            InverseLT(); Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[60]; X1 ^= wKey[61]; X2 ^= wKey[62]; X3 ^= wKey[63];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[56]; X1 ^= wKey[57]; X2 ^= wKey[58]; X3 ^= wKey[59];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[52]; X1 ^= wKey[53]; X2 ^= wKey[54]; X3 ^= wKey[55];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[48]; X1 ^= wKey[49]; X2 ^= wKey[50]; X3 ^= wKey[51];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[44]; X1 ^= wKey[45]; X2 ^= wKey[46]; X3 ^= wKey[47];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[40]; X1 ^= wKey[41]; X2 ^= wKey[42]; X3 ^= wKey[43];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[36]; X1 ^= wKey[37]; X2 ^= wKey[38]; X3 ^= wKey[39];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+            X0 ^= wKey[32]; X1 ^= wKey[33]; X2 ^= wKey[34]; X3 ^= wKey[35];
+            InverseLT(); Ib7(X0, X1, X2, X3);
+            X0 ^= wKey[28]; X1 ^= wKey[29]; X2 ^= wKey[30]; X3 ^= wKey[31];
+            InverseLT(); Ib6(X0, X1, X2, X3);
+            X0 ^= wKey[24]; X1 ^= wKey[25]; X2 ^= wKey[26]; X3 ^= wKey[27];
+            InverseLT(); Ib5(X0, X1, X2, X3);
+            X0 ^= wKey[20]; X1 ^= wKey[21]; X2 ^= wKey[22]; X3 ^= wKey[23];
+            InverseLT(); Ib4(X0, X1, X2, X3);
+            X0 ^= wKey[16]; X1 ^= wKey[17]; X2 ^= wKey[18]; X3 ^= wKey[19];
+            InverseLT(); Ib3(X0, X1, X2, X3);
+            X0 ^= wKey[12]; X1 ^= wKey[13]; X2 ^= wKey[14]; X3 ^= wKey[15];
+            InverseLT(); Ib2(X0, X1, X2, X3);
+            X0 ^= wKey[8]; X1 ^= wKey[9]; X2 ^= wKey[10]; X3 ^= wKey[11];
+            InverseLT(); Ib1(X0, X1, X2, X3);
+            X0 ^= wKey[4]; X1 ^= wKey[5]; X2 ^= wKey[6]; X3 ^= wKey[7];
+            InverseLT(); Ib0(X0, X1, X2, X3);
+
+            Pack.UInt32_To_BE((uint)(X3 ^ wKey[3]), output);
+            Pack.UInt32_To_BE((uint)(X2 ^ wKey[2]), output[4..]);
+            Pack.UInt32_To_BE((uint)(X1 ^ wKey[1]), output[8..]);
+            Pack.UInt32_To_BE((uint)(X0 ^ wKey[0]), output[12..]);
+        }
+#else
+        internal override void EncryptBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             X3 = (int)Pack.BE_To_UInt32(input, inOff);
             X2 = (int)Pack.BE_To_UInt32(input, inOff + 4);
@@ -211,15 +327,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_BE((uint)(wKey[128] ^ X0), output, outOff + 12);
         }
 
-        /**
-        * Decrypt one block of ciphertext.
-        *
-        * @param input the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param output the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        */
-        protected override void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff)
+        internal override void DecryptBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             X3 = wKey[131] ^ (int)Pack.BE_To_UInt32(input, inOff);
             X2 = wKey[130] ^ (int)Pack.BE_To_UInt32(input, inOff + 4);
@@ -295,5 +403,6 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_BE((uint)(X1 ^ wKey[1]), output, outOff + 8);
             Pack.UInt32_To_BE((uint)(X0 ^ wKey[0]), output, outOff + 12);
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/engines/TwofishEngine.cs b/crypto/src/crypto/engines/TwofishEngine.cs
index 0758451e4..09c7114f2 100644
--- a/crypto/src/crypto/engines/TwofishEngine.cs
+++ b/crypto/src/crypto/engines/TwofishEngine.cs
@@ -294,16 +294,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             get { return "Twofish"; }
         }
 
-		public bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
-		public int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+		public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             if (workingKey == null)
                 throw new InvalidOperationException("Twofish not initialised");
@@ -311,6 +302,16 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(input, inOff, BLOCK_SIZE, "input buffer too short");
             Check.OutputLength(output, outOff, BLOCK_SIZE, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (encrypting)
+            {
+                EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+            else
+            {
+                DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+            }
+#else
             if (encrypting)
             {
                 EncryptBlock(input, inOff, output, outOff);
@@ -319,17 +320,32 @@ namespace Org.BouncyCastle.Crypto.Engines
             {
                 DecryptBlock(input, inOff, output, outOff);
             }
+#endif
 
             return BLOCK_SIZE;
         }
 
-        public void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            if (this.workingKey != null)
+            if (workingKey == null)
+                throw new InvalidOperationException("Twofish not initialised");
+
+            Check.DataLength(input, BLOCK_SIZE, "input buffer too short");
+            Check.OutputLength(output, BLOCK_SIZE, "output buffer too short");
+
+            if (encrypting)
+            {
+                EncryptBlock(input, output);
+            }
+            else
             {
-                SetKey(this.workingKey);
+                DecryptBlock(input, output);
             }
+
+            return BLOCK_SIZE;
         }
+#endif
 
         public int GetBlockSize()
         {
@@ -424,6 +440,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             */
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         /**
         * Encrypt the given input starting at the given offset and place
         * the result in the provided buffer starting at the given offset.
@@ -432,11 +449,80 @@ namespace Org.BouncyCastle.Crypto.Engines
         * encryptBlock uses the pre-calculated gSBox[] and subKey[]
         * arrays.
         */
-        private void EncryptBlock(
-            byte[] src,
-            int srcIndex,
-            byte[] dst,
-            int dstIndex)
+        private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int x0 = (int)Pack.LE_To_UInt32(input) ^ gSubKeys[INPUT_WHITEN];
+            int x1 = (int)Pack.LE_To_UInt32(input[4..]) ^ gSubKeys[INPUT_WHITEN + 1];
+            int x2 = (int)Pack.LE_To_UInt32(input[8..]) ^ gSubKeys[INPUT_WHITEN + 2];
+            int x3 = (int)Pack.LE_To_UInt32(input[12..]) ^ gSubKeys[INPUT_WHITEN + 3];
+
+            int k = ROUND_SUBKEYS;
+            int t0, t1;
+            for (int r = 0; r < ROUNDS; r +=2)
+            {
+                t0 = Fe32_0(x0);
+                t1 = Fe32_3(x1);
+                x2 ^= t0 + t1 + gSubKeys[k++];
+                x2 = Integers.RotateRight(x2, 1);
+                x3 = Integers.RotateLeft(x3, 1) ^ (t0 + 2*t1 + gSubKeys[k++]);
+
+                t0 = Fe32_0(x2);
+                t1 = Fe32_3(x3);
+                x0 ^= t0 + t1 + gSubKeys[k++];
+                x0 = Integers.RotateRight(x0, 1);
+                x1 = Integers.RotateLeft(x1, 1) ^ (t0 + 2*t1 + gSubKeys[k++]);
+            }
+
+            Pack.UInt32_To_LE((uint)(x2 ^ gSubKeys[OUTPUT_WHITEN]), output);
+            Pack.UInt32_To_LE((uint)(x3 ^ gSubKeys[OUTPUT_WHITEN + 1]), output[4..]);
+            Pack.UInt32_To_LE((uint)(x0 ^ gSubKeys[OUTPUT_WHITEN + 2]), output[8..]);
+            Pack.UInt32_To_LE((uint)(x1 ^ gSubKeys[OUTPUT_WHITEN + 3]), output[12..]);
+        }
+
+        /**
+        * Decrypt the given input starting at the given offset and place
+        * the result in the provided buffer starting at the given offset.
+        * The input will be an exact multiple of our blocksize.
+        */
+        private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int x2 = (int)Pack.LE_To_UInt32(input) ^ gSubKeys[OUTPUT_WHITEN];
+            int x3 = (int)Pack.LE_To_UInt32(input[4..]) ^ gSubKeys[OUTPUT_WHITEN + 1];
+            int x0 = (int)Pack.LE_To_UInt32(input[8..]) ^ gSubKeys[OUTPUT_WHITEN + 2];
+            int x1 = (int)Pack.LE_To_UInt32(input[12..]) ^ gSubKeys[OUTPUT_WHITEN + 3];
+
+            int k = ROUND_SUBKEYS + 2 * ROUNDS -1 ;
+            int t0, t1;
+            for (int r = 0; r< ROUNDS ; r +=2)
+            {
+                t0 = Fe32_0(x2);
+                t1 = Fe32_3(x3);
+                x1 ^= t0 + 2*t1 + gSubKeys[k--];
+                x0 = Integers.RotateLeft(x0, 1) ^ (t0 + t1 + gSubKeys[k--]);
+                x1 = Integers.RotateRight(x1, 1);
+
+                t0 = Fe32_0(x0);
+                t1 = Fe32_3(x1);
+                x3 ^= t0 + 2*t1 + gSubKeys[k--];
+                x2 = Integers.RotateLeft(x2, 1) ^ (t0 + t1 + gSubKeys[k--]);
+                x3 = Integers.RotateRight(x3, 1);
+            }
+
+            Pack.UInt32_To_LE((uint)(x0 ^ gSubKeys[INPUT_WHITEN]), output);
+            Pack.UInt32_To_LE((uint)(x1 ^ gSubKeys[INPUT_WHITEN + 1]), output[4..]);
+            Pack.UInt32_To_LE((uint)(x2 ^ gSubKeys[INPUT_WHITEN + 2]), output[8..]);
+            Pack.UInt32_To_LE((uint)(x3 ^ gSubKeys[INPUT_WHITEN + 3]), output[12..]);
+        }
+#else
+        /**
+        * Encrypt the given input starting at the given offset and place
+        * the result in the provided buffer starting at the given offset.
+        * The input will be an exact multiple of our blocksize.
+        *
+        * encryptBlock uses the pre-calculated gSBox[] and subKey[]
+        * arrays.
+        */
+        private void EncryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             int x0 = (int)Pack.LE_To_UInt32(src, srcIndex) ^ gSubKeys[INPUT_WHITEN];
             int x1 = (int)Pack.LE_To_UInt32(src, srcIndex + 4) ^ gSubKeys[INPUT_WHITEN + 1];
@@ -471,11 +557,7 @@ namespace Org.BouncyCastle.Crypto.Engines
         * the result in the provided buffer starting at the given offset.
         * The input will be an exact multiple of our blocksize.
         */
-        private void DecryptBlock(
-            byte[] src,
-            int srcIndex,
-            byte[] dst,
-            int dstIndex)
+        private void DecryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
         {
             int x2 = (int)Pack.LE_To_UInt32(src, srcIndex) ^ gSubKeys[OUTPUT_WHITEN];
             int x3 = (int)Pack.LE_To_UInt32(src, srcIndex + 4) ^ gSubKeys[OUTPUT_WHITEN + 1];
@@ -484,7 +566,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             int k = ROUND_SUBKEYS + 2 * ROUNDS -1 ;
             int t0, t1;
-            for (int r = 0; r< ROUNDS ; r +=2)
+            for (int r = 0; r < ROUNDS ; r += 2)
             {
                 t0 = Fe32_0(x2);
                 t1 = Fe32_3(x3);
@@ -504,6 +586,7 @@ namespace Org.BouncyCastle.Crypto.Engines
             Pack.UInt32_To_LE((uint)(x2 ^ gSubKeys[INPUT_WHITEN + 2]), dst, dstIndex + 8);
             Pack.UInt32_To_LE((uint)(x3 ^ gSubKeys[INPUT_WHITEN + 3]), dst, dstIndex + 12);
         }
+#endif
 
         /*
         * TODO:  This can be optimised and made cleaner by combining
diff --git a/crypto/src/crypto/engines/VMPCEngine.cs b/crypto/src/crypto/engines/VMPCEngine.cs
index 852901e36..f34259248 100644
--- a/crypto/src/crypto/engines/VMPCEngine.cs
+++ b/crypto/src/crypto/engines/VMPCEngine.cs
@@ -97,18 +97,41 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             for (int i = 0; i < len; i++)
             {
-                s = P[(s + P[n & 0xff]) & 0xff];
-                byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff];
+                byte pn = P[n];
+                s = P[(s + pn) & 0xff];
+                byte ps = P[s];
+                byte z = P[(P[ps] + 1) & 0xff];
                 // encryption
-                byte temp = P[n & 0xff];
-                P[n & 0xff] = P[s & 0xff];
-                P[s & 0xff] = temp;
-                n = (byte) ((n + 1) & 0xff);
+                P[n] = ps;
+                P[s] = pn;
+                n = (byte)(n + 1);
+
+                // xor
+                output[i + outOff] = (byte)(input[i + inOff] ^ z);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                byte pn = P[n];
+                s = P[(s + pn) & 0xff];
+                byte ps = P[s];
+                byte z = P[(P[ps] + 1) & 0xff];
+                // encryption
+                P[n] = ps;
+                P[s] = pn;
+                n = (byte)(n + 1);
 
                 // xor
-                output[i + outOff] = (byte) (input[i + inOff] ^ z);
+                output[i] = (byte)(input[i] ^ z);
             }
         }
+#endif
 
         public virtual void Reset()
         {
diff --git a/crypto/src/crypto/engines/XTEAEngine.cs b/crypto/src/crypto/engines/XTEAEngine.cs
index 5fcfa4a57..0f028e9dd 100644
--- a/crypto/src/crypto/engines/XTEAEngine.cs
+++ b/crypto/src/crypto/engines/XTEAEngine.cs
@@ -40,11 +40,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 			get { return "XTEA"; }
 		}
 
-        public virtual bool IsPartialBlockOkay
-		{
-			get { return false; }
-		}
-
         public virtual int GetBlockSize()
 		{
 			return block_size;
@@ -76,11 +71,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 			setKey(p.GetKey());
 		}
 
-        public virtual int ProcessBlock(
-			byte[]	inBytes,
-			int		inOff,
-			byte[]	outBytes,
-			int		outOff)
+        public virtual int ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
 			if (!_initialised)
 				throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -88,14 +79,31 @@ namespace Org.BouncyCastle.Crypto.Engines
             Check.DataLength(inBytes, inOff, block_size, "input buffer too short");
             Check.OutputLength(outBytes, outOff, block_size, "output buffer too short");
 
-            return _forEncryption
-				?	encryptBlock(inBytes, inOff, outBytes, outOff)
-				:	decryptBlock(inBytes, inOff, outBytes, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return _forEncryption
+				? EncryptBlock(inBytes.AsSpan(inOff), outBytes.AsSpan(outOff))
+				: DecryptBlock(inBytes.AsSpan(inOff), outBytes.AsSpan(outOff));
+#else
+			return _forEncryption
+				? EncryptBlock(inBytes, inOff, outBytes, outOff)
+				: DecryptBlock(inBytes, inOff, outBytes, outOff);
+#endif
 		}
 
-        public virtual void Reset()
-		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+			if (!_initialised)
+				throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+			Check.DataLength(input, block_size, "input buffer too short");
+			Check.OutputLength(output, block_size, "output buffer too short");
+
+			return _forEncryption
+				? EncryptBlock(input, output)
+				: DecryptBlock(input, output);
 		}
+#endif
 
 		/**
 		* Re-key the cipher.
@@ -119,13 +127,45 @@ namespace Org.BouncyCastle.Crypto.Engines
 			}
 		}
 
-		private int encryptBlock(
-			byte[]  inBytes,
-			int     inOff,
-			byte[]  outBytes,
-			int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
 		{
 			// Pack bytes into integers
+			uint v0 = Pack.BE_To_UInt32(input);
+			uint v1 = Pack.BE_To_UInt32(input[4..]);
+
+			for (int i = 0; i < rounds; i++)
+			{
+				v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ _sum0[i];
+				v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ _sum1[i];
+			}
+
+			Pack.UInt32_To_BE(v0, output);
+			Pack.UInt32_To_BE(v1, output[4..]);
+
+			return block_size;
+		}
+
+		private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			// Pack bytes into integers
+			uint v0 = Pack.BE_To_UInt32(input);
+			uint v1 = Pack.BE_To_UInt32(input[4..]);
+
+			for (int i = rounds - 1; i >= 0; i--)
+			{
+				v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ _sum1[i];
+				v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ _sum0[i];
+			}
+
+			Pack.UInt32_To_BE(v0, output);
+			Pack.UInt32_To_BE(v1, output[4..]);
+
+			return block_size;
+		}
+#else
+		private int EncryptBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+		{
 			uint v0 = Pack.BE_To_UInt32(inBytes, inOff);
 			uint v1 = Pack.BE_To_UInt32(inBytes, inOff + 4);
 
@@ -141,11 +181,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 			return block_size;
 		}
 
-		private int decryptBlock(
-			byte[]	inBytes,
-			int		inOff,
-			byte[]	outBytes,
-			int		outOff)
+		private int DecryptBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
 		{
 			// Pack bytes into integers
 			uint v0 = Pack.BE_To_UInt32(inBytes, inOff);
@@ -162,5 +198,6 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			return block_size;
 		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/fpe/SP80038G.cs b/crypto/src/crypto/fpe/SP80038G.cs
index 53efc1499..a9dc7f144 100644
--- a/crypto/src/crypto/fpe/SP80038G.cs
+++ b/crypto/src/crypto/fpe/SP80038G.cs
@@ -301,14 +301,13 @@ namespace Org.BouncyCastle.Crypto.Fpe
             int t = T.Length;
 
             // i.
-            BigInteger numAB = num(bigRadix, AB);
-            byte[] bytesAB = BigIntegers.AsUnsignedByteArray(numAB);
-
             int zeroes = -(t + b + 1) & 15;
             byte[] Q = new byte[t + zeroes + 1 + b];
             Array.Copy(T, 0, Q, 0, t);
             Q[t + zeroes] = (byte)round;
-            Array.Copy(bytesAB, 0, Q, Q.Length - bytesAB.Length, bytesAB.Length);
+
+            BigInteger numAB = num(bigRadix, AB);
+            BigIntegers.AsUnsignedByteArray(numAB, Q, Q.Length - b, b);
 
             // ii.
             byte[] R = prf(cipher, Arrays.Concatenate(P, Q));
@@ -319,47 +318,43 @@ namespace Org.BouncyCastle.Crypto.Fpe
             {
                 int sBlocksLen = (d + BLOCK_SIZE - 1) / BLOCK_SIZE;
                 sBlocks = new byte[sBlocksLen * BLOCK_SIZE];
+
+                uint j0 = Pack.BE_To_UInt32(R, BLOCK_SIZE - 4);
                 Array.Copy(R, 0, sBlocks, 0, BLOCK_SIZE);
 
-                byte[] uint32 = new byte[4];
                 for (uint j = 1; j < sBlocksLen; ++j)
                 {
                     int sOff = (int)(j * BLOCK_SIZE);
-                    Array.Copy(R, 0, sBlocks, sOff, BLOCK_SIZE);
-                    Pack.UInt32_To_BE(j, uint32, 0);
-                    xor(uint32, 0, sBlocks, sOff + BLOCK_SIZE - 4, 4);
+
+                    Array.Copy(R, 0, sBlocks, sOff, BLOCK_SIZE - 4);
+                    Pack.UInt32_To_BE(j0 ^ j, sBlocks, sOff + BLOCK_SIZE - 4);
+
                     cipher.ProcessBlock(sBlocks, sOff, sBlocks, sOff);
                 }
             }
 
             // iv.
-            return num(sBlocks, 0, d);
+            return new BigInteger(1, sBlocks, 0, d);
         }
 
-        protected static BigInteger calculateY_FF3(IBlockCipher cipher, BigInteger bigRadix, byte[] T, int wOff, uint round, ushort[] AB)
+        protected static BigInteger calculateY_FF3(IBlockCipher cipher, BigInteger bigRadix, byte[] T, int wOff,
+            uint round, ushort[] AB)
         {
             // ii.
             byte[] P = new byte[BLOCK_SIZE];
-            Pack.UInt32_To_BE(round, P, 0);
-            xor(T, wOff, P, 0, 4);
-            BigInteger numAB = num(bigRadix, AB);
-
-            byte[] bytesAB = BigIntegers.AsUnsignedByteArray(numAB);
+            Pack.UInt32_To_BE(Pack.BE_To_UInt32(T, wOff) ^ round, P, 0);
 
-            if ((P.Length - bytesAB.Length) < 4)  // to be sure...
-            {
-                throw new InvalidOperationException("input out of range");
-            }
-            Array.Copy(bytesAB, 0, P, P.Length - bytesAB.Length, bytesAB.Length);
+            BigInteger numAB = num(bigRadix, AB);
+            BigIntegers.AsUnsignedByteArray(numAB, P, 4, BLOCK_SIZE - 4);
 
             // iii.
-            rev(P);
+            Array.Reverse(P);
             cipher.ProcessBlock(P, 0, P, 0);
-            rev(P);
+            Array.Reverse(P);
             byte[] S = P;
 
             // iv.
-            return num(S, 0, S.Length);
+            return new BigInteger(1, S);
         }
 
         protected static void checkArgs(IBlockCipher cipher, bool isFF1, int radix, ushort[] buf, int off, int len)
@@ -471,8 +466,8 @@ namespace Org.BouncyCastle.Crypto.Fpe
             int m = u;
 
             // Note we keep A, B in reverse order throughout
-            rev(A);
-            rev(B);
+            Array.Reverse(A);
+            Array.Reverse(B);
 
             for (int i = 7; i >= 0; --i)
             {
@@ -494,8 +489,8 @@ namespace Org.BouncyCastle.Crypto.Fpe
                 str(bigRadix, c, m, C, 0);
             }
 
-            rev(A);
-            rev(B);
+            Array.Reverse(A);
+            Array.Reverse(B);
 
             return Arrays.Concatenate(A, B);
         }
@@ -539,8 +534,8 @@ namespace Org.BouncyCastle.Crypto.Fpe
             int m = v;
 
             // Note we keep A, B in reverse order throughout
-            rev(a);
-            rev(b);
+            Array.Reverse(a);
+            Array.Reverse(b);
 
             for (uint i = 0; i < 8; ++i)
             {
@@ -562,17 +557,12 @@ namespace Org.BouncyCastle.Crypto.Fpe
                 str(bigRadix, c, m, C, 0);
             }
 
-            rev(a);
-            rev(b);
+            Array.Reverse(a);
+            Array.Reverse(b);
 
             return Arrays.Concatenate(a, b);
         }
 
-        protected static BigInteger num(byte[] buf, int off, int len)
-        {
-            return new BigInteger(1, Arrays.CopyOfRange(buf, off, off + len));
-        }
-
         protected static BigInteger num(BigInteger R, ushort[] x)
         {
             BigInteger result = BigInteger.Zero;
@@ -586,9 +576,7 @@ namespace Org.BouncyCastle.Crypto.Fpe
         protected static byte[] prf(IBlockCipher c, byte[] x)
         {
             if ((x.Length % BLOCK_SIZE) != 0)
-            {
                 throw new ArgumentException();
-            }
 
             int m = x.Length / BLOCK_SIZE;
             byte[] y = new byte[BLOCK_SIZE];
@@ -602,42 +590,11 @@ namespace Org.BouncyCastle.Crypto.Fpe
             return y;
         }
 
-        //    protected static void rev(byte[] x, int xOff, byte[] y, int yOff, int len)
-        //    {
-        //        for (int i = 1; i <= len; ++i)
-        //        {
-        //            y[yOff + len - i] = x[xOff + i - 1];
-        //        }
-        //    }
-
-        protected static void rev(byte[] x)
-        {
-            int half = x.Length / 2, end = x.Length - 1;
-            for (int i = 0; i < half; ++i)
-            {
-                byte tmp = x[i];
-                x[i] = x[end - i];
-                x[end - i] = tmp;
-            }
-        }
-
-        protected static void rev(ushort[] x)
-        {
-            int half = x.Length / 2, end = x.Length - 1;
-            for (int i = 0; i < half; ++i)
-            {
-                ushort tmp = x[i];
-                x[i] = x[end - i];
-                x[end - i] = tmp;
-            }
-        }
-
         protected static void str(BigInteger R, BigInteger x, int m, ushort[] output, int off)
         {
             if (x.SignValue < 0)
-            {
                 throw new ArgumentException();
-            }
+
             for (int i = 1; i <= m; ++i)
             {
                 BigInteger[] qr = x.DivideAndRemainder(R);
@@ -645,9 +602,7 @@ namespace Org.BouncyCastle.Crypto.Fpe
                 x = qr[0];
             }
             if (x.SignValue != 0)
-            {
                 throw new ArgumentException();
-            }
         }
 
         protected static void xor(byte[] x, int xOff, byte[] y, int yOff, int len)
diff --git a/crypto/src/crypto/generators/BaseKdfBytesGenerator.cs b/crypto/src/crypto/generators/BaseKdfBytesGenerator.cs
index bca420711..a1839dc33 100644
--- a/crypto/src/crypto/generators/BaseKdfBytesGenerator.cs
+++ b/crypto/src/crypto/generators/BaseKdfBytesGenerator.cs
@@ -11,7 +11,7 @@ namespace Org.BouncyCastle.Crypto.Generators
     * <br/>
     * This implementation is based on ISO 18033/P1363a.
     */
-    public class BaseKdfBytesGenerator
+    public abstract class BaseKdfBytesGenerator
         : IDerivationFunction
     {
         private int     counterStart;
@@ -25,26 +25,22 @@ namespace Org.BouncyCastle.Crypto.Generators
         * @param counterStart value of counter.
         * @param digest the digest to be used as the source of derived keys.
         */
-        public BaseKdfBytesGenerator(int counterStart, IDigest digest)
+        protected BaseKdfBytesGenerator(int counterStart, IDigest digest)
         {
             this.counterStart = counterStart;
             this.digest = digest;
         }
 
-        public virtual void Init(IDerivationParameters parameters)
+        public void Init(IDerivationParameters parameters)
         {
-            if (parameters is KdfParameters)
+            if (parameters is KdfParameters kdfParameters)
             {
-                KdfParameters   p = (KdfParameters)parameters;
-
-                shared = p.GetSharedSecret();
-                iv = p.GetIV();
+                shared = kdfParameters.GetSharedSecret();
+                iv = kdfParameters.GetIV();
             }
-            else if (parameters is Iso18033KdfParameters)
+            else if (parameters is Iso18033KdfParameters iso18033KdfParameters)
             {
-                Iso18033KdfParameters p = (Iso18033KdfParameters)parameters;
-
-                shared = p.GetSeed();
+                shared = iso18033KdfParameters.GetSeed();
                 iv = null;
             }
             else
@@ -56,10 +52,7 @@ namespace Org.BouncyCastle.Crypto.Generators
         /**
         * return the underlying digest.
         */
-        public virtual IDigest Digest
-        {
-            get { return digest; }
-        }
+        public IDigest Digest => digest;
 
         /**
         * fill len bytes of the output buffer with bytes generated from
@@ -68,13 +61,15 @@ namespace Org.BouncyCastle.Crypto.Generators
         * @throws ArgumentException if the size of the request will cause an overflow.
         * @throws DataLengthException if the out buffer is too small.
         */
-        public virtual int GenerateBytes(byte[] output, int outOff, int length)
+        public int GenerateBytes(byte[] output, int outOff, int length)
         {
-            if ((output.Length - length) < outOff)
-                throw new DataLengthException("output buffer too small");
+            Check.OutputLength(output, outOff, length, "output buffer too small");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
             long oBytes = length;
-            int outLen = digest.GetDigestSize();
+            int digestSize = digest.GetDigestSize();
 
             //
             // this is at odds with the standard implementation, the
@@ -85,9 +80,9 @@ namespace Org.BouncyCastle.Crypto.Generators
             if (oBytes > ((2L << 32) - 1))
                 throw new ArgumentException("Output length too large");
 
-            int cThreshold = (int)((oBytes + outLen - 1) / outLen);
+            int cThreshold = (int)((oBytes + digestSize - 1) / digestSize);
 
-            byte[] dig = new byte[digest.GetDigestSize()];
+            byte[] dig = new byte[digestSize];
 
             byte[] C = new byte[4];
             Pack.UInt32_To_BE((uint)counterStart, C, 0);
@@ -106,11 +101,11 @@ namespace Org.BouncyCastle.Crypto.Generators
 
                 digest.DoFinal(dig, 0);
 
-                if (length > outLen)
+                if (length > digestSize)
                 {
-                    Array.Copy(dig, 0, output, outOff, outLen);
-                    outOff += outLen;
-                    length -= outLen;
+                    Array.Copy(dig, 0, output, outOff, digestSize);
+                    outOff += digestSize;
+                    length -= digestSize;
                 }
                 else
                 {
@@ -127,6 +122,69 @@ namespace Org.BouncyCastle.Crypto.Generators
             digest.Reset();
 
             return (int)oBytes;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
+        {
+            long oBytes = output.Length;
+            int digestSize = digest.GetDigestSize();
+
+            //
+            // this is at odds with the standard implementation, the
+            // maximum value should be hBits * (2^32 - 1) where hBits
+            // is the digest output size in bits. We can't have an
+            // array with a long index at the moment...
+            //
+            if (oBytes > ((2L << 32) - 1))
+                throw new ArgumentException("Output length too large");
+
+            int cThreshold = (int)((oBytes + digestSize - 1) / digestSize);
+
+            Span<byte> dig = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+
+            Span<byte> C = stackalloc byte[4];
+            Pack.UInt32_To_BE((uint)counterStart, C);
+
+            uint counterBase = (uint)(counterStart & ~0xFF);
+
+            for (int i = 0; i < cThreshold; i++)
+            {
+                digest.BlockUpdate(shared);
+                digest.BlockUpdate(C);
+
+                if (iv != null)
+                {
+                    digest.BlockUpdate(iv);
+                }
+
+                digest.DoFinal(dig);
+
+                int remaining = output.Length;
+                if (remaining > digestSize)
+                {
+                    dig.CopyTo(output);
+                    output = output[digestSize..];
+                }
+                else
+                {
+                    dig[..remaining].CopyTo(output);
+                }
+
+                if (++C[3] == 0)
+                {
+                    counterBase += 0x100;
+                    Pack.UInt32_To_BE(counterBase, C);
+                }
+            }
+
+            digest.Reset();
+
+            return (int)oBytes;
         }
+#endif
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/generators/DesEdeKeyGenerator.cs b/crypto/src/crypto/generators/DesEdeKeyGenerator.cs
index 904cc71f1..24197e9da 100644
--- a/crypto/src/crypto/generators/DesEdeKeyGenerator.cs
+++ b/crypto/src/crypto/generators/DesEdeKeyGenerator.cs
@@ -26,8 +26,7 @@ namespace Org.BouncyCastle.Crypto.Generators
         *
         * @param param the parameters to be used for key generation
         */
-        protected override void engineInit(
-			KeyGenerationParameters parameters)
+        protected override void EngineInit(KeyGenerationParameters parameters)
         {
 			this.random = parameters.Random;
 			this.strength = (parameters.Strength + 7) / 8;
@@ -50,7 +49,7 @@ namespace Org.BouncyCastle.Crypto.Generators
             }
         }
 
-        protected override byte[] engineGenerateKey()
+        protected override byte[] EngineGenerateKey()
         {
             byte[] newKey = new byte[strength];
 
diff --git a/crypto/src/crypto/generators/DesKeyGenerator.cs b/crypto/src/crypto/generators/DesKeyGenerator.cs
index 4c2051d89..5a1befd71 100644
--- a/crypto/src/crypto/generators/DesKeyGenerator.cs
+++ b/crypto/src/crypto/generators/DesKeyGenerator.cs
@@ -24,10 +24,9 @@ namespace Org.BouncyCastle.Crypto.Generators
 		*
 		* @param param the parameters to be used for key generation
 		*/
-		protected override void engineInit(
-			KeyGenerationParameters parameters)
+		protected override void EngineInit(KeyGenerationParameters parameters)
 		{
-			base.engineInit(parameters);
+			base.EngineInit(parameters);
 
 			if (strength == 0 || strength == (56 / 8))
 			{
@@ -40,7 +39,7 @@ namespace Org.BouncyCastle.Crypto.Generators
 			}
 		}
 
-		protected override byte[] engineGenerateKey()
+		protected override byte[] EngineGenerateKey()
         {
             byte[] newKey = new byte[DesParameters.DesKeyLength];
 
diff --git a/crypto/src/crypto/generators/ECKeyPairGenerator.cs b/crypto/src/crypto/generators/ECKeyPairGenerator.cs
index 7b6ee168b..6aba6921e 100644
--- a/crypto/src/crypto/generators/ECKeyPairGenerator.cs
+++ b/crypto/src/crypto/generators/ECKeyPairGenerator.cs
@@ -83,7 +83,7 @@ namespace Org.BouncyCastle.Crypto.Generators
 
             if (this.random == null)
             {
-                this.random = new SecureRandom();
+                this.random = CryptoServicesRegistrar.GetSecureRandom();
             }
         }
 
diff --git a/crypto/src/crypto/generators/HKdfBytesGenerator.cs b/crypto/src/crypto/generators/HKdfBytesGenerator.cs
index 6f36a6faa..43cd66525 100644
--- a/crypto/src/crypto/generators/HKdfBytesGenerator.cs
+++ b/crypto/src/crypto/generators/HKdfBytesGenerator.cs
@@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Crypto.Generators
      * (output keying material) and is likely to have better security properties
      * than KDF's based on just a hash function.
      */
-    public class HkdfBytesGenerator
+    public sealed class HkdfBytesGenerator
         : IDerivationFunction
     {
         private HMac hMacHash;
@@ -35,12 +35,11 @@ namespace Org.BouncyCastle.Crypto.Generators
             this.hashLen = hash.GetDigestSize();
         }
 
-        public virtual void Init(IDerivationParameters parameters)
+        public void Init(IDerivationParameters parameters)
         {
-            if (!(parameters is HkdfParameters))
+            if (!(parameters is HkdfParameters hkdfParameters))
                 throw new ArgumentException("HKDF parameters required for HkdfBytesGenerator", "parameters");
 
-            HkdfParameters hkdfParameters = (HkdfParameters)parameters;
             if (hkdfParameters.SkipExtract)
             {
                 // use IKM directly as PRK
@@ -108,45 +107,70 @@ namespace Org.BouncyCastle.Crypto.Generators
             hMacHash.DoFinal(currentT, 0);
         }
 
-        public virtual IDigest Digest
-        {
-            get { return hMacHash.GetUnderlyingDigest(); }
-        }
+        public IDigest Digest => hMacHash.GetUnderlyingDigest();
 
-        public virtual int GenerateBytes(byte[] output, int outOff, int len)
+        public int GenerateBytes(byte[] output, int outOff, int length)
         {
-            if (generatedBytes + len > 255 * hashLen)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
+            if (generatedBytes > 255 * hashLen - length)
+                throw new DataLengthException("HKDF may only be used for 255 * HashLen bytes of output");
+
+            int toGenerate = length;
+            int posInT = generatedBytes % hashLen;
+            if (posInT != 0)
             {
-                throw new DataLengthException(
-                    "HKDF may only be used for 255 * HashLen bytes of output");
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(hashLen - posInT, toGenerate);
+                Array.Copy(currentT, posInT, output, outOff, toCopy);
+                generatedBytes += toCopy;
+                toGenerate -= toCopy;
+                outOff += toCopy;
             }
 
-            if (generatedBytes % hashLen == 0)
+            while (toGenerate > 0)
             {
                 ExpandNext();
+                int toCopy = System.Math.Min(hashLen, toGenerate);
+                Array.Copy(currentT, 0, output, outOff, toCopy);
+                generatedBytes += toCopy;
+                toGenerate -= toCopy;
+                outOff += toCopy;
             }
 
-            // copy what is left in the currentT (1..hash
-            int toGenerate = len;
+            return length;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
+        {
+            int length = output.Length;
+            if (generatedBytes > 255 * hashLen - length)
+                throw new DataLengthException("HKDF may only be used for 255 * HashLen bytes of output");
+
             int posInT = generatedBytes % hashLen;
-            int leftInT = hashLen - generatedBytes % hashLen;
-            int toCopy = System.Math.Min(leftInT, toGenerate);
-            Array.Copy(currentT, posInT, output, outOff, toCopy);
-            generatedBytes += toCopy;
-            toGenerate -= toCopy;
-            outOff += toCopy;
+            if (posInT != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(hashLen - posInT, output.Length);
+                currentT.AsSpan(posInT, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
 
-            while (toGenerate > 0)
+            while (!output.IsEmpty)
             {
                 ExpandNext();
-                toCopy = System.Math.Min(hashLen, toGenerate);
-                Array.Copy(currentT, 0, output, outOff, toCopy);
+                int toCopy = System.Math.Min(hashLen, output.Length);
+                currentT.AsSpan(0, toCopy).CopyTo(output);
                 generatedBytes += toCopy;
-                toGenerate -= toCopy;
-                outOff += toCopy;
+                output = output[toCopy..];
             }
 
-            return len;
+            return length;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/generators/KDFCounterBytesGenerator.cs b/crypto/src/crypto/generators/KDFCounterBytesGenerator.cs
index 0d7647289..7fa50e2fa 100644
--- a/crypto/src/crypto/generators/KDFCounterBytesGenerator.cs
+++ b/crypto/src/crypto/generators/KDFCounterBytesGenerator.cs
@@ -3,17 +3,12 @@
 using Org.BouncyCastle.Crypto.Macs;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Generators
 {
-    public class KdfCounterBytesGenerator : IMacDerivationFunction
+    public sealed class KdfCounterBytesGenerator
+        : IMacDerivationFunction
     {
-
-        private static readonly BigInteger IntegerMax = BigInteger.ValueOf(0x7fffffff);
-        private static readonly BigInteger Two = BigInteger.Two;
-
-
         private readonly IMac prf;
         private readonly int h;
 
@@ -37,13 +32,8 @@ namespace Org.BouncyCastle.Crypto.Generators
 
         public void Init(IDerivationParameters param)
         {
-            KdfCounterParameters kdfParams = param as KdfCounterParameters;
-            if (kdfParams == null)
-            {
+            if (!(param is KdfCounterParameters kdfParams))
                 throw new ArgumentException("Wrong type of arguments given");
-            }
-
-
 
             // --- init mac based PRF ---
 
@@ -57,54 +47,45 @@ namespace Org.BouncyCastle.Crypto.Generators
             int r = kdfParams.R;
             this.ios = new byte[r / 8];
 
-            BigInteger maxSize = Two.Pow(r).Multiply(BigInteger.ValueOf(h));
-            this.maxSizeExcl = maxSize.CompareTo(IntegerMax) == 1 ?
-                int.MaxValue : maxSize.IntValue;
+            BigInteger maxSize = BigInteger.One.ShiftLeft(r).Multiply(BigInteger.ValueOf(h));
+            this.maxSizeExcl = maxSize.BitLength > 31 ? int.MaxValue : maxSize.IntValueExact;
 
             // --- set operational state ---
 
             generatedBytes = 0;
         }
 
-
-        public IMac GetMac()
-        {
-            return prf;
-        }
+        public IMac Mac => prf;
 
         public IDigest Digest
         {
-            get { return prf is HMac ? ((HMac)prf).GetUnderlyingDigest() : null; }
+            get { return (prf as HMac)?.GetUnderlyingDigest(); }
         }
 
         public int GenerateBytes(byte[] output, int outOff, int length)
         {
-            int generatedBytesAfter = generatedBytes + length;
-            if (generatedBytesAfter < 0 || generatedBytesAfter >= maxSizeExcl)
-            {
-                throw new DataLengthException(
-                    "Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
-            }
-
-            if (generatedBytes % h == 0)
-            {
-                generateNext();
-            }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
+            if (generatedBytes >= maxSizeExcl - length)
+                throw new DataLengthException("Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
 
-            // copy what is left in the currentT (1..hash
             int toGenerate = length;
             int posInK = generatedBytes % h;
-            int leftInK = h - generatedBytes % h;
-            int toCopy = System.Math.Min(leftInK, toGenerate);
-            Array.Copy(k, posInK, output, outOff, toCopy);
-            generatedBytes += toCopy;
-            toGenerate -= toCopy;
-            outOff += toCopy;
+            if (posInK != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(h - posInK, toGenerate);
+                Array.Copy(k, posInK, output, outOff, toCopy);
+                generatedBytes += toCopy;
+                toGenerate -= toCopy;
+                outOff += toCopy;
+            }
 
             while (toGenerate > 0)
             {
-                generateNext();
-                toCopy = System.Math.Min(h, toGenerate);
+                GenerateNext();
+                int toCopy = System.Math.Min(h, toGenerate);
                 Array.Copy(k, 0, output, outOff, toCopy);
                 generatedBytes += toCopy;
                 toGenerate -= toCopy;
@@ -112,38 +93,65 @@ namespace Org.BouncyCastle.Crypto.Generators
             }
 
             return length;
-
+#endif
         }
 
-        private void generateNext()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
         {
+            int length = output.Length;
+            if (generatedBytes >= maxSizeExcl - length)
+                throw new DataLengthException("Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
+
+            int posInK = generatedBytes % h;
+            if (posInK != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(h - posInK, output.Length);
+                k.AsSpan(posInK, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
 
+            while (!output.IsEmpty)
+            {
+                GenerateNext();
+                int toCopy = System.Math.Min(h, output.Length);
+                k.AsSpan(0, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
+
+            return length;
+        }
+#endif
+
+        private void GenerateNext()
+        {
             int i = generatedBytes / h + 1;
 
             // encode i into counter buffer
             switch (ios.Length)
             {
-                case 4:
-                    ios[0] = (byte)(i >> 24);
-                    goto case 3;
+            case 4:
+                ios[0] = (byte)(i >> 24);
                 // fall through
-                case 3:
-                    ios[ios.Length - 3] = (byte)(i >> 16);
-                    // fall through
-                    goto case 2;
-                case 2:
-                    ios[ios.Length - 2] = (byte)(i >> 8);
-                    // fall through
-                    goto case 1;
-                case 1:
-                    ios[ios.Length - 1] = (byte)i;
-                    break;
-                default:
-                    throw new InvalidOperationException("Unsupported size of counter i");
+                goto case 3;
+            case 3:
+                ios[ios.Length - 3] = (byte)(i >> 16);
+                // fall through
+                goto case 2;
+            case 2:
+                ios[ios.Length - 2] = (byte)(i >> 8);
+                // fall through
+                goto case 1;
+            case 1:
+                ios[ios.Length - 1] = (byte)i;
+                break;
+            default:
+                throw new InvalidOperationException("Unsupported size of counter i");
             }
 
-
-
             // special case for K(0): K(0) is empty, so no update
             prf.BlockUpdate(fixedInputDataCtrPrefix, 0, fixedInputDataCtrPrefix.Length);
             prf.BlockUpdate(ios, 0, ios.Length);
@@ -151,4 +159,4 @@ namespace Org.BouncyCastle.Crypto.Generators
             prf.DoFinal(k, 0);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/generators/KDFDoublePipelineIterationBytesGenerator.cs b/crypto/src/crypto/generators/KDFDoublePipelineIterationBytesGenerator.cs
index 63c0787f3..01feda6f4 100644
--- a/crypto/src/crypto/generators/KDFDoublePipelineIterationBytesGenerator.cs
+++ b/crypto/src/crypto/generators/KDFDoublePipelineIterationBytesGenerator.cs
@@ -6,11 +6,9 @@ using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Crypto.Generators
 {
-    public class KdfDoublePipelineIterationBytesGenerator : IMacDerivationFunction
+    public sealed class KdfDoublePipelineIterationBytesGenerator
+        : IMacDerivationFunction
     {
-        private static readonly BigInteger IntegerMax = BigInteger.ValueOf(0x7fffffff);
-        private static readonly BigInteger Two = BigInteger.Two;
-
         // fields set by the constructor       
         private readonly IMac prf;
         private readonly int h;
@@ -28,7 +26,6 @@ namespace Org.BouncyCastle.Crypto.Generators
         private byte[] a;
         private byte[] k;
 
-
         public KdfDoublePipelineIterationBytesGenerator(IMac prf)
         {
             this.prf = prf;
@@ -37,16 +34,10 @@ namespace Org.BouncyCastle.Crypto.Generators
             this.k = new byte[h];
         }
 
-
         public void Init(IDerivationParameters parameters)
         {
-            KdfDoublePipelineIterationParameters dpiParams = parameters as KdfDoublePipelineIterationParameters;
-            if (dpiParams == null)
-            {
+            if (!(parameters is KdfDoublePipelineIterationParameters dpiParams))
                 throw new ArgumentException("Wrong type of arguments given");
-            }
-
-
 
             // --- init mac based PRF ---
 
@@ -62,13 +53,12 @@ namespace Org.BouncyCastle.Crypto.Generators
             if (dpiParams.UseCounter)
             {
                 // this is more conservative than the spec
-                BigInteger maxSize = Two.Pow(r).Multiply(BigInteger.ValueOf(h));
-                this.maxSizeExcl = maxSize.CompareTo(IntegerMax) == 1 ?
-                    int.MaxValue : maxSize.IntValue;
+                BigInteger maxSize = BigInteger.One.ShiftLeft(r).Multiply(BigInteger.ValueOf(h));
+                this.maxSizeExcl = maxSize.BitLength > 31 ? int.MaxValue : maxSize.IntValueExact;
             }
             else
             {
-                this.maxSizeExcl = IntegerMax.IntValue;
+                this.maxSizeExcl = int.MaxValue;
             }
 
             this.useCounter = dpiParams.UseCounter;
@@ -78,12 +68,8 @@ namespace Org.BouncyCastle.Crypto.Generators
             generatedBytes = 0;
         }
 
-
-
-
-        private void generateNext()
+        private void GenerateNext()
         {
-
             if (generatedBytes == 0)
             {
                 // --- step 4 ---
@@ -107,23 +93,23 @@ namespace Org.BouncyCastle.Crypto.Generators
                 // encode i into counter buffer
                 switch (ios.Length)
                 {
-                    case 4:
-                        ios[0] = (byte)(i >> 24);
-                        // fall through
-                        goto case 3;
-                    case 3:
-                        ios[ios.Length - 3] = (byte)(i >> 16);
-                        // fall through
-                        goto case 2;
-                    case 2:
-                        ios[ios.Length - 2] = (byte)(i >> 8);
-                        // fall through
-                        goto case 1;
-                    case 1:
-                        ios[ios.Length - 1] = (byte)i;
-                        break;
-                    default:
-                        throw new InvalidOperationException("Unsupported size of counter i");
+                case 4:
+                    ios[0] = (byte)(i >> 24);
+                    // fall through
+                    goto case 3;
+                case 3:
+                    ios[ios.Length - 3] = (byte)(i >> 16);
+                    // fall through
+                    goto case 2;
+                case 2:
+                    ios[ios.Length - 2] = (byte)(i >> 8);
+                    // fall through
+                    goto case 1;
+                case 1:
+                    ios[ios.Length - 1] = (byte)i;
+                    break;
+                default:
+                    throw new InvalidOperationException("Unsupported size of counter i");
                 }
                 prf.BlockUpdate(ios, 0, ios.Length);
             }
@@ -134,37 +120,33 @@ namespace Org.BouncyCastle.Crypto.Generators
 
         public IDigest Digest
         {
-            get { return prf is HMac ? ((HMac)prf).GetUnderlyingDigest() : null; }
+            get { return (prf as HMac)?.GetUnderlyingDigest(); }
         }
 
         public int GenerateBytes(byte[] output, int outOff, int length)
         {
-            int generatedBytesAfter = generatedBytes + length;
-            if (generatedBytesAfter < 0 || generatedBytesAfter >= maxSizeExcl)
-            {
-                throw new DataLengthException(
-                    "Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
-            }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
+            if (generatedBytes >= maxSizeExcl - length)
+                throw new DataLengthException("Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
 
-            if (generatedBytes % h == 0)
-            {
-                generateNext();
-            }
-
-            // copy what is left in the currentT (1..hash
             int toGenerate = length;
             int posInK = generatedBytes % h;
-            int leftInK = h - generatedBytes % h;
-            int toCopy = System.Math.Min(leftInK, toGenerate);
-            Array.Copy(k, posInK, output, outOff, toCopy);
-            generatedBytes += toCopy;
-            toGenerate -= toCopy;
-            outOff += toCopy;
+            if (posInK != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(h - posInK, toGenerate);
+                Array.Copy(k, posInK, output, outOff, toCopy);
+                generatedBytes += toCopy;
+                toGenerate -= toCopy;
+                outOff += toCopy;
+            }
 
             while (toGenerate > 0)
             {
-                generateNext();
-                toCopy = System.Math.Min(h, toGenerate);
+                GenerateNext();
+                int toCopy = System.Math.Min(h, toGenerate);
                 Array.Copy(k, 0, output, outOff, toCopy);
                 generatedBytes += toCopy;
                 toGenerate -= toCopy;
@@ -172,11 +154,40 @@ namespace Org.BouncyCastle.Crypto.Generators
             }
 
             return length;
+#endif
         }
 
-        public IMac GetMac()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
         {
-            return prf;
+            int length = output.Length;
+            if (generatedBytes >= maxSizeExcl - length)
+                throw new DataLengthException("Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
+
+            int posInK = generatedBytes % h;
+            if (posInK != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                GenerateNext();
+                int toCopy = System.Math.Min(h - posInK, output.Length);
+                k.AsSpan(posInK, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
+
+            while (!output.IsEmpty)
+            {
+                GenerateNext();
+                int toCopy = System.Math.Min(h, output.Length);
+                k.AsSpan(0, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
+
+            return length;
         }
+#endif
+
+        public IMac Mac => prf;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/generators/KDFFeedbackBytesGenerator.cs b/crypto/src/crypto/generators/KDFFeedbackBytesGenerator.cs
index 11a5552fe..58a035ef6 100644
--- a/crypto/src/crypto/generators/KDFFeedbackBytesGenerator.cs
+++ b/crypto/src/crypto/generators/KDFFeedbackBytesGenerator.cs
@@ -6,11 +6,9 @@ using Org.BouncyCastle.Math;
 
 namespace Org.BouncyCastle.Crypto.Generators
 {
-    public class KdfFeedbackBytesGenerator : IMacDerivationFunction
+    public sealed class KdfFeedbackBytesGenerator
+        : IMacDerivationFunction
     {
-        private static readonly BigInteger IntegerMax = BigInteger.ValueOf(0x7fffffff);
-        private static readonly BigInteger Two = BigInteger.Two;
-
         // please refer to the standard for the meaning of the variable names
         // all field lengths are in bytes, not in bits as specified by the standard
 
@@ -38,15 +36,10 @@ namespace Org.BouncyCastle.Crypto.Generators
             this.k = new byte[h];
         }
 
-
         public void Init(IDerivationParameters parameters)
         {
-            KdfFeedbackParameters feedbackParams = parameters as KdfFeedbackParameters;
-            if (feedbackParams == null)
-            {
+            if (!(parameters is KdfFeedbackParameters feedbackParams))
                 throw new ArgumentException("Wrong type of arguments given");
-            }
-
 
             // --- init mac based PRF ---
 
@@ -62,9 +55,8 @@ namespace Org.BouncyCastle.Crypto.Generators
             if (feedbackParams.UseCounter)
             {
                 // this is more conservative than the spec
-                BigInteger maxSize = Two.Pow(r).Multiply(BigInteger.ValueOf(h));
-                this.maxSizeExcl = maxSize.CompareTo(IntegerMax) == 1 ?
-                    int.MaxValue : maxSize.IntValue;
+                BigInteger maxSize = BigInteger.One.ShiftLeft(r).Multiply(BigInteger.ValueOf(h));
+                this.maxSizeExcl = maxSize.BitLength > 31 ? int.MaxValue : maxSize.IntValueExact;
             }
             else
             {
@@ -81,40 +73,33 @@ namespace Org.BouncyCastle.Crypto.Generators
 
         public IDigest Digest
         {
-            get { return prf is HMac ? ((HMac)prf).GetUnderlyingDigest() : null; }
+            get { return (prf as HMac)?.GetUnderlyingDigest(); }
         }
 
         public int GenerateBytes(byte[] output, int outOff, int length)
         {
-            int generatedBytesAfter = generatedBytes + length;
-            if (generatedBytesAfter < 0 || generatedBytesAfter >= maxSizeExcl)
-            {
-                throw new DataLengthException(
-                    "Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
-            }
-
-            if (generatedBytes % h == 0)
-            {
-                generateNext();
-            }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
+            if (generatedBytes >= maxSizeExcl - length)
+                throw new DataLengthException("Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
 
-            // copy what is left in the currentT (1..hash
             int toGenerate = length;
             int posInK = generatedBytes % h;
-            int leftInK = h - generatedBytes % h;
-
-
-            int toCopy = System.Math.Min(leftInK, toGenerate);
-            Array.Copy(k, posInK, output, outOff, toCopy);
-
-            generatedBytes += toCopy;
-            toGenerate -= toCopy;
-            outOff += toCopy;
+            if (posInK != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(h - posInK, toGenerate);
+                Array.Copy(k, posInK, output, outOff, toCopy);
+                generatedBytes += toCopy;
+                toGenerate -= toCopy;
+                outOff += toCopy;
+            }
 
             while (toGenerate > 0)
             {
-                generateNext();
-                toCopy = System.Math.Min(h, toGenerate);
+                GenerateNext();
+                int toCopy = System.Math.Min(h, toGenerate);
                 Array.Copy(k, 0, output, outOff, toCopy);
                 generatedBytes += toCopy;
                 toGenerate -= toCopy;
@@ -122,11 +107,41 @@ namespace Org.BouncyCastle.Crypto.Generators
             }
 
             return length;
+#endif
         }
 
-        private void generateNext()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
         {
+            int length = output.Length;
+            if (generatedBytes >= maxSizeExcl - length)
+                throw new DataLengthException("Current KDFCTR may only be used for " + maxSizeExcl + " bytes");
 
+            int posInK = generatedBytes % h;
+            if (posInK != 0)
+            {
+                // copy what is left in the currentT (1..hash
+                int toCopy = System.Math.Min(h - posInK, output.Length);
+                k.AsSpan(posInK, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
+
+            while (!output.IsEmpty)
+            {
+                GenerateNext();
+                int toCopy = System.Math.Min(h, output.Length);
+                k.AsSpan(0, toCopy).CopyTo(output);
+                generatedBytes += toCopy;
+                output = output[toCopy..];
+            }
+
+            return length;
+        }
+#endif
+
+        private void GenerateNext()
+        {
             // TODO enable IV
             if (generatedBytes == 0)
             {
@@ -144,23 +159,23 @@ namespace Org.BouncyCastle.Crypto.Generators
                 // encode i into counter buffer
                 switch (ios.Length)
                 {
-                    case 4:
-                        ios[0] = (byte)(i >> 24);
-                        goto case 3;
+                case 4:
+                    ios[0] = (byte)(i >> 24);
                     // fall through
-                    case 3:
-                        ios[ios.Length - 3] = (byte)(i >> 16);
-                        // fall through
-                        goto case 2;
-                    case 2:
-                        ios[ios.Length - 2] = (byte)(i >> 8);
-                        // fall through
-                        goto case 1;
-                    case 1:
-                        ios[ios.Length - 1] = (byte)i;
-                        break;
-                    default:
-                        throw new InvalidOperationException("Unsupported size of counter i");
+                    goto case 3;
+                case 3:
+                    ios[ios.Length - 3] = (byte)(i >> 16);
+                    // fall through
+                    goto case 2;
+                case 2:
+                    ios[ios.Length - 2] = (byte)(i >> 8);
+                    // fall through
+                    goto case 1;
+                case 1:
+                    ios[ios.Length - 1] = (byte)i;
+                    break;
+                default:
+                    throw new InvalidOperationException("Unsupported size of counter i");
                 }
                 prf.BlockUpdate(ios, 0, ios.Length);
             }
@@ -169,9 +184,6 @@ namespace Org.BouncyCastle.Crypto.Generators
             prf.DoFinal(k, 0);
         }
 
-        public IMac GetMac()
-        {
-            return prf;
-        }
+        public IMac Mac => prf;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/crypto/generators/Kdf1BytesGenerator.cs b/crypto/src/crypto/generators/Kdf1BytesGenerator.cs
index 0ddf6c166..a43fa06d1 100644
--- a/crypto/src/crypto/generators/Kdf1BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Kdf1BytesGenerator.cs
@@ -1,16 +1,11 @@
-using System;
-
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Parameters;
-
 namespace Org.BouncyCastle.Crypto.Generators
 {
 	/**
-	 * KFD2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033
+	 * KFD1 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033
 	 * <br/>
 	 * This implementation is based on IEEE P1363/ISO 18033.
 	 */
-	public class Kdf1BytesGenerator
+	public sealed class Kdf1BytesGenerator
 		: BaseKdfBytesGenerator
 	{
 		/**
diff --git a/crypto/src/crypto/generators/Kdf2BytesGenerator.cs b/crypto/src/crypto/generators/Kdf2BytesGenerator.cs
index 8a6821980..2dc1d4301 100644
--- a/crypto/src/crypto/generators/Kdf2BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Kdf2BytesGenerator.cs
@@ -1,8 +1,3 @@
-using System;
-
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Parameters;
-
 namespace Org.BouncyCastle.Crypto.Generators
 {
 	/**
@@ -10,7 +5,7 @@ namespace Org.BouncyCastle.Crypto.Generators
 	 * <br/>
 	 * This implementation is based on IEEE P1363/ISO 18033.
 	 */
-	public class Kdf2BytesGenerator
+	public sealed class Kdf2BytesGenerator
 		: BaseKdfBytesGenerator
 	{
 		/**
diff --git a/crypto/src/crypto/generators/Mgf1BytesGenerator.cs b/crypto/src/crypto/generators/Mgf1BytesGenerator.cs
index 23a3aca25..7b4bb3c0b 100644
--- a/crypto/src/crypto/generators/Mgf1BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Mgf1BytesGenerator.cs
@@ -1,117 +1,112 @@
 using System;
-//using Org.BouncyCastle.Math;
-//using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Crypto;
+
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Generators
 {
-    /**
-    * Generator for MGF1 as defined in Pkcs 1v2
-    */
-    public class Mgf1BytesGenerator : IDerivationFunction
+    /// <summary>Generator for MGF1 as defined in Pkcs 1v2</summary>
+    public sealed class Mgf1BytesGenerator
+        : IDerivationFunction
     {
-        private IDigest digest;
-        private byte[]  seed;
-        private int     hLen;
-
-        /**
-        * @param digest the digest to be used as the source of Generated bytes
-        */
-        public Mgf1BytesGenerator(
-            IDigest  digest)
+        private readonly IDigest m_digest;
+        private readonly int m_hLen;
+
+        private byte[] m_buffer;
+
+        /// <param name="digest">the digest to be used as the source of generated bytes</param>
+        public Mgf1BytesGenerator(IDigest digest)
         {
-            this.digest = digest;
-            this.hLen = digest.GetDigestSize();
+            m_digest = digest;
+            m_hLen = digest.GetDigestSize();
         }
 
-        public void Init(
-            IDerivationParameters    parameters)
+        public void Init(IDerivationParameters parameters)
         {
-            if (!(typeof(MgfParameters).IsInstanceOfType(parameters)))
-            {
+            if (!(parameters is MgfParameters mgfParameters))
                 throw new ArgumentException("MGF parameters required for MGF1Generator");
-            }
-
-            MgfParameters   p = (MgfParameters)parameters;
 
-            seed = p.GetSeed();
+            m_buffer = new byte[mgfParameters.SeedLength + 4 + m_hLen];
+            mgfParameters.GetSeed(m_buffer, 0);
         }
 
-        /**
-        * return the underlying digest.
-        */
-        public IDigest Digest
+        /// <summary>the underlying digest.</summary>
+        public IDigest Digest => m_digest;
+
+        /// <summary>Fill <c>len</c> bytes of the output buffer with bytes generated from the derivation function.
+        /// </summary>
+        public int GenerateBytes(byte[] output, int outOff, int length)
         {
-            get
+            Check.OutputLength(output, outOff, length, "output buffer too small");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return GenerateBytes(output.AsSpan(outOff, length));
+#else
+            int hashPos = m_buffer.Length - m_hLen;
+            int counterPos = hashPos - 4;
+            uint counter = 0;
+
+            m_digest.Reset();
+
+            int end = outOff + length;
+            int limit = end - m_hLen;
+
+            while (outOff <= limit)
             {
-                return digest;
+                Pack.UInt32_To_BE(counter++, m_buffer, counterPos);
+
+                m_digest.BlockUpdate(m_buffer, 0, hashPos);
+                m_digest.DoFinal(output, outOff);
+
+                outOff += m_hLen;
             }
-        }
 
-        /**
-        * int to octet string.
-        */
-        private void ItoOSP(
-            int     i,
-            byte[]  sp)
-        {
-            sp[0] = (byte)((uint) i >> 24);
-            sp[1] = (byte)((uint) i >> 16);
-            sp[2] = (byte)((uint) i >> 8);
-            sp[3] = (byte)((uint) i >> 0);
+            if (outOff < end)
+            {
+                Pack.UInt32_To_BE(counter, m_buffer, counterPos);
+
+                m_digest.BlockUpdate(m_buffer, 0, hashPos);
+                m_digest.DoFinal(m_buffer, hashPos);
+
+                Array.Copy(m_buffer, hashPos, output, outOff, end - outOff);
+            }
+
+            return length;
+#endif
         }
 
-        /**
-        * fill len bytes of the output buffer with bytes Generated from
-        * the derivation function.
-        *
-        * @throws DataLengthException if the out buffer is too small.
-        */
-        public int GenerateBytes(
-            byte[]  output,
-            int     outOff,
-            int     length)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int GenerateBytes(Span<byte> output)
         {
-			if ((output.Length - length) < outOff)
-			{
-				throw new DataLengthException("output buffer too small");
-			}
+            int hashPos = m_buffer.Length - m_hLen;
+            int counterPos = hashPos - 4;
+            uint counter = 0;
 
-			byte[]  hashBuf = new byte[hLen];
-            byte[]  C = new byte[4];
-            int     counter = 0;
+            m_digest.Reset();
 
-            digest.Reset();
+            int pos = 0, length = output.Length, limit = length - m_hLen;
 
-			if (length > hLen)
-			{
-				do
-				{
-					ItoOSP(counter, C);
+            while (pos <= limit)
+            {
+                Pack.UInt32_To_BE(counter++, m_buffer.AsSpan(counterPos));
 
-					digest.BlockUpdate(seed, 0, seed.Length);
-					digest.BlockUpdate(C, 0, C.Length);
-					digest.DoFinal(hashBuf, 0);
+                m_digest.BlockUpdate(m_buffer.AsSpan(0, hashPos));
+                m_digest.DoFinal(output[pos..]);
 
-					Array.Copy(hashBuf, 0, output, outOff + counter * hLen, hLen);
-				}
-				while (++counter < (length / hLen));
-			}
+                pos += m_hLen;
+            }
 
-            if ((counter * hLen) < length)
+            if (pos < length)
             {
-                ItoOSP(counter, C);
-
-                digest.BlockUpdate(seed, 0, seed.Length);
-                digest.BlockUpdate(C, 0, C.Length);
-                digest.DoFinal(hashBuf, 0);
+                Pack.UInt32_To_BE(counter, m_buffer.AsSpan(counterPos));
 
-                Array.Copy(hashBuf, 0, output, outOff + counter * hLen, length - (counter * hLen));
+                m_digest.BlockUpdate(m_buffer.AsSpan(0, hashPos));
+                m_digest.DoFinal(m_buffer.AsSpan(hashPos));
+                m_buffer.AsSpan(hashPos, length - pos).CopyTo(output[pos..]);
             }
 
             return length;
         }
+#endif
     }
-
 }
diff --git a/crypto/src/crypto/generators/Poly1305KeyGenerator.cs b/crypto/src/crypto/generators/Poly1305KeyGenerator.cs
index cdb24bfa0..d7827fea9 100644
--- a/crypto/src/crypto/generators/Poly1305KeyGenerator.cs
+++ b/crypto/src/crypto/generators/Poly1305KeyGenerator.cs
@@ -31,7 +31,7 @@ namespace Org.BouncyCastle.Crypto.Generators
 		/// <remarks>
 		/// Poly1305 keys are always 256 bits, so the key length in the provided parameters is ignored.
 		/// </remarks>
-		protected override void engineInit(KeyGenerationParameters param)
+		protected override void EngineInit(KeyGenerationParameters param)
 		{
 			// Poly1305 keys are always 256 bits
 			this.random = param.Random;
@@ -43,9 +43,9 @@ namespace Org.BouncyCastle.Crypto.Generators
 		/// <code>k[0] ... k[15], r[0] ... r[15]</code> with the required bits in <code>r</code> cleared
 		/// as per <see cref="Clamp(byte[])"/>.
 		/// </summary>
-		protected override byte[] engineGenerateKey()
+		protected override byte[] EngineGenerateKey()
 		{
-			byte[] key = base.engineGenerateKey();
+			byte[] key = base.EngineGenerateKey();
 			Clamp(key);
 			return key;
 		}
diff --git a/crypto/src/crypto/generators/RSABlindingFactorGenerator.cs b/crypto/src/crypto/generators/RSABlindingFactorGenerator.cs
index e2f63face..a9eeb46df 100644
--- a/crypto/src/crypto/generators/RSABlindingFactorGenerator.cs
+++ b/crypto/src/crypto/generators/RSABlindingFactorGenerator.cs
@@ -21,20 +21,17 @@ namespace Org.BouncyCastle.Crypto.Generators
 		*
 		* @param param the necessary RSA key parameters.
 		*/
-		public void Init(
-			ICipherParameters param)
+		public void Init(ICipherParameters param)
 		{
-			if (param is ParametersWithRandom)
+			if (param is ParametersWithRandom rParam)
 			{
-				ParametersWithRandom rParam = (ParametersWithRandom)param;
-
 				key = (RsaKeyParameters)rParam.Parameters;
 				random = rParam.Random;
 			}
 			else
 			{
 				key = (RsaKeyParameters)param;
-				random = new SecureRandom();
+				random = CryptoServicesRegistrar.GetSecureRandom();
 			}
 
 			if (key.IsPrivate)
diff --git a/crypto/src/crypto/generators/SCrypt.cs b/crypto/src/crypto/generators/SCrypt.cs
index 0c51d89bc..fef842be2 100644
--- a/crypto/src/crypto/generators/SCrypt.cs
+++ b/crypto/src/crypto/generators/SCrypt.cs
@@ -5,6 +5,7 @@ using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math.Raw;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Generators
@@ -101,7 +102,76 @@ namespace Org.BouncyCastle.Crypto.Generators
 			return key.GetKey();
 		}
 
-		private static void SMix(uint[] B, int BOff, int N, int d, int r)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void SMix(uint[] 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;
+
+			uint[] blockY = new uint[BCount];
+
+            uint[][] VV = new uint[chunkCount][];
+
+			try
+			{
+                var X = B.AsSpan(BOff, BCount);
+
+                for (int c = 0; c < chunkCount; ++c)
+                {
+                    uint[] V = new uint[blocksPerChunk * BCount];
+                    VV[c] = V;
+
+                    Nat.Copy(BCount, X, V);
+                    int off = 0;
+                    for (int i = 1; i < blocksPerChunk; ++i)
+                    {
+                        BlockMix(V.AsSpan(off, BCount), V.AsSpan(off + BCount));
+                        off += BCount;
+                    }
+                    BlockMix(V.AsSpan()[^BCount..], X);
+                }
+
+                uint mask = (uint)N - 1;
+                for (int i = 0; i < N; ++i)
+                {
+                    int j = (int)(X[BCount - 16] & mask);
+                    uint[] V = VV[j >> chunkPow];
+                    int VOff = (j & chunkMask) * BCount;
+                    Nat.Xor(BCount, V.AsSpan(VOff), X, blockY);
+                    BlockMix(blockY, X);
+                }
+            }
+            finally
+			{
+				ClearAll(VV);
+                Clear(blockY);
+			}
+		}
+
+        private static void BlockMix(Span<uint> B, Span<uint> Y)
+		{
+            int BCount = B.Length;
+            int half = BCount >> 1;
+            var y1 = B[^16..];
+
+            for (int pos = 0; pos < BCount; pos += 32)
+            {
+                var b0 = B[pos..];
+                var y0 = Y[(pos >> 1)..];
+                Nat512.Xor(y1, b0, y0);
+                Salsa20Engine.SalsaCore(8, y0, y0);
+
+                var b1 = b0[16..];
+                    y1 = y0[half..];
+                Nat512.Xor(y0, b1, y1);
+                Salsa20Engine.SalsaCore(8, y1, y1);
+            }
+        }
+#else
+        private static void SMix(uint[] B, int BOff, int N, int d, int r)
 		{
             int powN = Integers.NumberOfTrailingZeros(N);
             int blocksPerChunk = N >> d;
@@ -110,7 +180,6 @@ namespace Org.BouncyCastle.Crypto.Generators
 			int BCount = r * 32;
 
 			uint[] blockX1 = new uint[16];
-			uint[] blockX2 = new uint[16];
 			uint[] blockY = new uint[BCount];
 
 			uint[] X = new uint[BCount];
@@ -130,10 +199,10 @@ namespace Org.BouncyCastle.Crypto.Generators
                     {
                         Array.Copy(X, 0, V, off, BCount);
                         off += BCount;
-                        BlockMix(X, blockX1, blockX2, blockY, r);
+                        BlockMix(X, blockX1, blockY, r);
                         Array.Copy(blockY, 0, V, off, BCount);
                         off += BCount;
-                        BlockMix(blockY, blockX1, blockX2, X, r);
+                        BlockMix(blockY, blockX1, X, r);
                     }
                 }
 
@@ -143,9 +212,9 @@ namespace Org.BouncyCastle.Crypto.Generators
                     int j = (int)(X[BCount - 16] & mask);
                     uint[] V = VV[j >> chunkPow];
                     int VOff = (j & chunkMask) * BCount;
-                    Array.Copy(V, VOff, blockY, 0, BCount);
-                    Xor(blockY, X, 0, blockY);
-                    BlockMix(blockY, blockX1, blockX2, X, r);
+                    Nat.Xor(BCount, V, VOff, X, 0, blockY, 0);
+
+                    BlockMix(blockY, blockX1, X, r);
                 }
 
 				Array.Copy(X, 0, B, BOff, BCount);
@@ -153,37 +222,30 @@ namespace Org.BouncyCastle.Crypto.Generators
 			finally
 			{
 				ClearAll(VV);
-				ClearAll(X, blockX1, blockX2, blockY);
+				ClearAll(X, blockX1, blockY);
 			}
 		}
 
-		private static void BlockMix(uint[] B, uint[] X1, uint[] X2, uint[] Y, int r)
+        private static void BlockMix(uint[] B, uint[] X1, uint[] Y, int r)
 		{
-			Array.Copy(B, B.Length - 16, X1, 0, 16);
+            Array.Copy(B, B.Length - 16, X1, 0, 16);
 
-			int BOff = 0, YOff = 0, halfLen = B.Length >> 1;
+            int BOff = 0, YOff = 0, halfLen = B.Length >> 1;
 
-			for (int i = 2 * r; i > 0; --i)
-			{
-				Xor(X1, B, BOff, X2);
-
-				Salsa20Engine.SalsaCore(8, X2, X1);
-				Array.Copy(X1, 0, Y, YOff, 16);
+            for (int i = 2 * r; i > 0; --i)
+            {
+                Nat512.XorTo(B, BOff, X1, 0);
 
-				YOff = halfLen + BOff - YOff;
-				BOff += 16;
-			}
-		}
+            	Salsa20Engine.SalsaCore(8, X1, X1);
+            	Array.Copy(X1, 0, Y, YOff, 16);
 
-		private static void Xor(uint[] a, uint[] b, int bOff, uint[] output)
-		{
-			for (int i = output.Length - 1; i >= 0; --i)
-			{
-				output[i] = a[i] ^ b[bOff + i];
-			}
-		}
+            	YOff = halfLen + BOff - YOff;
+            	BOff += 16;
+            }
+        }
+#endif
 
-		private static void Clear(Array array)
+        private static void Clear(Array array)
 		{
 			if (array != null)
 			{
diff --git a/crypto/src/crypto/io/CipherStream.cs b/crypto/src/crypto/io/CipherStream.cs
index 06a3d392c..6802e81d7 100644
--- a/crypto/src/crypto/io/CipherStream.cs
+++ b/crypto/src/crypto/io/CipherStream.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETCOREAPP1_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers;
+#endif
 using System.Diagnostics;
 using System.IO;
 
@@ -7,44 +10,39 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class CipherStream
+    public sealed class CipherStream
         : Stream
     {
-        internal Stream stream;
-        internal IBufferedCipher inCipher, outCipher;
-        private byte[] mInBuf;
-        private int mInPos;
-        private bool inStreamEnded;
+        private readonly Stream m_stream;
+        private readonly IBufferedCipher m_readCipher, m_writeCipher;
+
+        private byte[] m_readBuf;
+        private int m_readBufPos;
+        private bool m_readEnded;
 
         public CipherStream(Stream stream, IBufferedCipher readCipher, IBufferedCipher writeCipher)
         {
-            this.stream = stream;
+            m_stream = stream;
 
             if (readCipher != null)
             {
-                this.inCipher = readCipher;
-                mInBuf = null;
+                m_readCipher = readCipher;
+                m_readBuf = null;
             }
 
             if (writeCipher != null)
             {
-                this.outCipher = writeCipher;
+                m_writeCipher = writeCipher;
             }
         }
 
-        public IBufferedCipher ReadCipher
-        {
-            get { return inCipher; }
-        }
+        public IBufferedCipher ReadCipher => m_readCipher;
 
-        public IBufferedCipher WriteCipher
-        {
-            get { return outCipher; }
-        }
+        public IBufferedCipher WriteCipher => m_writeCipher;
 
         public override bool CanRead
         {
-            get { return stream.CanRead; }
+            get { return m_stream.CanRead; }
         }
 
         public sealed override bool CanSeek
@@ -54,42 +52,26 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override bool CanWrite
         {
-            get { return stream.CanWrite; }
+            get { return m_stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (m_readCipher == null)
             {
-			    if (outCipher != null)
-			    {
-				    byte[] data = outCipher.DoFinal();
-				    stream.Write(data, 0, data.Length);
-				    stream.Flush();
-			    }
-                Platform.Dispose(stream);
+                m_stream.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            if (outCipher != null)
+            else
             {
-                byte[] data = outCipher.DoFinal();
-                stream.Write(data, 0, data.Length);
-                stream.Flush();
+                base.CopyTo(destination, bufferSize);
             }
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
         public override void Flush()
         {
-            // Note: outCipher.DoFinal is only called during Close()
-            stream.Flush();
+            m_stream.Flush();
         }
 
         public sealed override long Length
@@ -105,41 +87,70 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override int Read(byte[] buffer, int offset, int count)
         {
-            if (inCipher == null)
-                return stream.Read(buffer, offset, count);
+            if (m_readCipher == null)
+                return m_stream.Read(buffer, offset, count);
 
             Streams.ValidateBufferArguments(buffer, offset, count);
 
             int num = 0;
             while (num < count)
             {
-                if (mInBuf == null || mInPos >= mInBuf.Length)
+                if (m_readBuf == null || m_readBufPos >= m_readBuf.Length)
+                {
+                    if (!FillInBuf())
+                        break;
+                }
+
+                int numToCopy = System.Math.Min(count - num, m_readBuf.Length - m_readBufPos);
+                Array.Copy(m_readBuf, m_readBufPos, buffer, offset + num, numToCopy);
+                m_readBufPos += numToCopy;
+                num += numToCopy;
+            }
+
+            return num;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            if (buffer.IsEmpty)
+                return 0;
+
+            if (m_readCipher == null)
+                return m_stream.Read(buffer);
+
+            int num = 0;
+            while (num < buffer.Length)
+            {
+                if (m_readBuf == null || m_readBufPos >= m_readBuf.Length)
                 {
                     if (!FillInBuf())
                         break;
                 }
 
-                int numToCopy = System.Math.Min(count - num, mInBuf.Length - mInPos);
-                Array.Copy(mInBuf, mInPos, buffer, offset + num, numToCopy);
-                mInPos += numToCopy;
+                int numToCopy = System.Math.Min(buffer.Length - num, m_readBuf.Length - m_readBufPos);
+                m_readBuf.AsSpan(m_readBufPos, numToCopy).CopyTo(buffer[num..]);
+
+                m_readBufPos += numToCopy;
                 num += numToCopy;
             }
 
             return num;
         }
+#endif
 
         public override int ReadByte()
         {
-            if (inCipher == null)
-                return stream.ReadByte();
+            if (m_readCipher == null)
+                return m_stream.ReadByte();
 
-            if (mInBuf == null || mInPos >= mInBuf.Length)
+            if (m_readBuf == null || m_readBufPos >= m_readBuf.Length)
             {
                 if (!FillInBuf())
                     return -1;
             }
 
-            return mInBuf[mInPos++];
+            return m_readBuf[m_readBufPos++];
         }
 
         public sealed override long Seek(long offset, SeekOrigin origin)
@@ -154,9 +165,9 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override void Write(byte[] buffer, int offset, int count)
         {
-            if (outCipher == null)
+            if (m_writeCipher == null)
             {
-                stream.Write(buffer, offset, count);
+                m_stream.Write(buffer, offset, count);
                 return;
             }
 
@@ -164,69 +175,144 @@ namespace Org.BouncyCastle.Crypto.IO
 
             if (count > 0)
             {
-                byte[] data = outCipher.ProcessBytes(buffer, offset, count);
-                if (data != null)
+#if NETCOREAPP1_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                int outputSize = m_writeCipher.GetUpdateOutputSize(count);
+                byte[] output = outputSize > 0 ? ArrayPool<byte>.Shared.Rent(outputSize) : null;
+                try
+                {
+                    int length = m_writeCipher.ProcessBytes(buffer, offset, count, output, 0);
+                    if (length > 0)
+                    {
+                        m_stream.Write(output, 0, length);
+                    }
+                }
+                finally
+                {
+                    if (output != null)
+                    {
+                        ArrayPool<byte>.Shared.Return(output);
+                    }
+                }
+#else
+                byte[] output = m_writeCipher.ProcessBytes(buffer, offset, count);
+                if (output != null)
+                {
+                    m_stream.Write(output, 0, output.Length);
+                }
+#endif
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (buffer.IsEmpty)
+                return;
+
+            if (m_writeCipher == null)
+            {
+                m_stream.Write(buffer);
+                return;
+            }
+
+            int outputSize = m_writeCipher.GetUpdateOutputSize(buffer.Length);
+            byte[] output = outputSize > 0 ? ArrayPool<byte>.Shared.Rent(outputSize) : null;
+            try
+            {
+                int length = m_writeCipher.ProcessBytes(buffer, Spans.FromNullable(output));
+                if (length > 0)
                 {
-                    stream.Write(data, 0, data.Length);
+                    m_stream.Write(output[..length]);
+                }
+            }
+            finally
+            {
+                if (output != null)
+                {
+                    ArrayPool<byte>.Shared.Return(output);
                 }
             }
         }
+#endif
 
         public override void WriteByte(byte value)
         {
-            if (outCipher == null)
+            if (m_writeCipher == null)
             {
-                stream.WriteByte(value);
+                m_stream.WriteByte(value);
                 return;
             }
 
-            byte[] data = outCipher.ProcessByte(value);
+            byte[] data = m_writeCipher.ProcessByte(value);
             if (data != null)
             {
-                stream.Write(data, 0, data.Length);
+                m_stream.Write(data, 0, data.Length);
             }
         }
 
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+			    if (m_writeCipher != null)
+			    {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    int outputSize = m_writeCipher.GetOutputSize(0);
+                    Span<byte> output = outputSize <= 256
+                        ? stackalloc byte[outputSize]
+                        : new byte[outputSize];
+                    int len = m_writeCipher.DoFinal(output);
+                    m_stream.Write(output[..len]);
+#else
+                    byte[] data = m_writeCipher.DoFinal();
+                    m_stream.Write(data, 0, data.Length);
+#endif
+			    }
+                Platform.Dispose(m_stream);
+            }
+            base.Dispose(disposing);
+        }
+
         private bool FillInBuf()
         {
-            if (inStreamEnded)
+            if (m_readEnded)
                 return false;
 
-            mInPos = 0;
+            m_readBufPos = 0;
 
             do
             {
-                mInBuf = ReadAndProcessBlock();
+                m_readBuf = ReadAndProcessBlock();
             }
-            while (!inStreamEnded && mInBuf == null);
+            while (!m_readEnded && m_readBuf == null);
 
-            return mInBuf != null;
+            return m_readBuf != null;
         }
 
         private byte[] ReadAndProcessBlock()
         {
-            int blockSize = inCipher.GetBlockSize();
-            int readSize = (blockSize == 0) ? 256 : blockSize;
+            int blockSize = m_readCipher.GetBlockSize();
+            int readSize = blockSize == 0 ? 256 : blockSize;
 
             byte[] block = new byte[readSize];
             int numRead = 0;
             do
             {
-                int count = stream.Read(block, numRead, block.Length - numRead);
+                int count = m_stream.Read(block, numRead, block.Length - numRead);
                 if (count < 1)
                 {
-                    inStreamEnded = true;
+                    m_readEnded = true;
                     break;
                 }
                 numRead += count;
             }
             while (numRead < block.Length);
 
-            Debug.Assert(inStreamEnded || numRead == block.Length);
+            Debug.Assert(m_readEnded || numRead == block.Length);
 
-            byte[] bytes = inStreamEnded
-                ? inCipher.DoFinal(block, 0, numRead)
-                : inCipher.ProcessBytes(block);
+            byte[] bytes = m_readEnded
+                ? m_readCipher.DoFinal(block, 0, numRead)
+                : m_readCipher.ProcessBytes(block);
 
             if (bytes != null && bytes.Length == 0)
             {
diff --git a/crypto/src/crypto/io/DigestSink.cs b/crypto/src/crypto/io/DigestSink.cs
index c2a168bfe..283bda28b 100644
--- a/crypto/src/crypto/io/DigestSink.cs
+++ b/crypto/src/crypto/io/DigestSink.cs
@@ -4,20 +4,17 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class DigestSink
+    public sealed class DigestSink
         : BaseOutputStream
     {
-        private readonly IDigest mDigest;
+        private readonly IDigest m_digest;
 
         public DigestSink(IDigest digest)
         {
-            this.mDigest = digest;
+            m_digest = digest;
         }
 
-        public virtual IDigest Digest
-        {
-            get { return mDigest; }
-        }
+        public IDigest Digest => m_digest;
 
         public override void Write(byte[] buffer, int offset, int count)
         {
@@ -25,13 +22,23 @@ namespace Org.BouncyCastle.Crypto.IO
 
             if (count > 0)
             {
-                mDigest.BlockUpdate(buffer, offset, count);
+                m_digest.BlockUpdate(buffer, offset, count);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (!buffer.IsEmpty)
+            {
+                m_digest.BlockUpdate(buffer);
             }
         }
+#endif
 
         public override void WriteByte(byte value)
         {
-            mDigest.Update(value);
+            m_digest.Update(value);
         }
     }
 }
diff --git a/crypto/src/crypto/io/DigestStream.cs b/crypto/src/crypto/io/DigestStream.cs
index 387f42766..ae7fc94de 100644
--- a/crypto/src/crypto/io/DigestStream.cs
+++ b/crypto/src/crypto/io/DigestStream.cs
@@ -5,33 +5,27 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class DigestStream
+    public sealed class DigestStream
         : Stream
     {
-        protected readonly Stream stream;
-        protected readonly IDigest inDigest;
-        protected readonly IDigest outDigest;
+        private readonly Stream m_stream;
+        private readonly IDigest m_readDigest;
+        private readonly IDigest m_writeDigest;
 
         public DigestStream(Stream stream, IDigest readDigest, IDigest writeDigest)
         {
-            this.stream = stream;
-            this.inDigest = readDigest;
-            this.outDigest = writeDigest;
+            m_stream = stream;
+            m_readDigest = readDigest;
+            m_writeDigest = writeDigest;
         }
 
-        public virtual IDigest ReadDigest()
-        {
-            return inDigest;
-        }
+        public IDigest ReadDigest => m_readDigest;
 
-        public virtual IDigest WriteDigest()
-        {
-            return outDigest;
-        }
+        public IDigest WriteDigest => m_writeDigest;
 
         public override bool CanRead
         {
-            get { return stream.CanRead; }
+            get { return m_stream.CanRead; }
         }
 
         public sealed override bool CanSeek
@@ -41,29 +35,26 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override bool CanWrite
         {
-            get { return stream.CanWrite; }
+            get { return m_stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (m_readDigest == null)
             {
-                Platform.Dispose(stream);
+                m_stream.CopyTo(destination, bufferSize);
+            }
+            else
+            {
+                base.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
         public override void Flush()
         {
-            stream.Flush();
+            m_stream.Flush();
         }
 
         public sealed override long Length
@@ -79,23 +70,37 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override int Read(byte[] buffer, int offset, int count)
         {
-            int n = stream.Read(buffer, offset, count);
+            int n = m_stream.Read(buffer, offset, count);
+
+            if (m_readDigest != null && n > 0)
+            {
+                m_readDigest.BlockUpdate(buffer, offset, n);
+            }
+
+            return n;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int n = m_stream.Read(buffer);
 
-            if (inDigest != null && n > 0)
+            if (m_readDigest != null && n > 0)
             {
-                inDigest.BlockUpdate(buffer, offset, n);
+                m_readDigest.BlockUpdate(buffer[..n]);
             }
 
             return n;
         }
+#endif
 
         public override int ReadByte()
         {
-            int b = stream.ReadByte();
+            int b = m_stream.ReadByte();
 
-            if (inDigest != null && b >= 0)
+            if (m_readDigest != null && b >= 0)
             {
-                inDigest.Update((byte)b);
+                m_readDigest.Update((byte)b);
             }
 
             return b;
@@ -113,23 +118,43 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override void Write(byte[] buffer, int offset, int count)
         {
-            stream.Write(buffer, offset, count);
+            m_stream.Write(buffer, offset, count);
 
-            if (outDigest != null && count > 0)
+            if (m_writeDigest != null && count > 0)
             {
-                outDigest.BlockUpdate(buffer, offset, count);
+                m_writeDigest.BlockUpdate(buffer, offset, count);
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_stream.Write(buffer);
+
+            if (m_writeDigest != null && !buffer.IsEmpty)
+            {
+                m_writeDigest.BlockUpdate(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
-            stream.WriteByte(value);
+            m_stream.WriteByte(value);
 
-            if (outDigest != null)
+            if (m_writeDigest != null)
             {
-                outDigest.Update(value);
+                m_writeDigest.Update(value);
             }
         }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Platform.Dispose(m_stream);
+            }
+            base.Dispose(disposing);
+        }
     }
 }
-
diff --git a/crypto/src/crypto/io/MacSink.cs b/crypto/src/crypto/io/MacSink.cs
index aa72e9047..cc5d93b37 100644
--- a/crypto/src/crypto/io/MacSink.cs
+++ b/crypto/src/crypto/io/MacSink.cs
@@ -4,20 +4,17 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class MacSink
+    public sealed class MacSink
         : BaseOutputStream
     {
-        private readonly IMac mMac;
+        private readonly IMac m_mac;
 
         public MacSink(IMac mac)
         {
-            this.mMac = mac;
+            m_mac = mac;
         }
 
-        public virtual IMac Mac
-        {
-            get { return mMac; }
-        }
+        public IMac Mac => m_mac;
 
         public override void Write(byte[] buffer, int offset, int count)
         {
@@ -25,13 +22,23 @@ namespace Org.BouncyCastle.Crypto.IO
 
             if (count > 0)
             {
-                mMac.BlockUpdate(buffer, offset, count);
+                m_mac.BlockUpdate(buffer, offset, count);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (!buffer.IsEmpty)
+            {
+                m_mac.BlockUpdate(buffer);
             }
         }
+#endif
 
         public override void WriteByte(byte value)
         {
-            mMac.Update(value);
+            m_mac.Update(value);
         }
     }
 }
diff --git a/crypto/src/crypto/io/MacStream.cs b/crypto/src/crypto/io/MacStream.cs
index c56e00d2c..c97614289 100644
--- a/crypto/src/crypto/io/MacStream.cs
+++ b/crypto/src/crypto/io/MacStream.cs
@@ -5,33 +5,27 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class MacStream
+    public sealed class MacStream
         : Stream
     {
-        protected readonly Stream stream;
-        protected readonly IMac inMac;
-        protected readonly IMac outMac;
+        private readonly Stream m_stream;
+        private readonly IMac m_readMac;
+        private readonly IMac m_writeMac;
 
         public MacStream(Stream stream, IMac readMac, IMac writeMac)
         {
-            this.stream = stream;
-            this.inMac = readMac;
-            this.outMac = writeMac;
+            m_stream = stream;
+            m_readMac = readMac;
+            m_writeMac = writeMac;
         }
 
-        public virtual IMac ReadMac()
-        {
-            return inMac;
-        }
+        public IMac ReadMac => m_readMac;
 
-        public virtual IMac WriteMac()
-        {
-            return outMac;
-        }
+        public IMac WriteMac => m_writeMac;
 
         public override bool CanRead
         {
-            get { return stream.CanRead; }
+            get { return m_stream.CanRead; }
         }
 
         public sealed override bool CanSeek
@@ -41,29 +35,26 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override bool CanWrite
         {
-            get { return stream.CanWrite; }
+            get { return m_stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (m_readMac == null)
             {
-                Platform.Dispose(stream);
+                m_stream.CopyTo(destination, bufferSize);
+            }
+            else
+            {
+                base.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
         public override void Flush()
         {
-            stream.Flush();
+            m_stream.Flush();
         }
 
         public sealed override long Length
@@ -79,23 +70,37 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override int Read(byte[] buffer, int offset, int count)
         {
-            int n = stream.Read(buffer, offset, count);
+            int n = m_stream.Read(buffer, offset, count);
+
+            if (m_readMac != null && n > 0)
+            {
+                m_readMac.BlockUpdate(buffer, offset, n);
+            }
+
+            return n;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int n = m_stream.Read(buffer);
 
-            if (inMac != null && n > 0)
+            if (m_readMac != null && n > 0)
             {
-                inMac.BlockUpdate(buffer, offset, n);
+                m_readMac.BlockUpdate(buffer[..n]);
             }
 
             return n;
         }
+#endif
 
         public override int ReadByte()
         {
-            int b = stream.ReadByte();
+            int b = m_stream.ReadByte();
 
-            if (inMac != null && b >= 0)
+            if (m_readMac != null && b >= 0)
             {
-                inMac.Update((byte)b);
+                m_readMac.Update((byte)b);
             }
 
             return b;
@@ -113,23 +118,43 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override void Write(byte[] buffer, int offset, int count)
         {
-            stream.Write(buffer, offset, count);
+            m_stream.Write(buffer, offset, count);
 
-            if (outMac != null && count > 0)
+            if (m_writeMac != null && count > 0)
             {
-                outMac.BlockUpdate(buffer, offset, count);
+                m_writeMac.BlockUpdate(buffer, offset, count);
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_stream.Write(buffer);
+
+            if (m_writeMac != null && !buffer.IsEmpty)
+            {
+                m_writeMac.BlockUpdate(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
-            stream.WriteByte(value);
+            m_stream.WriteByte(value);
 
-            if (outMac != null)
+            if (m_writeMac != null)
             {
-                outMac.Update(value);
+                m_writeMac.Update(value);
             }
         }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Platform.Dispose(m_stream);
+            }
+            base.Dispose(disposing);
+        }
     }
 }
-
diff --git a/crypto/src/crypto/io/SignerSink.cs b/crypto/src/crypto/io/SignerSink.cs
index 3485d3cdc..aaae59966 100644
--- a/crypto/src/crypto/io/SignerSink.cs
+++ b/crypto/src/crypto/io/SignerSink.cs
@@ -4,20 +4,17 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class SignerSink
+    public sealed class SignerSink
 		: BaseOutputStream
 	{
-		private readonly ISigner mSigner;
+		private readonly ISigner m_signer;
 
         public SignerSink(ISigner signer)
 		{
-            this.mSigner = signer;
+            m_signer = signer;
 		}
 
-        public virtual ISigner Signer
-        {
-            get { return mSigner; }
-        }
+		public ISigner Signer => m_signer;
 
 		public override void Write(byte[] buffer, int offset, int count)
 		{
@@ -25,13 +22,23 @@ namespace Org.BouncyCastle.Crypto.IO
 
 			if (count > 0)
 			{
-				mSigner.BlockUpdate(buffer, offset, count);
+				m_signer.BlockUpdate(buffer, offset, count);
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override void Write(ReadOnlySpan<byte> buffer)
+		{
+			if (!buffer.IsEmpty)
+			{
+				m_signer.BlockUpdate(buffer);
+			}
+		}
+#endif
+
 		public override void WriteByte(byte value)
 		{
-			mSigner.Update(value);
+			m_signer.Update(value);
 		}
 	}
 }
diff --git a/crypto/src/crypto/io/SignerStream.cs b/crypto/src/crypto/io/SignerStream.cs
index d25d7e285..fecc0d309 100644
--- a/crypto/src/crypto/io/SignerStream.cs
+++ b/crypto/src/crypto/io/SignerStream.cs
@@ -5,12 +5,12 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class SignerStream
+    public sealed class SignerStream
         : Stream
     {
-        protected readonly Stream stream;
-        protected readonly ISigner inSigner;
-        protected readonly ISigner outSigner;
+        private readonly Stream stream;
+        private readonly ISigner inSigner;
+        private readonly ISigner outSigner;
 
         public SignerStream(Stream stream, ISigner readSigner, ISigner writeSigner)
         {
@@ -19,15 +19,9 @@ namespace Org.BouncyCastle.Crypto.IO
             this.outSigner = writeSigner;
         }
 
-        public virtual ISigner ReadSigner()
-        {
-            return inSigner;
-        }
+        public ISigner ReadSigner => inSigner;
 
-        public virtual ISigner WriteSigner()
-        {
-            return outSigner;
-        }
+        public ISigner WriteSigner => outSigner;
 
         public override bool CanRead
         {
@@ -44,20 +38,17 @@ namespace Org.BouncyCastle.Crypto.IO
             get { return stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (inSigner == null)
             {
-                Platform.Dispose(stream);
+                stream.CopyTo(destination, bufferSize);
+            }
+            else
+            {
+                base.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
@@ -89,6 +80,20 @@ namespace Org.BouncyCastle.Crypto.IO
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int n = stream.Read(buffer);
+
+            if (inSigner != null && n > 0)
+            {
+                inSigner.BlockUpdate(buffer[..n]);
+            }
+
+            return n;
+        }
+#endif
+
         public override int ReadByte()
         {
             int b = stream.ReadByte();
@@ -121,6 +126,18 @@ namespace Org.BouncyCastle.Crypto.IO
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            stream.Write(buffer);
+
+            if (outSigner != null && !buffer.IsEmpty)
+            {
+                outSigner.BlockUpdate(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             stream.WriteByte(value);
@@ -130,6 +147,14 @@ namespace Org.BouncyCastle.Crypto.IO
                 outSigner.Update(value);
             }
         }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Platform.Dispose(stream);
+            }
+            base.Dispose(disposing);
+        }
     }
 }
-
diff --git a/crypto/src/crypto/macs/CMac.cs b/crypto/src/crypto/macs/CMac.cs
index 682c12bac..dd4281f8f 100644
--- a/crypto/src/crypto/macs/CMac.cs
+++ b/crypto/src/crypto/macs/CMac.cs
@@ -37,7 +37,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         private byte[] buf;
         private int bufOff;
-        private IBlockCipher cipher;
+        private IBlockCipherMode m_cipherMode;
 
         private int macSize;
 
@@ -88,7 +88,7 @@ namespace Org.BouncyCastle.Crypto.Macs
                     "Block size must be either 64 or 128 bits");
             }
 
-            this.cipher = new CbcBlockCipher(cipher);
+            m_cipherMode = new CbcBlockCipher(cipher);
             this.macSize = macSizeInBits / 8;
 
             mac = new byte[cipher.GetBlockSize()];
@@ -102,7 +102,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         public string AlgorithmName
         {
-            get { return cipher.AlgorithmName; }
+            get { return m_cipherMode.AlgorithmName; }
         }
 
         private static int ShiftLeft(byte[] block, byte[] output)
@@ -132,16 +132,15 @@ namespace Org.BouncyCastle.Crypto.Macs
             return ret;
         }
 
-        public void Init(
-            ICipherParameters parameters)
+        public void Init(ICipherParameters parameters)
         {
             if (parameters is KeyParameter)
             {
-                cipher.Init(true, parameters);
+                m_cipherMode.Init(true, parameters);
 
                 //initializes the L, Lu, Lu2 numbers
                 L = new byte[ZEROES.Length];
-                cipher.ProcessBlock(ZEROES, 0, L, 0);
+                m_cipherMode.ProcessBlock(ZEROES, 0, L, 0);
                 Lu = DoubleLu(L);
                 Lu2 = DoubleLu(Lu);
             }
@@ -159,34 +158,33 @@ namespace Org.BouncyCastle.Crypto.Macs
             return macSize;
         }
 
-        public void Update(
-            byte input)
+        public void Update(byte input)
         {
             if (bufOff == buf.Length)
             {
-                cipher.ProcessBlock(buf, 0, mac, 0);
+                m_cipherMode.ProcessBlock(buf, 0, mac, 0);
                 bufOff = 0;
             }
 
             buf[bufOff++] = input;
         }
 
-        public void BlockUpdate(
-            byte[]	inBytes,
-            int		inOff,
-            int		len)
+        public void BlockUpdate(byte[] inBytes, int inOff, int len)
         {
             if (len < 0)
                 throw new ArgumentException("Can't have a negative input length!");
 
-            int blockSize = cipher.GetBlockSize();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(inBytes.AsSpan(inOff, len));
+#else
+            int blockSize = m_cipherMode.GetBlockSize();
             int gapLen = blockSize - bufOff;
 
             if (len > gapLen)
             {
                 Array.Copy(inBytes, inOff, buf, bufOff, gapLen);
 
-                cipher.ProcessBlock(buf, 0, mac, 0);
+                m_cipherMode.ProcessBlock(buf, 0, mac, 0);
 
                 bufOff = 0;
                 len -= gapLen;
@@ -194,7 +192,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
                 while (len > blockSize)
                 {
-                    cipher.ProcessBlock(inBytes, inOff, mac, 0);
+                    m_cipherMode.ProcessBlock(inBytes, inOff, mac, 0);
 
                     len -= blockSize;
                     inOff += blockSize;
@@ -204,13 +202,43 @@ namespace Org.BouncyCastle.Crypto.Macs
             Array.Copy(inBytes, inOff, buf, bufOff, len);
 
             bufOff += len;
+#endif
         }
 
-        public int DoFinal(
-            byte[]	outBytes,
-            int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            int blockSize = cipher.GetBlockSize();
+            int blockSize = m_cipherMode.GetBlockSize();
+            int gapLen = blockSize - bufOff;
+
+            if (input.Length > gapLen)
+            {
+                input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+                m_cipherMode.ProcessBlock(buf, mac);
+
+                bufOff = 0;
+                input = input[gapLen..];
+
+                while (input.Length > blockSize)
+                {
+                    m_cipherMode.ProcessBlock(input, mac);
+                    input = input[blockSize..];
+                }
+            }
+
+            input.CopyTo(buf.AsSpan(bufOff));
+
+            bufOff += input.Length;
+        }
+#endif
+
+        public int DoFinal(byte[] outBytes, int outOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(outBytes.AsSpan(outOff));
+#else
+            int blockSize = m_cipherMode.GetBlockSize();
 
             byte[] lu;
             if (bufOff == blockSize)
@@ -228,14 +256,46 @@ namespace Org.BouncyCastle.Crypto.Macs
                 buf[i] ^= lu[i];
             }
 
-            cipher.ProcessBlock(buf, 0, mac, 0);
+            m_cipherMode.ProcessBlock(buf, 0, mac, 0);
 
             Array.Copy(mac, 0, outBytes, outOff, macSize);
 
             Reset();
 
             return macSize;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            int blockSize = m_cipherMode.GetBlockSize();
+
+            byte[] lu;
+            if (bufOff == blockSize)
+            {
+                lu = Lu;
+            }
+            else
+            {
+                new ISO7816d4Padding().AddPadding(buf, bufOff);
+                lu = Lu2;
+            }
+
+            for (int i = 0; i < mac.Length; i++)
+            {
+                buf[i] ^= lu[i];
+            }
+
+            m_cipherMode.ProcessBlock(buf, mac);
+
+            mac.AsSpan(0, macSize).CopyTo(output);
+
+            Reset();
+
+            return macSize;
         }
+#endif
 
         /**
         * Reset the mac generator.
@@ -251,7 +311,7 @@ namespace Org.BouncyCastle.Crypto.Macs
             /*
             * Reset the underlying cipher.
             */
-            cipher.Reset();
+            m_cipherMode.Reset();
         }
     }
 }
diff --git a/crypto/src/crypto/macs/CbcBlockCipherMac.cs b/crypto/src/crypto/macs/CbcBlockCipherMac.cs
index 146e16aa8..c660c0dc3 100644
--- a/crypto/src/crypto/macs/CbcBlockCipherMac.cs
+++ b/crypto/src/crypto/macs/CbcBlockCipherMac.cs
@@ -14,7 +14,7 @@ namespace Org.BouncyCastle.Crypto.Macs
     {
         private byte[] buf;
         private int bufOff;
-        private IBlockCipher cipher;
+        private IBlockCipherMode m_cipherMode;
         private IBlockCipherPadding padding;
 		private int macSize;
 
@@ -86,7 +86,7 @@ namespace Org.BouncyCastle.Crypto.Macs
             if ((macSizeInBits % 8) != 0)
                 throw new ArgumentException("MAC size must be multiple of 8");
 
-			this.cipher = new CbcBlockCipher(cipher);
+			this.m_cipherMode = new CbcBlockCipher(cipher);
             this.padding = padding;
             this.macSize = macSizeInBits / 8;
 
@@ -96,15 +96,14 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 		public string AlgorithmName
         {
-            get { return cipher.AlgorithmName; }
+            get { return m_cipherMode.AlgorithmName; }
         }
 
-		public void Init(
-            ICipherParameters parameters)
+		public void Init(ICipherParameters parameters)
         {
             Reset();
 
-			cipher.Init(true, parameters);
+            m_cipherMode.Init(true, parameters);
         }
 
 		public int GetMacSize()
@@ -112,34 +111,33 @@ namespace Org.BouncyCastle.Crypto.Macs
             return macSize;
         }
 
-		public void Update(
-            byte input)
+		public void Update(byte input)
         {
 			if (bufOff == buf.Length)
             {
-				cipher.ProcessBlock(buf, 0, buf, 0);
+                m_cipherMode.ProcessBlock(buf, 0, buf, 0);
                 bufOff = 0;
             }
 
 			buf[bufOff++] = input;
         }
 
-        public void BlockUpdate(
-            byte[]	input,
-            int		inOff,
-            int		len)
+        public void BlockUpdate(byte[] input, int inOff, int len)
         {
             if (len < 0)
                 throw new ArgumentException("Can't have a negative input length!");
 
-			int blockSize = cipher.GetBlockSize();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(inOff, len));
+#else
+            int blockSize = m_cipherMode.GetBlockSize();
             int gapLen = blockSize - bufOff;
 
             if (len > gapLen)
             {
                 Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-                cipher.ProcessBlock(buf, 0, buf, 0);
+                m_cipherMode.ProcessBlock(buf, 0, buf, 0);
 
                 bufOff = 0;
                 len -= gapLen;
@@ -147,7 +145,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
                 while (len > blockSize)
                 {
-                    cipher.ProcessBlock(input, inOff, buf, 0);
+                    m_cipherMode.ProcessBlock(input, inOff, buf, 0);
 
                     len -= blockSize;
                     inOff += blockSize;
@@ -157,13 +155,43 @@ namespace Org.BouncyCastle.Crypto.Macs
             Array.Copy(input, inOff, buf, bufOff, len);
 
             bufOff += len;
+#endif
         }
 
-        public int DoFinal(
-            byte[]	output,
-            int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            int blockSize = cipher.GetBlockSize();
+            int blockSize = m_cipherMode.GetBlockSize();
+            int gapLen = blockSize - bufOff;
+
+            if (input.Length > gapLen)
+            {
+                input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+                m_cipherMode.ProcessBlock(buf, buf);
+
+                bufOff = 0;
+                input = input[gapLen..];
+
+                while (input.Length > blockSize)
+                {
+                    m_cipherMode.ProcessBlock(input, buf);
+                    input = input[blockSize..];
+                }
+            }
+
+            input.CopyTo(buf.AsSpan(bufOff));
+
+            bufOff += input.Length;
+        }
+#endif
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
+            int blockSize = m_cipherMode.GetBlockSize();
 
             if (padding == null)
             {
@@ -177,23 +205,58 @@ namespace Org.BouncyCastle.Crypto.Macs
             {
                 if (bufOff == blockSize)
                 {
-                    cipher.ProcessBlock(buf, 0, buf, 0);
+                    m_cipherMode.ProcessBlock(buf, 0, buf, 0);
                     bufOff = 0;
                 }
 
 				padding.AddPadding(buf, bufOff);
             }
 
-			cipher.ProcessBlock(buf, 0, buf, 0);
+			m_cipherMode.ProcessBlock(buf, 0, buf, 0);
 
 			Array.Copy(buf, 0, output, outOff, macSize);
 
 			Reset();
 
 			return macSize;
+#endif
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            int blockSize = m_cipherMode.GetBlockSize();
+
+            if (padding == null)
+            {
+                // pad with zeroes
+                while (bufOff < blockSize)
+                {
+                    buf[bufOff++] = 0;
+                }
+            }
+            else
+            {
+                if (bufOff == blockSize)
+                {
+                    m_cipherMode.ProcessBlock(buf, buf);
+                    bufOff = 0;
+                }
+
+				padding.AddPadding(buf, bufOff);
+            }
+
+            m_cipherMode.ProcessBlock(buf, buf);
+
+            buf.AsSpan(0, macSize).CopyTo(output);
+
+			Reset();
+
+			return macSize;
+        }
+#endif
+
+        /**
         * Reset the mac generator.
         */
         public void Reset()
@@ -202,8 +265,8 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Array.Clear(buf, 0, buf.Length);
 			bufOff = 0;
 
-			// Reset the underlying cipher.
-            cipher.Reset();
+            // Reset the underlying cipher.
+            m_cipherMode.Reset();
         }
     }
 }
diff --git a/crypto/src/crypto/macs/CfbBlockCipherMac.cs b/crypto/src/crypto/macs/CfbBlockCipherMac.cs
index 364cf8499..8f3f89993 100644
--- a/crypto/src/crypto/macs/CfbBlockCipherMac.cs
+++ b/crypto/src/crypto/macs/CfbBlockCipherMac.cs
@@ -9,8 +9,8 @@ namespace Org.BouncyCastle.Crypto.Macs
     /**
     * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher.
     */
-    class MacCFBBlockCipher
-		: IBlockCipher
+    internal class MacCfbBlockCipher
+		: IBlockCipherMode
     {
         private byte[] IV;
         private byte[] cfbV;
@@ -26,7 +26,7 @@ namespace Org.BouncyCastle.Crypto.Macs
         * feedback mode.
         * @param blockSize the block size in bits (note: a multiple of 8)
         */
-        public MacCFBBlockCipher(
+        public MacCfbBlockCipher(
             IBlockCipher	cipher,
             int				bitBlockSize)
         {
@@ -47,13 +47,10 @@ namespace Org.BouncyCastle.Crypto.Macs
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-		public void Init(
-			bool				forEncryption,
-            ICipherParameters	parameters)
+		public void Init(bool forEncryption, ICipherParameters parameters)
         {
-			if (parameters is ParametersWithIV)
+			if (parameters is ParametersWithIV ivParam)
             {
-                ParametersWithIV ivParam = (ParametersWithIV)parameters;
                 byte[] iv = ivParam.GetIV();
 
                 if (iv.Length < IV.Length)
@@ -84,7 +81,9 @@ namespace Org.BouncyCastle.Crypto.Macs
 			get { return cipher.AlgorithmName + "/CFB" + (blockSize * 8); }
         }
 
-		public bool IsPartialBlockOkay
+        public IBlockCipher UnderlyingCipher => cipher;
+
+        public bool IsPartialBlockOkay
 		{
 			get { return true; }
 		}
@@ -99,30 +98,10 @@ namespace Org.BouncyCastle.Crypto.Macs
             return blockSize;
         }
 
-		/**
-        * Process one block of input from the array in and write it to
-        * the out array.
-        *
-        * @param in the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	outBytes,
-            int		outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            if ((inOff + blockSize) > input.Length)
-                throw new DataLengthException("input buffer too short");
-
-			if ((outOff + blockSize) > outBytes.Length)
-                throw new DataLengthException("output buffer too short");
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
 
 			cipher.ProcessBlock(cfbV, 0, cfbOutV, 0);
 
@@ -143,15 +122,39 @@ namespace Org.BouncyCastle.Crypto.Macs
 			return blockSize;
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            cipher.ProcessBlock(cfbV, cfbOutV);
+
+            //
+            // XOR the cfbV with the plaintext producing the cipher text
+            //
+            for (int i = 0; i < blockSize; i++)
+            {
+                output[i] = (byte)(cfbOutV[i] ^ input[i]);
+            }
+
+            //
+            // change over the input block.
+            //
+            Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize);
+            output[..blockSize].CopyTo(cfbV.AsSpan(cfbV.Length - blockSize));
+
+            return blockSize;
+        }
+#endif
+
+        /**
         * reset the chaining vector back to the IV and reset the underlying
         * cipher.
         */
         public void Reset()
         {
 			IV.CopyTo(cfbV, 0);
-
-			cipher.Reset();
         }
 
 		public void GetMacBlock(
@@ -167,7 +170,7 @@ namespace Org.BouncyCastle.Crypto.Macs
         private byte[] mac;
         private byte[] Buffer;
         private int bufOff;
-        private MacCFBBlockCipher cipher;
+        private MacCfbBlockCipher cipher;
         private IBlockCipherPadding padding;
         private int macSize;
 
@@ -247,7 +250,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 			mac = new byte[cipher.GetBlockSize()];
 
-			this.cipher = new MacCFBBlockCipher(cipher, cfbBitSize);
+			this.cipher = new MacCfbBlockCipher(cipher, cfbBitSize);
             this.padding = padding;
             this.macSize = macSizeInBits / 8;
 
@@ -260,8 +263,7 @@ namespace Org.BouncyCastle.Crypto.Macs
             get { return cipher.AlgorithmName; }
         }
 
-		public void Init(
-            ICipherParameters parameters)
+		public void Init(ICipherParameters parameters)
         {
             Reset();
 
@@ -273,8 +275,7 @@ namespace Org.BouncyCastle.Crypto.Macs
             return macSize;
         }
 
-		public void Update(
-            byte input)
+		public void Update(byte input)
         {
             if (bufOff == Buffer.Length)
             {
@@ -285,15 +286,15 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Buffer[bufOff++] = input;
         }
 
-		public void BlockUpdate(
-            byte[]	input,
-            int		inOff,
-            int		len)
+		public void BlockUpdate(byte[] input, int inOff, int len)
         {
             if (len < 0)
                 throw new ArgumentException("Can't have a negative input length!");
 
-			int blockSize = cipher.GetBlockSize();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(inOff, len));
+#else
+            int blockSize = cipher.GetBlockSize();
             int resultLen = 0;
             int gapLen = blockSize - bufOff;
 
@@ -319,13 +320,44 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Array.Copy(input, inOff, Buffer, bufOff, len);
 
 			bufOff += len;
+#endif
         }
 
-		public int DoFinal(
-            byte[]	output,
-            int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
         {
             int blockSize = cipher.GetBlockSize();
+            int resultLen = 0;
+            int gapLen = blockSize - bufOff;
+
+            if (input.Length > gapLen)
+            {
+                input[..gapLen].CopyTo(Buffer.AsSpan(bufOff));
+
+                resultLen += cipher.ProcessBlock(Buffer, mac);
+
+                bufOff = 0;
+                input = input[gapLen..];
+
+                while (input.Length > blockSize)
+                {
+                    resultLen += cipher.ProcessBlock(input, mac);
+                    input = input[blockSize..];
+                }
+            }
+
+            input.CopyTo(Buffer.AsSpan(bufOff));
+
+            bufOff += input.Length;
+        }
+#endif
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
+            int blockSize = cipher.GetBlockSize();
 
             // pad with zeroes
             if (this.padding == null)
@@ -349,7 +381,38 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Reset();
 
 			return macSize;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            int blockSize = cipher.GetBlockSize();
+
+            // pad with zeroes
+            if (this.padding == null)
+            {
+                while (bufOff < blockSize)
+                {
+                    Buffer[bufOff++] = 0;
+                }
+            }
+            else
+            {
+                padding.AddPadding(Buffer, bufOff);
+            }
+
+            cipher.ProcessBlock(Buffer, 0, mac, 0);
+
+            cipher.GetMacBlock(mac);
+
+            mac.AsSpan(0, macSize).CopyTo(output);
+
+            Reset();
+
+            return macSize;
         }
+#endif
 
         /**
         * Reset the mac generator.
@@ -364,5 +427,4 @@ namespace Org.BouncyCastle.Crypto.Macs
             cipher.Reset();
         }
     }
-
 }
diff --git a/crypto/src/crypto/macs/DSTU7564Mac.cs b/crypto/src/crypto/macs/DSTU7564Mac.cs
index 36e86418a..401d85a1e 100644
--- a/crypto/src/crypto/macs/DSTU7564Mac.cs
+++ b/crypto/src/crypto/macs/DSTU7564Mac.cs
@@ -61,7 +61,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         public void BlockUpdate(byte[] input, int inOff, int len)
         {
-            Check.DataLength(input, inOff, len, "Input buffer too short");
+            Check.DataLength(input, inOff, len, "input buffer too short");
 
             if (paddedKey == null)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -70,6 +70,17 @@ namespace Org.BouncyCastle.Crypto.Macs
             inputLength += (ulong)len;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (paddedKey == null)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            engine.BlockUpdate(input);
+            inputLength += (ulong)input.Length;
+        }
+#endif
+
         public void Update(byte input)
         {
             engine.Update(input);
@@ -78,11 +89,11 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         public int DoFinal(byte[] output, int outOff)
         {
-            Check.OutputLength(output, outOff, macSize, "Output buffer too short");
-
             if (paddedKey == null)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
 
+            Check.OutputLength(output, outOff, macSize, "output buffer too short");
+
             Pad();
 
             engine.BlockUpdate(invertedKey, 0, invertedKey.Length);
@@ -92,6 +103,24 @@ namespace Org.BouncyCastle.Crypto.Macs
             return engine.DoFinal(output, outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            if (paddedKey == null)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+
+            Check.OutputLength(output, macSize, "output buffer too short");
+
+            Pad();
+
+            engine.BlockUpdate(invertedKey);
+
+            inputLength = 0;
+
+            return engine.DoFinal(output);
+        }
+#endif
+
         public void Reset()
         {
             inputLength = 0;
diff --git a/crypto/src/crypto/macs/DSTU7624Mac.cs b/crypto/src/crypto/macs/DSTU7624Mac.cs
index 953d8164f..f96bdec98 100644
--- a/crypto/src/crypto/macs/DSTU7624Mac.cs
+++ b/crypto/src/crypto/macs/DSTU7624Mac.cs
@@ -1,87 +1,89 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Parameters;
-
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Macs
 {
-     /**
+    /**
      * implementation of DSTU 7624 MAC
      */
-     public class Dstu7624Mac : IMac
-     {
-          private int macSize;
-                    
-          private Dstu7624Engine engine;
-          private int blockSize;
+    public class Dstu7624Mac : IMac
+    {
+        private int macSize;
+
+        private Dstu7624Engine engine;
+        private int blockSize;
+
+        private byte[] c, cTemp, kDelta;
+        private byte[] buf;
+        private int bufOff;
 
-          private byte[] c, cTemp, kDelta;
-          private byte[] buf;
-          private int bufOff;
+        public Dstu7624Mac(int blockSizeBits, int q)
+        {
+            engine = new Dstu7624Engine(blockSizeBits);
+
+            blockSize = blockSizeBits / 8;
 
-          public Dstu7624Mac(int blockSizeBits, int q)
-          {
-               engine = new Dstu7624Engine(blockSizeBits);
+            macSize = q / 8;
 
-               blockSize = blockSizeBits / 8;
+            c = new byte[blockSize];
 
-               macSize = q / 8;
+            cTemp = new byte[blockSize];
 
-               c = new byte[blockSize];
-              
-               cTemp = new byte[blockSize];
+            kDelta = new byte[blockSize];
+            buf = new byte[blockSize];
+        }
+
+        public void Init(ICipherParameters parameters)
+        {
+            if (parameters is KeyParameter)
+            {
+                engine.Init(true, (KeyParameter)parameters);
+
+                engine.ProcessBlock(kDelta, 0, kDelta, 0);
+            }
+            else
+            {
+                throw new ArgumentException("invalid parameter passed to Dstu7624Mac init - "
+                + Platform.GetTypeName(parameters));
+            }
+        }
+
+        public string AlgorithmName
+        {
+            get { return "Dstu7624Mac"; }
+        }
 
-               kDelta = new byte[blockSize];
-               buf = new byte[blockSize];
+        public int GetMacSize()
+        {
+            return macSize;
         }
 
-          public void Init(ICipherParameters parameters)
-          {
-               if (parameters is KeyParameter)
-               {
-                    engine.Init(true, (KeyParameter)parameters);
-
-                    engine.ProcessBlock(kDelta, 0, kDelta, 0);
-               }
-               else
-               {
-                    throw new ArgumentException("invalid parameter passed to Dstu7624Mac init - "
-                    + Platform.GetTypeName(parameters));
-               }             
-          }
-
-          public string AlgorithmName
-          {
-               get { return "Dstu7624Mac"; }
-          }
-
-          public int GetMacSize()
-          {
-               return macSize;
-          }
-
-          public void Update(byte input)
-          {
+        public void Update(byte input)
+        {
             if (bufOff == buf.Length)
             {
-                processBlock(buf, 0);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                ProcessBlock(buf);
+#else
+                ProcessBlock(buf, 0);
+#endif
                 bufOff = 0;
             }
 
             buf[bufOff++] = input;
         }
 
-          public void BlockUpdate(byte[] input, int inOff, int len)
-          {
+        public void BlockUpdate(byte[] input, int inOff, int len)
+        {
             if (len < 0)
-            {
-                throw new ArgumentException(
-                    "Can't have a negative input length!");
-            }
+                throw new ArgumentException("Can't have a negative input length!");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(inOff, len));
+#else
             int blockSize = engine.GetBlockSize();
             int gapLen = blockSize - bufOff;
 
@@ -89,7 +91,7 @@ namespace Org.BouncyCastle.Crypto.Macs
             {
                 Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-                processBlock(buf, 0);
+                ProcessBlock(buf, 0);
 
                 bufOff = 0;
                 len -= gapLen;
@@ -97,7 +99,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
                 while (len > blockSize)
                 {
-                    processBlock(input, inOff);
+                    ProcessBlock(input, inOff);
 
                     len -= blockSize;
                     inOff += blockSize;
@@ -107,54 +109,113 @@ namespace Org.BouncyCastle.Crypto.Macs
             Array.Copy(input, inOff, buf, bufOff, len);
 
             bufOff += len;
+#endif
         }
 
-        private void processBlock(byte[] input, int inOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int blockSize = engine.GetBlockSize();
+            int gapLen = blockSize - bufOff;
+
+            if (input.Length > gapLen)
+            {
+                input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+                ProcessBlock(buf);
+
+                bufOff = 0;
+                input = input[gapLen..];
+
+                while (input.Length > blockSize)
+                {
+                    ProcessBlock(input);
+                    input = input[blockSize..];
+                }
+            }
+
+            input.CopyTo(buf.AsSpan(bufOff));
+
+            bufOff += input.Length;
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ProcessBlock(ReadOnlySpan<byte> input)
+        {
+            Xor(c, input, cTemp);
+
+            engine.ProcessBlock(cTemp, c);
+        }
+
+        private void Xor(ReadOnlySpan<byte> c, ReadOnlySpan<byte> input, Span<byte> xorResult)
+        {
+            for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
+            {
+                xorResult[byteIndex] = (byte)(c[byteIndex] ^ input[byteIndex]);
+            }
+        }
+#else
+        private void ProcessBlock(byte[] input, int inOff)
         {
             Xor(c, 0, input, inOff, cTemp);
 
             engine.ProcessBlock(cTemp, 0, c, 0);
         }
+#endif
 
         private void Xor(byte[] c, int cOff, byte[] input, int inOff, byte[] xorResult)
-          {
-               for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
-               {
-                    xorResult[byteIndex] = (byte)(c[byteIndex + cOff] ^ input[byteIndex + inOff]);
-               }
-          }
-
-          public int DoFinal(byte[] output, int outOff)
-          {
-            if (bufOff % buf.Length != 0)
+        {
+            for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
             {
-                throw new DataLengthException("Input must be a multiple of blocksize");
+                xorResult[byteIndex] = (byte)(c[byteIndex + cOff] ^ input[byteIndex + inOff]);
             }
+        }
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+            if (bufOff % buf.Length != 0)
+                throw new DataLengthException("Input must be a multiple of blocksize");
+
+            Check.OutputLength(output, outOff, macSize, "output buffer too short");
 
             //Last block
             Xor(c, 0, buf, 0, cTemp);
             Xor(cTemp, 0, kDelta, 0, c);
             engine.ProcessBlock(c, 0, c, 0);
 
-            if (macSize + outOff > output.Length)
-            {
-                throw new DataLengthException("Output buffer too short");
-            }
-
             Array.Copy(c, 0, output, outOff, macSize);
 
             return macSize;
         }
 
-          public void Reset()
-          {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            if (bufOff % buf.Length != 0)
+                throw new DataLengthException("Input must be a multiple of blocksize");
+
+            Check.OutputLength(output, macSize, "output buffer too short");
+
+            //Last block
+            Xor(c, 0, buf, 0, cTemp);
+            Xor(cTemp, 0, kDelta, 0, c);
+            engine.ProcessBlock(c, c);
+
+            c.AsSpan(0, macSize).CopyTo(output);
+
+            return macSize;
+        }
+#endif
+
+        public void Reset()
+        {
             Arrays.Fill(c, (byte)0x00);
             Arrays.Fill(cTemp, (byte)0x00);
             Arrays.Fill(kDelta, (byte)0x00);
             Arrays.Fill(buf, (byte)0x00);
-            engine.Reset();
             engine.ProcessBlock(kDelta, 0, kDelta, 0);
             bufOff = 0;
         }
-     }
+    }
 }
diff --git a/crypto/src/crypto/macs/GMac.cs b/crypto/src/crypto/macs/GMac.cs
index 0554c44f0..78c51b1ec 100644
--- a/crypto/src/crypto/macs/GMac.cs
+++ b/crypto/src/crypto/macs/GMac.cs
@@ -52,10 +52,8 @@ namespace Org.BouncyCastle.Crypto.Macs
         /// </summary>
         public void Init(ICipherParameters parameters)
         {
-            if (parameters is ParametersWithIV)
+            if (parameters is ParametersWithIV param)
             {
-                ParametersWithIV param = (ParametersWithIV)parameters;
-
                 byte[] iv = param.GetIV();
                 KeyParameter keyParam = (KeyParameter)param.Parameters;
 
@@ -70,7 +68,7 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         public string AlgorithmName
         {
-            get { return cipher.GetUnderlyingCipher().AlgorithmName + "-GMAC"; }
+            get { return cipher.UnderlyingCipher.AlgorithmName + "-GMAC"; }
         }
 
         public int GetMacSize()
@@ -88,6 +86,13 @@ namespace Org.BouncyCastle.Crypto.Macs
             cipher.ProcessAadBytes(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            cipher.ProcessAadBytes(input);
+        }
+#endif
+
         public int DoFinal(byte[] output, int outOff)
         {
             try
@@ -101,6 +106,21 @@ namespace Org.BouncyCastle.Crypto.Macs
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            try
+            {
+                return cipher.DoFinal(output);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                // Impossible in encrypt mode
+                throw new InvalidOperationException(e.ToString());
+            }
+        }
+#endif
+
         public void Reset()
         {
             cipher.Reset();
diff --git a/crypto/src/crypto/macs/GOST28147Mac.cs b/crypto/src/crypto/macs/GOST28147Mac.cs
index 33c2d67ee..8c39fc6b0 100644
--- a/crypto/src/crypto/macs/GOST28147Mac.cs
+++ b/crypto/src/crypto/macs/GOST28147Mac.cs
@@ -1,7 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Macs
@@ -9,10 +9,11 @@ namespace Org.BouncyCastle.Crypto.Macs
 	/**
 	* implementation of GOST 28147-89 MAC
 	*/
-	public class Gost28147Mac : IMac
+	public class Gost28147Mac
+		: IMac
 	{
-		private const int			blockSize = 8;
-		private const int			macSize = 4;
+		private const int			BlockSize = 8;
+		private const int			MacSize = 4;
 		private int					bufOff;
 		private byte[]				buf;
 		private byte[]				mac;
@@ -36,8 +37,8 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 		public Gost28147Mac()
 		{
-			mac = new byte[blockSize];
-			buf = new byte[blockSize];
+			mac = new byte[BlockSize];
+			buf = new byte[BlockSize];
 			bufOff = 0;
 		}
 
@@ -50,22 +51,19 @@ namespace Org.BouncyCastle.Crypto.Macs
 			int[] key = new int[8];
 			for(int i=0; i!=8; i++)
 			{
-				key[i] = bytesToint(userKey,i*4);
+				key[i] = (int)Pack.LE_To_UInt32(userKey, i * 4);
 			}
 
 			return key;
 		}
 
-		public void Init(
-			ICipherParameters parameters)
+		public void Init(ICipherParameters parameters)
 		{
 			Reset();
-			buf = new byte[blockSize];
+			buf = new byte[BlockSize];
             macIV = null;
-            if (parameters is ParametersWithSBox)
+            if (parameters is ParametersWithSBox param)
 			{
-				ParametersWithSBox param = (ParametersWithSBox)parameters;
-
 				//
 				// Set the S-Box
 				//
@@ -79,17 +77,15 @@ namespace Org.BouncyCastle.Crypto.Macs
 					workingKey = GenerateWorkingKey(((KeyParameter)param.Parameters).GetKey());
 				}
 			}
-			else if (parameters is KeyParameter)
+			else if (parameters is KeyParameter keyParameter)
 			{
-				workingKey = GenerateWorkingKey(((KeyParameter)parameters).GetKey());
+				workingKey = GenerateWorkingKey(keyParameter.GetKey());
 			}
-            else if (parameters is ParametersWithIV)
+            else if (parameters is ParametersWithIV ivParam)
             {
-                ParametersWithIV p = (ParametersWithIV)parameters;
-
-                workingKey = GenerateWorkingKey(((KeyParameter)p.Parameters).GetKey());
-                Array.Copy(p.GetIV(), 0, mac, 0, mac.Length);
-                macIV = p.GetIV(); // don't skip the initial CM5Func
+                workingKey = GenerateWorkingKey(((KeyParameter)ivParam.Parameters).GetKey());
+				macIV = ivParam.GetIV(); // don't skip the initial CM5Func
+				Array.Copy(macIV, 0, mac, 0, mac.Length);
             }
 			else
 			{
@@ -105,10 +101,10 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 		public int GetMacSize()
 		{
-			return macSize;
+			return MacSize;
 		}
 
-		private int gost28147_mainStep(int n1, int key)
+		private int Gost28147_mainStep(int n1, int key)
 		{
 			int cm = (key + n1); // CM1
 
@@ -130,178 +126,222 @@ namespace Org.BouncyCastle.Crypto.Macs
 			return omLeft | omRight;
 		}
 
-		private void gost28147MacFunc(
+		private void Gost28147MacFunc(
 			int[]	workingKey,
 			byte[]	input,
 			int		inOff,
 			byte[]	output,
 			int		outOff)
 		{
-			int N1, N2, tmp;  //tmp -> for saving N1
-			N1 = bytesToint(input, inOff);
-			N2 = bytesToint(input, inOff + 4);
+			int N1 = (int)Pack.LE_To_UInt32(input, inOff);
+			int N2 = (int)Pack.LE_To_UInt32(input, inOff + 4);
+			int tmp;  //tmp -> for saving N1
 
 			for (int k = 0; k < 2; k++)  // 1-16 steps
 			{
 				for (int j = 0; j < 8; j++)
 				{
 					tmp = N1;
-					N1 = N2 ^ gost28147_mainStep(N1, workingKey[j]); // CM2
+					N1 = N2 ^ Gost28147_mainStep(N1, workingKey[j]); // CM2
 					N2 = tmp;
 				}
 			}
 
-			intTobytes(N1, output, outOff);
-			intTobytes(N2, output, outOff + 4);
-		}
-
-		//array of bytes to type int
-		private static int bytesToint(
-			byte[]	input,
-			int		inOff)
-		{
-			return (int)((input[inOff + 3] << 24) & 0xff000000) + ((input[inOff + 2] << 16) & 0xff0000)
-				+ ((input[inOff + 1] << 8) & 0xff00) + (input[inOff] & 0xff);
-		}
-
-		//int to array of bytes
-		private static void intTobytes(
-			int		num,
-			byte[]	output,
-			int		outOff)
-		{
-			output[outOff + 3] = (byte)(num >> 24);
-			output[outOff + 2] = (byte)(num >> 16);
-			output[outOff + 1] = (byte)(num >> 8);
-			output[outOff] =     (byte)num;
-		}
-
-		private static byte[] CM5func(
-			byte[]	buf,
-			int		bufOff,
-			byte[]	mac)
-		{
-			byte[] sum = new byte[buf.Length - bufOff];
-
-			Array.Copy(buf, bufOff, sum, 0, mac.Length);
-
-			for (int i = 0; i != mac.Length; i++)
-			{
-				sum[i] = (byte)(sum[i] ^ mac[i]);
-			}
-
-			return sum;
+			Pack.UInt32_To_LE((uint)N1, output, outOff);
+			Pack.UInt32_To_LE((uint)N2, output, outOff + 4);
 		}
 
-		public void Update(
-			byte input)
+		public void Update(byte input)
 		{
 			if (bufOff == buf.Length)
 			{
-				byte[] sumbuf = new byte[buf.Length];
-				Array.Copy(buf, 0, sumbuf, 0, mac.Length);
-
+				byte[] sum = new byte[buf.Length];
 				if (firstStep)
 				{
 					firstStep = false;
                     if (macIV != null)
                     {
-                        sumbuf = CM5func(buf, 0, macIV);
+                        Cm5Func(buf, 0, macIV, sum);
                     }
-                }
+					else
+                    {
+						Array.Copy(buf, 0, sum, 0, mac.Length);
+					}
+				}
 				else
 				{
-					sumbuf = CM5func(buf, 0, mac);
+					Cm5Func(buf, 0, mac, sum);
 				}
 
-				gost28147MacFunc(workingKey, sumbuf, 0, mac, 0);
+				Gost28147MacFunc(workingKey, sum, 0, mac, 0);
 				bufOff = 0;
 			}
 
 			buf[bufOff++] = input;
 		}
 
-		public void BlockUpdate(
-			byte[]	input,
-			int		inOff,
-			int		len)
+		public void BlockUpdate(byte[] input, int inOff, int len)
 		{
 			if (len < 0)
 				throw new ArgumentException("Can't have a negative input length!");
 
-			int gapLen = blockSize - bufOff;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			BlockUpdate(input.AsSpan(inOff, len));
+#else
+			int gapLen = BlockSize - bufOff;
 
 			if (len > gapLen)
 			{
 				Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-				byte[] sumbuf = new byte[buf.Length];
-				Array.Copy(buf, 0, sumbuf, 0, mac.Length);
-
+				byte[] sum = new byte[buf.Length];
 				if (firstStep)
 				{
 					firstStep = false;
                     if (macIV != null)
                     {
-                        sumbuf = CM5func(buf, 0, macIV);
+                        Cm5Func(buf, 0, macIV, sum);
                     }
-                }
+					else
+                    {
+						Array.Copy(buf, 0, sum, 0, mac.Length);
+					}
+				}
 				else
 				{
-					sumbuf = CM5func(buf, 0, mac);
+					Cm5Func(buf, 0, mac, sum);
 				}
 
-				gost28147MacFunc(workingKey, sumbuf, 0, mac, 0);
+				Gost28147MacFunc(workingKey, sum, 0, mac, 0);
 
 				bufOff = 0;
 				len -= gapLen;
 				inOff += gapLen;
 
-				while (len > blockSize)
+				while (len > BlockSize)
 				{
-					sumbuf = CM5func(input, inOff, mac);
-					gost28147MacFunc(workingKey, sumbuf, 0, mac, 0);
+					Cm5Func(input, inOff, mac, sum);
+					Gost28147MacFunc(workingKey, sum, 0, mac, 0);
 
-					len -= blockSize;
-					inOff += blockSize;
+					len -= BlockSize;
+					inOff += BlockSize;
 				}
 			}
 
 			Array.Copy(input, inOff, buf, bufOff, len);
 
 			bufOff += len;
+#endif
 		}
 
-		public int DoFinal(
-			byte[]	output,
-			int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
 		{
+			int gapLen = BlockSize - bufOff;
+
+			if (input.Length > gapLen)
+			{
+				input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+				byte[] sum = new byte[buf.Length];
+				if (firstStep)
+				{
+					firstStep = false;
+                    if (macIV != null)
+                    {
+                        Cm5Func(buf, macIV, sum);
+                    }
+                    else
+                    {
+						Array.Copy(buf, 0, sum, 0, mac.Length);
+					}
+				}
+				else
+				{
+					Cm5Func(buf, mac, sum);
+				}
+
+				Gost28147MacFunc(workingKey, sum, 0, mac, 0);
+
+				bufOff = 0;
+				input = input[gapLen..];
+
+				while (input.Length > BlockSize)
+				{
+					Cm5Func(input, mac, sum);
+					Gost28147MacFunc(workingKey, sum, 0, mac, 0);
+
+					input = input[BlockSize..];
+				}
+			}
+
+			input.CopyTo(buf.AsSpan(bufOff));
+
+			bufOff += input.Length;
+		}
+#endif
+
+		public int DoFinal(byte[] output, int outOff)
+		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return DoFinal(output.AsSpan(outOff));
+#else
 			//padding with zero
-			while (bufOff < blockSize)
+			while (bufOff < BlockSize)
 			{
 				buf[bufOff++] = 0;
 			}
 
-			byte[] sumbuf = new byte[buf.Length];
-			Array.Copy(buf, 0, sumbuf, 0, mac.Length);
+			byte[] sum = new byte[buf.Length];
+			if (firstStep)
+			{
+				firstStep = false;
+				Array.Copy(buf, 0, sum, 0, mac.Length);
+			}
+			else
+			{
+				Cm5Func(buf, 0, mac, sum);
+			}
+
+			Gost28147MacFunc(workingKey, sum, 0, mac, 0);
 
+			Array.Copy(mac, (mac.Length/2)-MacSize, output, outOff, MacSize);
+
+			Reset();
+
+			return MacSize;
+#endif
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			//padding with zero
+			while (bufOff < BlockSize)
+			{
+				buf[bufOff++] = 0;
+			}
+
+			byte[] sum = new byte[buf.Length];
 			if (firstStep)
 			{
 				firstStep = false;
+				Array.Copy(buf, 0, sum, 0, mac.Length);
 			}
 			else
 			{
-				sumbuf = CM5func(buf, 0, mac);
+				Cm5Func(buf, 0, mac, sum);
 			}
 
-			gost28147MacFunc(workingKey, sumbuf, 0, mac, 0);
+			Gost28147MacFunc(workingKey, sum, 0, mac, 0);
 
-			Array.Copy(mac, (mac.Length/2)-macSize, output, outOff, macSize);
+			mac.AsSpan((mac.Length / 2) - MacSize, MacSize).CopyTo(output);
 
 			Reset();
 
-			return macSize;
+			return MacSize;
 		}
+#endif
 
 		public void Reset()
 		{
@@ -311,5 +351,23 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 			firstStep = true;
 		}
+
+		private static void Cm5Func(byte[] buf, int bufOff, byte[] mac, byte[] sum)
+		{
+			for (int i = 0; i < BlockSize; ++i)
+			{
+				sum[i] = (byte)(buf[bufOff + i] ^ mac[i]);
+			}
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		private static void Cm5Func(ReadOnlySpan<byte> buffer, ReadOnlySpan<byte> mac, Span<byte> sum)
+		{
+			for (int i = 0; i < BlockSize; ++i)
+			{
+				sum[i] = (byte)(buffer[i] ^ mac[i]);
+			}
+		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/macs/HMac.cs b/crypto/src/crypto/macs/HMac.cs
index a717ce4f7..3445a945b 100644
--- a/crypto/src/crypto/macs/HMac.cs
+++ b/crypto/src/crypto/macs/HMac.cs
@@ -99,14 +99,24 @@ namespace Org.BouncyCastle.Crypto.Macs
             digest.BlockUpdate(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            digest.BlockUpdate(input);
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             digest.DoFinal(outputBuf, blockLength);
 
 			if (opadState != null)
 			{
 				((IMemoable)digest).Reset(opadState);
-				digest.BlockUpdate(outputBuf, blockLength, digest.GetDigestSize());
+				digest.BlockUpdate(outputBuf, blockLength, digestSize);
 			}
 			else
 			{
@@ -127,7 +137,40 @@ namespace Org.BouncyCastle.Crypto.Macs
 			}
 
             return len;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            digest.DoFinal(outputBuf.AsSpan(blockLength));
+
+            if (opadState != null)
+            {
+                ((IMemoable)digest).Reset(opadState);
+                digest.BlockUpdate(outputBuf.AsSpan(blockLength, digestSize));
+            }
+            else
+            {
+                digest.BlockUpdate(outputBuf);
+            }
+
+            int len = digest.DoFinal(output);
+
+            Array.Clear(outputBuf, blockLength, digestSize);
+
+            if (ipadState != null)
+            {
+                ((IMemoable)digest).Reset(ipadState);
+            }
+            else
+            {
+                digest.BlockUpdate(inputPad);
+            }
+
+            return len;
         }
+#endif
 
         /**
         * Reset the mac generator.
diff --git a/crypto/src/crypto/macs/ISO9797Alg3Mac.cs b/crypto/src/crypto/macs/ISO9797Alg3Mac.cs
index 6fee619c1..1b9562bda 100644
--- a/crypto/src/crypto/macs/ISO9797Alg3Mac.cs
+++ b/crypto/src/crypto/macs/ISO9797Alg3Mac.cs
@@ -13,7 +13,8 @@ namespace Org.BouncyCastle.Crypto.Macs
 	* This could as well be derived from CBCBlockCipherMac, but then the property mac in the base
 	* class must be changed to protected
 	*/
-	public class ISO9797Alg3Mac : IMac
+	public class ISO9797Alg3Mac
+		: IMac
 	{
 		private byte[] mac;
 		private byte[] buf;
@@ -180,14 +181,14 @@ namespace Org.BouncyCastle.Crypto.Macs
 			buf[bufOff++] = input;
 		}
 
-		public void BlockUpdate(
-			byte[]	input,
-			int		inOff,
-			int		len)
+		public void BlockUpdate(byte[] input, int inOff, int len)
 		{
 			if (len < 0)
 				throw new ArgumentException("Can't have a negative input length!");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			BlockUpdate(input.AsSpan(inOff, len));
+#else
 			int blockSize = cipher.GetBlockSize();
 			int resultLen = 0;
 			int gapLen = blockSize - bufOff;
@@ -214,13 +215,44 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Array.Copy(input, inOff, buf, bufOff, len);
 
 			bufOff += len;
+#endif
 		}
 
-		public int DoFinal(
-			byte[]	output,
-			int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
 		{
 			int blockSize = cipher.GetBlockSize();
+			int resultLen = 0;
+			int gapLen = blockSize - bufOff;
+
+			if (input.Length > gapLen)
+			{
+				input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+				resultLen += cipher.ProcessBlock(buf, mac);
+
+				bufOff = 0;
+				input = input[gapLen..];
+
+				while (input.Length > blockSize)
+				{
+					resultLen += cipher.ProcessBlock(input, mac);
+					input = input[blockSize..];
+				}
+			}
+
+			input.CopyTo(buf.AsSpan(bufOff));
+
+			bufOff += input.Length;
+		}
+#endif
+
+		public int DoFinal(byte[] output, int outOff)
+		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return DoFinal(output.AsSpan(outOff));
+#else
+			int blockSize = cipher.GetBlockSize();
 
 			if (padding == null)
 			{
@@ -258,7 +290,52 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Reset();
 
 			return macSize;
+#endif
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			int blockSize = cipher.GetBlockSize();
+
+			if (padding == null)
+			{
+				// pad with zeroes
+				while (bufOff < blockSize)
+				{
+					buf[bufOff++] = 0;
+				}
+			}
+			else
+			{
+				if (bufOff == blockSize)
+				{
+					cipher.ProcessBlock(buf, mac);
+					bufOff = 0;
+				}
+
+				padding.AddPadding(buf, bufOff);
+			}
+
+			cipher.ProcessBlock(buf, mac);
+
+			// Added to code from base class
+			DesEngine deseng = new DesEngine();
+
+			deseng.Init(false, this.lastKey2);
+			deseng.ProcessBlock(mac, mac);
+
+			deseng.Init(true, this.lastKey3);
+			deseng.ProcessBlock(mac, mac);
+			// ****
+
+			mac.AsSpan(0, macSize).CopyTo(output);
+
+			Reset();
+
+			return macSize;
 		}
+#endif
 
 		/**
 		* Reset the mac generator.
@@ -267,9 +344,6 @@ namespace Org.BouncyCastle.Crypto.Macs
 		{
 			Array.Clear(buf, 0, buf.Length);
 			bufOff = 0;
-
-			// reset the underlying cipher.
-			cipher.Reset();
 		}
 	}
 }
diff --git a/crypto/src/crypto/macs/KMac.cs b/crypto/src/crypto/macs/KMac.cs
index 05031ac2f..4dd754765 100644
--- a/crypto/src/crypto/macs/KMac.cs
+++ b/crypto/src/crypto/macs/KMac.cs
@@ -39,6 +39,16 @@ namespace Org.BouncyCastle.Crypto.Macs
             cshake.BlockUpdate(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (!initialised)
+                throw new InvalidOperationException("KMAC not initialized");
+
+            cshake.BlockUpdate(input);
+        }
+#endif
+
         public int DoFinal(byte[] output, int outOff)
         {
             if (firstOutput)
@@ -51,14 +61,35 @@ namespace Org.BouncyCastle.Crypto.Macs
                 cshake.BlockUpdate(encOut, 0, encOut.Length);
             }
 
-            int rv = cshake.DoFinal(output, outOff, GetMacSize());
+            int rv = cshake.OutputFinal(output, outOff, GetMacSize());
 
             Reset();
 
             return rv;
         }
 
-        public int DoFinal(byte[] output, int outOff, int outLen)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                if (!initialised)
+                    throw new InvalidOperationException("KMAC not initialized");
+
+                Span<byte> lengthEncoding = stackalloc byte[9];
+                int count = XofUtilities.RightEncode(GetMacSize() * 8, lengthEncoding);
+                cshake.BlockUpdate(lengthEncoding[..count]);
+            }
+
+            int rv = cshake.OutputFinal(output[..GetMacSize()]);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
+        public int OutputFinal(byte[] output, int outOff, int outLen)
         {
             if (firstOutput)
             {
@@ -70,14 +101,35 @@ namespace Org.BouncyCastle.Crypto.Macs
                 cshake.BlockUpdate(encOut, 0, encOut.Length);
             }
 
-            int rv = cshake.DoFinal(output, outOff, outLen);
+            int rv = cshake.OutputFinal(output, outOff, outLen);
+
+            Reset();
+
+            return rv;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int OutputFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                if (!initialised)
+                    throw new InvalidOperationException("KMAC not initialized");
+
+                Span<byte> lengthEncoding = stackalloc byte[9];
+                int count = XofUtilities.RightEncode(output.Length * 8, lengthEncoding);
+                cshake.BlockUpdate(lengthEncoding[..count]);
+            }
+
+            int rv = cshake.OutputFinal(output);
 
             Reset();
 
             return rv;
         }
+#endif
 
-        public int DoOutput(byte[] output, int outOff, int outLen)
+        public int Output(byte[] output, int outOff, int outLen)
         {
             if (firstOutput)
             {
@@ -91,8 +143,27 @@ namespace Org.BouncyCastle.Crypto.Macs
                 firstOutput = false;
             }
 
-            return cshake.DoOutput(output, outOff, outLen);
+            return cshake.Output(output, outOff, outLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Output(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                if (!initialised)
+                    throw new InvalidOperationException("KMAC not initialized");
+
+                Span<byte> lengthEncoding = stackalloc byte[9];
+                int count = XofUtilities.RightEncode(0, lengthEncoding);
+                cshake.BlockUpdate(lengthEncoding[..count]);
+
+                firstOutput = false;
+            }
+
+            return cshake.Output(output);
         }
+#endif
 
         public int GetByteLength()
         {
diff --git a/crypto/src/crypto/macs/Poly1305.cs b/crypto/src/crypto/macs/Poly1305.cs
index 595d9b051..d02216309 100644
--- a/crypto/src/crypto/macs/Poly1305.cs
+++ b/crypto/src/crypto/macs/Poly1305.cs
@@ -167,15 +167,22 @@ namespace Org.BouncyCastle.Crypto.Macs
             currentBlock[currentBlockOffset++] = input;
             if (currentBlockOffset == BlockSize)
             {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                ProcessBlock(currentBlock);
+#else
                 ProcessBlock(currentBlock, 0);
+#endif
                 currentBlockOffset = 0;
             }
         }
 
         public void BlockUpdate(byte[] input, int inOff, int len)
         {
-            // TODO Validity check on arguments
+            Check.DataLength(input, inOff, len, "input buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(inOff, len));
+#else
             int available = BlockSize - currentBlockOffset;
             if (len < available)
             {
@@ -201,37 +208,62 @@ namespace Org.BouncyCastle.Crypto.Macs
 
             Array.Copy(input, inOff + pos, currentBlock, 0, remaining);
             currentBlockOffset = remaining;
+#endif
         }
 
-        private void ProcessBlock(byte[] buf, int off)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
         {
-#if NETCOREAPP3_0_OR_GREATER
-            if (BitConverter.IsLittleEndian)
+            int available = BlockSize - currentBlockOffset;
+            if (input.Length < available)
             {
-                Span<uint> t = stackalloc uint[4];
-                Unsafe.CopyBlockUnaligned(ref Unsafe.As<uint, byte>(ref t[0]), ref buf[off], 16);
-
-                h0 +=   t[0]                        & 0x3ffffffU;
-                h1 += ((t[1] <<  6) | (t[0] >> 26)) & 0x3ffffffU;
-                h2 += ((t[2] << 12) | (t[1] >> 20)) & 0x3ffffffU;
-                h3 += ((t[3] << 18) | (t[2] >> 14)) & 0x3ffffffU;
-                h4 +=     (1 << 24) | (t[3] >>  8);
+                input.CopyTo(currentBlock.AsSpan(currentBlockOffset));
+                currentBlockOffset += input.Length;
+                return;
             }
-            else
-#endif
+
+            int pos = 0;
+            if (currentBlockOffset > 0)
+            {
+                input[..available].CopyTo(currentBlock.AsSpan(currentBlockOffset));
+                pos = available;
+                ProcessBlock(currentBlock);
+            }
+
+            int remaining;
+            while ((remaining = input.Length - pos) >= BlockSize)
             {
-                uint t0 = Pack.LE_To_UInt32(buf, off +  0);
-                uint t1 = Pack.LE_To_UInt32(buf, off +  4);
-                uint t2 = Pack.LE_To_UInt32(buf, off +  8);
-                uint t3 = Pack.LE_To_UInt32(buf, off + 12);
-
-                h0 +=   t0                      & 0x3ffffffU;
-                h1 += ((t1 <<  6) | (t0 >> 26)) & 0x3ffffffU;
-                h2 += ((t2 << 12) | (t1 >> 20)) & 0x3ffffffU;
-                h3 += ((t3 << 18) | (t2 >> 14)) & 0x3ffffffU;
-                h4 +=  ( 1 << 24) | (t3 >>  8);
+                ProcessBlock(input[pos..]);
+                pos += BlockSize;
             }
 
+            input[pos..].CopyTo(currentBlock);
+            currentBlockOffset = remaining;
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ProcessBlock(ReadOnlySpan<byte> block)
+        {
+            uint t0 = Pack.LE_To_UInt32(block);
+            uint t1 = Pack.LE_To_UInt32(block[4..]);
+            uint t2 = Pack.LE_To_UInt32(block[8..]);
+            uint t3 = Pack.LE_To_UInt32(block[12..]);
+#else
+        private void ProcessBlock(byte[] buf, int off)
+        {
+            uint t0 = Pack.LE_To_UInt32(buf, off +  0);
+            uint t1 = Pack.LE_To_UInt32(buf, off +  4);
+            uint t2 = Pack.LE_To_UInt32(buf, off +  8);
+            uint t3 = Pack.LE_To_UInt32(buf, off + 12);
+#endif
+
+            h0 +=   t0                      & 0x3ffffffU;
+            h1 += ((t1 <<  6) | (t0 >> 26)) & 0x3ffffffU;
+            h2 += ((t2 << 12) | (t1 >> 20)) & 0x3ffffffU;
+            h3 += ((t3 << 18) | (t2 >> 14)) & 0x3ffffffU;
+            h4 +=  ( 1 << 24) | (t3 >>  8);
+
             ulong tp0 = (ulong)h0 * r0 + (ulong)h1 * s4 + (ulong)h2 * s3 + (ulong)h3 * s2 + (ulong)h4 * s1;
             ulong tp1 = (ulong)h0 * r1 + (ulong)h1 * r0 + (ulong)h2 * s4 + (ulong)h3 * s3 + (ulong)h4 * s2;
             ulong tp2 = (ulong)h0 * r2 + (ulong)h1 * r1 + (ulong)h2 * r0 + (ulong)h3 * s4 + (ulong)h4 * s3;
@@ -249,7 +281,10 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         public int DoFinal(byte[] output, int outOff)
         {
-            Check.DataLength(output, outOff, BlockSize, "Output buffer is too short.");
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
+            Check.OutputLength(output, outOff, BlockSize, "output buffer is too short.");
 
             if (currentBlockOffset > 0)
             {
@@ -289,7 +324,54 @@ namespace Org.BouncyCastle.Crypto.Macs
 
             Reset();
             return BlockSize;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            Check.OutputLength(output, BlockSize, "output buffer is too short.");
+
+            if (currentBlockOffset > 0)
+            {
+                // Process padded block
+                if (currentBlockOffset < BlockSize)
+                {
+                    currentBlock[currentBlockOffset++] = 1;
+                    while (currentBlockOffset < BlockSize)
+                    {
+                        currentBlock[currentBlockOffset++] = 0;
+                    }
+
+                    h4 -= (1 << 24);
+                }
+
+                ProcessBlock(currentBlock);
+            }
+
+            Debug.Assert(h4 >> 26 == 0);
+
+            //h0 += (h4 >> 26) * 5U + 5U; h4 &= 0x3ffffff;
+            h0 += 5U;
+            h1 += h0 >> 26; h0 &= 0x3ffffff;
+            h2 += h1 >> 26; h1 &= 0x3ffffff;
+            h3 += h2 >> 26; h2 &= 0x3ffffff;
+            h4 += h3 >> 26; h3 &= 0x3ffffff;
+
+            long c = ((int)(h4 >> 26) - 1) * 5;
+            c += (long)k0 + ((h0) | (h1 << 26));
+            Pack.UInt32_To_LE((uint)c, output); c >>= 32;
+            c += (long)k1 + ((h1 >> 6) | (h2 << 20));
+            Pack.UInt32_To_LE((uint)c, output[4..]); c >>= 32;
+            c += (long)k2 + ((h2 >> 12) | (h3 << 14));
+            Pack.UInt32_To_LE((uint)c, output[8..]); c >>= 32;
+            c += (long)k3 + ((h3 >> 18) | (h4 << 8));
+            Pack.UInt32_To_LE((uint)c, output[12..]);
+
+            Reset();
+            return BlockSize;
         }
+#endif
 
         public void Reset()
         {
diff --git a/crypto/src/crypto/macs/SipHash.cs b/crypto/src/crypto/macs/SipHash.cs
index e1a19fa5b..2e8a89e3d 100644
--- a/crypto/src/crypto/macs/SipHash.cs
+++ b/crypto/src/crypto/macs/SipHash.cs
@@ -80,6 +80,9 @@ namespace Org.BouncyCastle.Crypto.Macs
 
         public virtual void BlockUpdate(byte[] input, int offset, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(offset, length));
+#else
             int i = 0, fullWords = length & ~7;
             if (wordPos == 0)
             {
@@ -115,8 +118,51 @@ namespace Org.BouncyCastle.Crypto.Macs
                     }
                 }
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int length = input.Length;
+            int i = 0, fullWords = length & ~7;
+            if (wordPos == 0)
+            {
+                for (; i < fullWords; i += 8)
+                {
+                    m = (long)Pack.LE_To_UInt64(input[i..]);
+                    ProcessMessageWord();
+                }
+                for (; i < length; ++i)
+                {
+                    m = (long)(((ulong)m >> 8) | ((ulong)input[i] << 56));
+                }
+                wordPos = length - fullWords;
+            }
+            else
+            {
+                int bits = wordPos << 3;
+                for (; i < fullWords; i += 8)
+                {
+                    ulong n = Pack.LE_To_UInt64(input[i..]);
+                    m = (long)((n << bits) | ((ulong)m >> -bits));
+                    ProcessMessageWord();
+                    m = (long)n;
+                }
+                for (; i < length; ++i)
+                {
+                    m = (long)(((ulong)m >> 8) | ((ulong)input[i] << 56));
+
+                    if (++wordPos == 8)
+                    {
+                        ProcessMessageWord();
+                        wordPos = 0;
+                    }
+                }
+            }
+        }
+#endif
+
         public virtual long DoFinal()
         {
             // NOTE: 2 distinct shifts to avoid "64-bit shift" when wordPos == 0
@@ -144,6 +190,15 @@ namespace Org.BouncyCastle.Crypto.Macs
             return 8;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            long result = DoFinal();
+            Pack.UInt64_To_LE((ulong)result, output);
+            return 8;
+        }
+#endif
+
         public virtual void Reset()
         {
             v0 = k0 ^ 0x736f6d6570736575L;
diff --git a/crypto/src/crypto/macs/SkeinMac.cs b/crypto/src/crypto/macs/SkeinMac.cs
index 07eff24f4..aaf5b312d 100644
--- a/crypto/src/crypto/macs/SkeinMac.cs
+++ b/crypto/src/crypto/macs/SkeinMac.cs
@@ -106,13 +106,26 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 		public void BlockUpdate(byte[] input, int inOff, int len)
 		{
-			engine.Update(input, inOff, len);
+			engine.BlockUpdate(input, inOff, len);
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
+		{
+			engine.BlockUpdate(input);
+		}
+#endif
+
 		public int DoFinal(byte[] output, int outOff)
 		{
 			return engine.DoFinal(output, outOff);
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			return engine.DoFinal(output);
+		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/macs/VMPCMac.cs b/crypto/src/crypto/macs/VMPCMac.cs
index 6f2da075c..c2902179f 100644
--- a/crypto/src/crypto/macs/VMPCMac.cs
+++ b/crypto/src/crypto/macs/VMPCMac.cs
@@ -22,6 +22,9 @@ namespace Org.BouncyCastle.Crypto.Macs
 
 		public virtual int DoFinal(byte[] output, int outOff)
 		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return DoFinal(output.AsSpan(outOff));
+#else
 			// Execute the Post-Processing Phase
 			for (int r = 1; r < 25; r++)
 			{
@@ -68,8 +71,61 @@ namespace Org.BouncyCastle.Crypto.Macs
 			Reset();
 
 			return M.Length;
+#endif
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual int DoFinal(Span<byte> output)
+        {
+			// Execute the Post-Processing Phase
+			for (int r = 1; r < 25; r++)
+			{
+				s = P[(s + P[n & 0xff]) & 0xff];
+
+				x4 = P[(x4 + x3 + r) & 0xff];
+				x3 = P[(x3 + x2 + r) & 0xff];
+				x2 = P[(x2 + x1 + r) & 0xff];
+				x1 = P[(x1 + s + r) & 0xff];
+				T[g & 0x1f] = (byte)(T[g & 0x1f] ^ x1);
+				T[(g + 1) & 0x1f] = (byte)(T[(g + 1) & 0x1f] ^ x2);
+				T[(g + 2) & 0x1f] = (byte)(T[(g + 2) & 0x1f] ^ x3);
+				T[(g + 3) & 0x1f] = (byte)(T[(g + 3) & 0x1f] ^ x4);
+				g = (byte)((g + 4) & 0x1f);
+
+				byte temp = P[n & 0xff];
+				P[n & 0xff] = P[s & 0xff];
+				P[s & 0xff] = temp;
+				n = (byte)((n + 1) & 0xff);
+			}
+
+			// Input T to the IV-phase of the VMPC KSA
+			for (int m = 0; m < 768; m++)
+			{
+				s = P[(s + P[m & 0xff] + T[m & 0x1f]) & 0xff];
+				byte temp = P[m & 0xff];
+				P[m & 0xff] = P[s & 0xff];
+				P[s & 0xff] = temp;
+			}
+
+			// Store 20 new outputs of the VMPC Stream Cipher input table M
+			byte[] M = new byte[20];
+			for (int i = 0; i < 20; i++)
+			{
+				s = P[(s + P[i & 0xff]) & 0xff];
+				M[i] = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff];
+
+				byte temp = P[i & 0xff];
+				P[i & 0xff] = P[s & 0xff];
+				P[s & 0xff] = temp;
+			}
+
+			M.CopyTo(output);
+			Reset();
+
+			return M.Length;
+		}
+#endif
+
 		public virtual string AlgorithmName
 		{
 			get { return "VMPC-MAC"; }
@@ -159,15 +215,24 @@ namespace Org.BouncyCastle.Crypto.Macs
 			n = (byte) ((n + 1) & 0xff);
 		}
 
-		public virtual void BlockUpdate(byte[] input, int inOff, int len)
+		public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
 		{
-			if ((inOff + len) > input.Length)
-				throw new DataLengthException("input buffer too short");
+			Check.DataLength(input, inOff, inLen, "input buffer too short");
 
-			for (int i = 0; i < len; i++)
+			for (int i = 0; i < inLen; i++)
 			{
 				Update(input[inOff + i]);
 			}
 		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+			for (int i = 0; i < input.Length; i++)
+			{
+				Update(input[i]);
+			}
+		}
+#endif
 	}
 }
diff --git a/crypto/src/crypto/modes/CbcBlockCipher.cs b/crypto/src/crypto/modes/CbcBlockCipher.cs
index 9345fd8c2..8e2b3c2a4 100644
--- a/crypto/src/crypto/modes/CbcBlockCipher.cs
+++ b/crypto/src/crypto/modes/CbcBlockCipher.cs
@@ -7,8 +7,8 @@ namespace Org.BouncyCastle.Crypto.Modes
     /**
     * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
     */
-    public class CbcBlockCipher
-		: IBlockCipher
+    public sealed class CbcBlockCipher
+		: IBlockCipherMode
     {
         private byte[]			IV, cbcV, cbcNextV;
 		private int				blockSize;
@@ -36,10 +36,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
 
         /**
         * Initialise the cipher and, possibly, the initialisation vector (IV).
@@ -51,23 +48,18 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public void Init(
-            bool forEncryption,
-            ICipherParameters parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
             bool oldEncrypting = this.encrypting;
 
             this.encrypting = forEncryption;
 
-            if (parameters is ParametersWithIV)
+            if (parameters is ParametersWithIV ivParam)
             {
-                ParametersWithIV ivParam = (ParametersWithIV)parameters;
-                byte[]      iv = ivParam.GetIV();
+                byte[] iv = ivParam.GetIV();
 
                 if (iv.Length != blockSize)
-                {
                     throw new ArgumentException("initialisation vector must be the same length as block size");
-                }
 
                 Array.Copy(iv, 0, IV, 0, iv.Length);
 
@@ -112,30 +104,28 @@ namespace Org.BouncyCastle.Crypto.Modes
             return cipher.GetBlockSize();
         }
 
-        /**
-        * Process one block of input from the array in and write it to
-        * the out array.
-        *
-        * @param in the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-            return (encrypting)
-				?	EncryptBlock(input, inOff, output, outOff)
-				:	DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return encrypting
+                ? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+                : DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+            return encrypting
+				? EncryptBlock(input, inOff, output, outOff)
+				: DecryptBlock(input, inOff, output, outOff);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            return encrypting
+                ? EncryptBlock(input, output)
+                : DecryptBlock(input, output);
+        }
+#endif
+
         /**
         * reset the chaining vector back to the IV and reset the underlying
         * cipher.
@@ -144,37 +134,52 @@ namespace Org.BouncyCastle.Crypto.Modes
         {
             Array.Copy(IV, 0, cbcV, 0, IV.Length);
 			Array.Clear(cbcNextV, 0, cbcNextV.Length);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            for (int i = 0; i < blockSize; i++)
+            {
+                cbcV[i] ^= input[i];
+            }
 
-            cipher.Reset();
+            int length = cipher.ProcessBlock(cbcV, output);
+
+            output[..blockSize].CopyTo(cbcV);
+
+            return length;
         }
 
-        /**
-        * Do the appropriate chaining step for CBC mode encryption.
-        *
-        * @param in the array containing the data to be encrypted.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the encrypted data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        private int EncryptBlock(
-            byte[]      input,
-            int         inOff,
-            byte[]      outBytes,
-            int         outOff)
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            if ((inOff + blockSize) > input.Length)
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            input[..blockSize].CopyTo(cbcNextV);
+
+            int length = cipher.ProcessBlock(input, output);
+
+            for (int i = 0; i < blockSize; i++)
             {
-                throw new DataLengthException("input buffer too short");
+                output[i] ^= cbcV[i];
             }
 
-            /*
-            * XOR the cbcV and the input,
-            * then encrypt the cbcV
-            */
+            byte[] tmp = cbcV;
+            cbcV = cbcNextV;
+            cbcNextV = tmp;
+
+            return length;
+        }
+#else
+        private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
+        {
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
+
             for (int i = 0; i < blockSize; i++)
             {
                 cbcV[i] ^= input[inOff + i];
@@ -182,60 +187,31 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             int length = cipher.ProcessBlock(cbcV, 0, outBytes, outOff);
 
-            /*
-            * copy ciphertext to cbcV
-            */
             Array.Copy(outBytes, outOff, cbcV, 0, cbcV.Length);
 
             return length;
         }
 
-        /**
-        * Do the appropriate chaining step for CBC mode decryption.
-        *
-        * @param in the array containing the data to be decrypted.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the decrypted data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        private int DecryptBlock(
-            byte[]      input,
-            int         inOff,
-            byte[]      outBytes,
-            int         outOff)
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            if ((inOff + blockSize) > input.Length)
-            {
-                throw new DataLengthException("input buffer too short");
-            }
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
 
             Array.Copy(input, inOff, cbcNextV, 0, blockSize);
 
             int length = cipher.ProcessBlock(input, inOff, outBytes, outOff);
 
-            /*
-            * XOR the cbcV and the output
-            */
             for (int i = 0; i < blockSize; i++)
             {
                 outBytes[outOff + i] ^= cbcV[i];
             }
 
-            /*
-            * swap the back up buffer into next position
-            */
-            byte[]  tmp;
-
-            tmp = cbcV;
+            byte[] tmp = cbcV;
             cbcV = cbcNextV;
             cbcNextV = tmp;
 
             return length;
         }
+#endif
     }
-
 }
diff --git a/crypto/src/crypto/modes/CcmBlockCipher.cs b/crypto/src/crypto/modes/CcmBlockCipher.cs
index abd7dbb8d..3569ac2b7 100644
--- a/crypto/src/crypto/modes/CcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/CcmBlockCipher.cs
@@ -49,35 +49,26 @@ namespace Org.BouncyCastle.Crypto.Modes
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public virtual IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public virtual IBlockCipher UnderlyingCipher => cipher;
 
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public virtual void Init(bool forEncryption, ICipherParameters parameters)
         {
             this.forEncryption = forEncryption;
 
             ICipherParameters cipherParameters;
-            if (parameters is AeadParameters)
+            if (parameters is AeadParameters aeadParameters)
             {
-                AeadParameters param = (AeadParameters) parameters;
-
-                nonce = param.GetNonce();
-                initialAssociatedText = param.GetAssociatedText();
-                macSize = GetMacSize(forEncryption, param.MacSize);
-                cipherParameters = param.Key;
+                nonce = aeadParameters.GetNonce();
+                initialAssociatedText = aeadParameters.GetAssociatedText();
+                macSize = GetMacSize(forEncryption, aeadParameters.MacSize);
+                cipherParameters = aeadParameters.Key;
             }
-            else if (parameters is ParametersWithIV)
+            else if (parameters is ParametersWithIV parametersWithIV)
             {
-                ParametersWithIV param = (ParametersWithIV) parameters;
-
-                nonce = param.GetIV();
+                nonce = parametersWithIV.GetIV();
                 initialAssociatedText = null;
                 macSize = GetMacSize(forEncryption, 64);
-                cipherParameters = param.Parameters;
+                cipherParameters = parametersWithIV.Parameters;
             }
             else
             {
@@ -96,10 +87,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset();
         }
 
-        public virtual string AlgorithmName
-        {
-            get { return cipher.AlgorithmName + "/CCM"; }
-        }
+        public virtual string AlgorithmName => cipher.AlgorithmName + "/CCM";
 
         public virtual int GetBlockSize()
         {
@@ -117,34 +105,53 @@ namespace Org.BouncyCastle.Crypto.Modes
             associatedText.Write(inBytes, inOff, len);
         }
 
-        public virtual int ProcessByte(
-            byte	input,
-            byte[]	outBytes,
-            int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            // TODO: Process AAD online
+            associatedText.Write(input);
+        }
+#endif
+
+        public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
+        {
+            data.WriteByte(input);
+
+            return 0;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
         {
             data.WriteByte(input);
 
             return 0;
         }
+#endif
 
-        public virtual int ProcessBytes(
-            byte[]	inBytes,
-            int		inOff,
-            int		inLen,
-            byte[]	outBytes,
-            int		outOff)
+        public virtual int ProcessBytes(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
         {
-            Check.DataLength(inBytes, inOff, inLen, "Input buffer too short");
+            Check.DataLength(inBytes, inOff, inLen, "input buffer too short");
 
             data.Write(inBytes, inOff, inLen);
 
             return 0;
         }
 
-        public virtual int DoFinal(
-            byte[]	outBytes,
-            int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            data.Write(input);
+
+            return 0;
+        }
+#endif
+
+        public virtual int DoFinal(byte[] outBytes, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(outBytes.AsSpan(outOff));
+#else
             byte[] input = data.GetBuffer();
             int inLen = Convert.ToInt32(data.Length);
 
@@ -153,11 +160,25 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset();
 
             return len;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            byte[] input = data.GetBuffer();
+            int inLen = Convert.ToInt32(data.Length);
+
+            int len = ProcessPacket(input.AsSpan(0, inLen), output);
+
+            Reset();
+
+            return len;
+        }
+#endif
+
         public virtual void Reset()
         {
-            cipher.Reset();
             associatedText.SetLength(0);
             data.SetLength(0);
         }
@@ -173,14 +194,12 @@ namespace Org.BouncyCastle.Crypto.Modes
             return Arrays.CopyOfRange(macBlock, 0, macSize);
         }
 
-        public virtual int GetUpdateOutputSize(
-            int len)
+        public virtual int GetUpdateOutputSize(int len)
         {
             return 0;
         }
 
-        public virtual int GetOutputSize(
-            int len)
+        public virtual int GetOutputSize(int len)
         {
             int totalData = Convert.ToInt32(data.Length) + len;
 
@@ -333,8 +352,106 @@ namespace Org.BouncyCastle.Crypto.Modes
             return outputLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int inLen = input.Length;
+
+            // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
+            // Need to keep the CTR and CBC Mac parts around and reset
+            if (keyParam == null)
+                throw new InvalidOperationException("CCM cipher unitialized.");
+
+            int n = nonce.Length;
+            int q = 15 - n;
+            if (q < 4)
+            {
+                int limitLen = 1 << (8 * q);
+                if (inLen >= limitLen)
+                    throw new InvalidOperationException("CCM packet too large for choice of q.");
+            }
+
+            byte[] iv = new byte[BlockSize];
+            iv[0] = (byte)((q - 1) & 0x7);
+            nonce.CopyTo(iv, 1);
+
+            IBlockCipher ctrCipher = new SicBlockCipher(cipher);
+            ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
+
+            int outputLen;
+            int index = 0;
+            Span<byte> block = stackalloc byte[BlockSize];
+
+            if (forEncryption)
+            {
+                outputLen = inLen + macSize;
+                Check.OutputLength(output, outputLen, "output buffer too short");
+
+                CalculateMac(input, macBlock);
+
+                byte[] encMac = new byte[BlockSize];
+                ctrCipher.ProcessBlock(macBlock, encMac);   // S0
+
+                while (index < (inLen - BlockSize))                 // S1...
+                {
+                    ctrCipher.ProcessBlock(input[index..], output[index..]);
+                    index += BlockSize;
+                }
+
+                input[index..].CopyTo(block);
+
+                ctrCipher.ProcessBlock(block, block);
+
+                block[..(inLen - index)].CopyTo(output[index..]);
+
+                encMac.AsSpan(0, macSize).CopyTo(output[inLen..]);
+            }
+            else
+            {
+                if (inLen < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                outputLen = inLen - macSize;
+                Check.OutputLength(output, outputLen, "output buffer too short");
+
+                input[outputLen..].CopyTo(macBlock);
+
+                ctrCipher.ProcessBlock(macBlock, macBlock);
+
+                for (int i = macSize; i != macBlock.Length; i++)
+                {
+                    macBlock[i] = 0;
+                }
+
+                while (index < (outputLen - BlockSize))
+                {
+                    ctrCipher.ProcessBlock(input[index..], output[index..]);
+                    index += BlockSize;
+                }
+
+                input[index..outputLen].CopyTo(block);
+
+                ctrCipher.ProcessBlock(block, block);
+
+                block[..(outputLen - index)].CopyTo(output[index..]);
+
+                Span<byte> calculatedMacBlock = stackalloc byte[BlockSize];
+
+                CalculateMac(output[..outputLen], calculatedMacBlock);
+
+                if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
+                    throw new InvalidCipherTextException("mac check in CCM failed");
+            }
+
+            return outputLen;
+        }
+#endif
+
         private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return CalculateMac(data.AsSpan(dataOff, dataLen), macBlock);
+#else
             IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
 
             cMac.Init(keyParam);
@@ -421,7 +538,100 @@ namespace Org.BouncyCastle.Crypto.Modes
             cMac.BlockUpdate(data, dataOff, dataLen);
 
             return cMac.DoFinal(macBlock, 0);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int CalculateMac(ReadOnlySpan<byte> data, Span<byte> macBlock)
+        {
+            IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
+
+            cMac.Init(keyParam);
+
+            //
+            // build b0
+            //
+            byte[] b0 = new byte[16];
+
+            if (HasAssociatedText())
+            {
+                b0[0] |= 0x40;
+            }
+
+            b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
+
+            b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
+
+            Array.Copy(nonce, 0, b0, 1, nonce.Length);
+
+            int q = data.Length;
+            int count = 1;
+            while (q > 0)
+            {
+                b0[b0.Length - count] = (byte)(q & 0xff);
+                q >>= 8;
+                count++;
+            }
+
+            cMac.BlockUpdate(b0, 0, b0.Length);
+
+            //
+            // process associated text
+            //
+            if (HasAssociatedText())
+            {
+                int extra;
+
+                int textLength = GetAssociatedTextLength();
+                if (textLength < ((1 << 16) - (1 << 8)))
+                {
+                    cMac.Update((byte)(textLength >> 8));
+                    cMac.Update((byte)textLength);
+
+                    extra = 2;
+                }
+                else // can't go any higher than 2^32
+                {
+                    cMac.Update((byte)0xff);
+                    cMac.Update((byte)0xfe);
+                    cMac.Update((byte)(textLength >> 24));
+                    cMac.Update((byte)(textLength >> 16));
+                    cMac.Update((byte)(textLength >> 8));
+                    cMac.Update((byte)textLength);
+
+                    extra = 6;
+                }
+
+                if (initialAssociatedText != null)
+                {
+                    cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
+                }
+                if (associatedText.Length > 0)
+                {
+                    byte[] input = associatedText.GetBuffer();
+                    int len = Convert.ToInt32(associatedText.Length);
+
+                    cMac.BlockUpdate(input, 0, len);
+                }
+
+                extra = (extra + textLength) % 16;
+                if (extra != 0)
+                {
+                    for (int i = extra; i < 16; ++i)
+                    {
+                        cMac.Update((byte)0x00);
+                    }
+                }
+            }
+
+            //
+            // add the text
+            //
+            cMac.BlockUpdate(data);
+
+            return cMac.DoFinal(macBlock);
         }
+#endif
 
         private int GetMacSize(bool forEncryption, int requestedMacBits)
         {
diff --git a/crypto/src/crypto/modes/CfbBlockCipher.cs b/crypto/src/crypto/modes/CfbBlockCipher.cs
index ed0be407a..7bce9843f 100644
--- a/crypto/src/crypto/modes/CfbBlockCipher.cs
+++ b/crypto/src/crypto/modes/CfbBlockCipher.cs
@@ -8,7 +8,7 @@ namespace Org.BouncyCastle.Crypto.Modes
     * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher.
     */
     public class CfbBlockCipher
-		: IBlockCipher
+		: IBlockCipherMode
     {
         private byte[]	IV;
         private byte[]	cfbV;
@@ -43,10 +43,8 @@ namespace Org.BouncyCastle.Crypto.Modes
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
+
         /**
         * Initialise the cipher and, possibly, the initialisation vector (IV).
         * If an IV isn't passed as part of the parameter, the IV will be all zeros.
@@ -108,56 +106,76 @@ namespace Org.BouncyCastle.Crypto.Modes
             return blockSize;
         }
 
-		/**
-        * Process one block of input from the array in and write it to
-        * the out array.
-        *
-        * @param in the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-            return (encrypting)
-				?	EncryptBlock(input, inOff, output, outOff)
-				:	DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return encrypting
+                ? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+                : DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+            return encrypting
+				? EncryptBlock(input, inOff, output, outOff)
+				: DecryptBlock(input, inOff, output, outOff);
+#endif
         }
 
-		/**
-        * Do the appropriate processing for CFB mode encryption.
-        *
-        * @param in the array containing the data to be encrypted.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the encrypted data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int EncryptBlock(
-            byte[]      input,
-            int         inOff,
-            byte[]      outBytes,
-            int         outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            return encrypting
+                ? EncryptBlock(input, output)
+                : DecryptBlock(input, output);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            if ((inOff + blockSize) > input.Length)
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            cipher.ProcessBlock(cfbV, cfbOutV);
+            //
+            // XOR the cfbV with the plaintext producing the ciphertext
+            //
+            for (int i = 0; i < blockSize; i++)
             {
-                throw new DataLengthException("input buffer too short");
+                output[i] = (byte)(cfbOutV[i] ^ input[i]);
             }
-            if ((outOff + blockSize) > outBytes.Length)
+            //
+            // change over the input block.
+            //
+            Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize);
+            output[..blockSize].CopyTo(cfbV.AsSpan(cfbV.Length - blockSize));
+            return blockSize;
+        }
+
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            cipher.ProcessBlock(cfbV, 0, cfbOutV, 0);
+            //
+            // change over the input block.
+            //
+            Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize);
+            input[..blockSize].CopyTo(cfbV.AsSpan(cfbV.Length - blockSize));
+            //
+            // XOR the cfbV with the ciphertext producing the plaintext
+            //
+            for (int i = 0; i < blockSize; i++)
             {
-                throw new DataLengthException("output buffer too short");
+                output[i] = (byte)(cfbOutV[i] ^ input[i]);
             }
+            return blockSize;
+        }
+#else
+        private int EncryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
+        {
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
+
             cipher.ProcessBlock(cfbV, 0, cfbOutV, 0);
             //
             // XOR the cfbV with the plaintext producing the ciphertext
@@ -173,32 +191,12 @@ namespace Org.BouncyCastle.Crypto.Modes
             Array.Copy(outBytes, outOff, cfbV, cfbV.Length - blockSize, blockSize);
             return blockSize;
         }
-        /**
-        * Do the appropriate processing for CFB mode decryption.
-        *
-        * @param in the array containing the data to be decrypted.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the encrypted data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int DecryptBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	outBytes,
-            int		outOff)
+
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            if ((inOff + blockSize) > input.Length)
-            {
-                throw new DataLengthException("input buffer too short");
-            }
-            if ((outOff + blockSize) > outBytes.Length)
-            {
-                throw new DataLengthException("output buffer too short");
-            }
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
+
             cipher.ProcessBlock(cfbV, 0, cfbOutV, 0);
             //
             // change over the input block.
@@ -214,6 +212,8 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
             return blockSize;
         }
+#endif
+
         /**
         * reset the chaining vector back to the IV and reset the underlying
         * cipher.
@@ -221,7 +221,6 @@ namespace Org.BouncyCastle.Crypto.Modes
         public void Reset()
         {
             Array.Copy(IV, 0, cfbV, 0, IV.Length);
-            cipher.Reset();
         }
     }
 }
diff --git a/crypto/src/crypto/modes/ChaCha20Poly1305.cs b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
index 462013200..2fce81e22 100644
--- a/crypto/src/crypto/modes/ChaCha20Poly1305.cs
+++ b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
@@ -209,6 +209,19 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            CheckAad();
+
+            if (!input.IsEmpty)
+            {
+                this.mAadCount = IncrementCount(mAadCount, (uint)input.Length, AadLimit);
+                mPoly1305.BlockUpdate(input);
+            }
+        }
+#endif
+
         public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
         {
             CheckData();
@@ -221,7 +234,11 @@ namespace Org.BouncyCastle.Crypto.Modes
                 if (++mBufPos == mBuf.Length)
                 {
                     mPoly1305.BlockUpdate(mBuf, 0, BufSize);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    ProcessBlock(mBuf, outBytes.AsSpan(outOff));
+#else
                     ProcessBlock(mBuf, 0, outBytes, outOff);
+#endif
                     Array.Copy(mBuf, BufSize, mBuf, 0, MacSize);
                     this.mBufPos = MacSize;
                     return BufSize;
@@ -234,7 +251,11 @@ namespace Org.BouncyCastle.Crypto.Modes
                 mBuf[mBufPos] = input;
                 if (++mBufPos == BufSize)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    ProcessBlock(mBuf, outBytes.AsSpan(outOff));
+#else
                     ProcessBlock(mBuf, 0, outBytes, outOff);
+#endif
                     mPoly1305.BlockUpdate(outBytes, outOff, BufSize);
                     this.mBufPos = 0;
                     return BufSize;
@@ -247,6 +268,46 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            CheckData();
+
+            switch (mState)
+            {
+            case State.DecData:
+            {
+                mBuf[mBufPos] = input;
+                if (++mBufPos == mBuf.Length)
+                {
+                    mPoly1305.BlockUpdate(mBuf.AsSpan(0, BufSize));
+                    ProcessBlock(mBuf, output);
+                    Array.Copy(mBuf, BufSize, mBuf, 0, MacSize);
+                    this.mBufPos = MacSize;
+                    return BufSize;
+                }
+
+                return 0;
+            }
+            case State.EncData:
+            {
+                mBuf[mBufPos] = input;
+                if (++mBufPos == BufSize)
+                {
+                    ProcessBlock(mBuf, output);
+                    mPoly1305.BlockUpdate(output[..BufSize]);
+                    this.mBufPos = 0;
+                    return BufSize;
+                }
+
+                return 0;
+            }
+            default:
+                throw new InvalidOperationException();
+            }
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
         {
             if (null == inBytes)
@@ -267,6 +328,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             if (outOff < 0)
                 throw new ArgumentException("cannot be negative", "outOff");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(inBytes.AsSpan(inOff, len), Spans.FromNullable(outBytes, outOff));
+#else
             CheckData();
 
             int resultLen = 0;
@@ -375,7 +439,119 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             return resultLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            CheckData();
+
+            int resultLen = 0;
+
+            switch (mState)
+            {
+            case State.DecData:
+            {
+                int available = mBuf.Length - mBufPos;
+                if (input.Length < available)
+                {
+                    input.CopyTo(mBuf.AsSpan(mBufPos));
+                    mBufPos += input.Length;
+                    break;
+                }
+
+                if (mBufPos >= BufSize)
+                {
+                    mPoly1305.BlockUpdate(mBuf.AsSpan(0, BufSize));
+                    ProcessBlock(mBuf, output);
+                    Array.Copy(mBuf, BufSize, mBuf, 0, mBufPos -= BufSize);
+                    resultLen = BufSize;
+
+                    available += BufSize;
+                    if (input.Length < available)
+                    {
+                        input.CopyTo(mBuf.AsSpan(mBufPos));
+                        mBufPos += input.Length;
+                        break;
+                    }
+                }
+
+                int inLimit1 = mBuf.Length;
+                int inLimit2 = inLimit1 + BufSize;
+
+                available = BufSize - mBufPos;
+                input[..available].CopyTo(mBuf.AsSpan(mBufPos));
+                mPoly1305.BlockUpdate(mBuf.AsSpan(0, BufSize));
+                ProcessBlock(mBuf, output[resultLen..]);
+                input = input[available..];
+                resultLen += BufSize;
+
+                while (input.Length >= inLimit2)
+                {
+                    mPoly1305.BlockUpdate(input[..(BufSize * 2)]);
+                    ProcessBlocks2(input, output[resultLen..]);
+                    input = input[(BufSize * 2)..];
+                    resultLen += BufSize * 2;
+                }
+
+                if (input.Length >= inLimit1)
+                {
+                    mPoly1305.BlockUpdate(input[..BufSize]);
+                    ProcessBlock(input, output[resultLen..]);
+                    input = input[BufSize..];
+                    resultLen += BufSize;
+                }
+
+                mBufPos = input.Length;
+                input.CopyTo(mBuf);
+                break;
+            }
+            case State.EncData:
+            {
+                int available = BufSize - mBufPos;
+                if (input.Length < available)
+                {
+                    input.CopyTo(mBuf.AsSpan(mBufPos));
+                    mBufPos += input.Length;
+                    break;
+                }
+
+                if (mBufPos > 0)
+                {
+                    input[..available].CopyTo(mBuf.AsSpan(mBufPos));
+                    ProcessBlock(mBuf, output);
+                    input = input[available..];
+                    resultLen = BufSize;
+                }
+
+                while (input.Length >= BufSize * 2)
+                {
+                    ProcessBlocks2(input, output[resultLen..]);
+                    input = input[(BufSize * 2)..];
+                    resultLen += BufSize * 2;
+                }
+
+                if (input.Length >= BufSize)
+                {
+                    ProcessBlock(input, output[resultLen..]);
+                    input = input[BufSize..];
+                    resultLen += BufSize;
+                }
+
+                mPoly1305.BlockUpdate(output[..resultLen]);
+
+                mBufPos = input.Length;
+                input.CopyTo(mBuf);
+                break;
+            }
+            default:
+                throw new InvalidOperationException();
+            }
+
+            return resultLen;
         }
+#endif
 
         public virtual int DoFinal(byte[] outBytes, int outOff)
         {
@@ -384,6 +560,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             if (outOff < 0)
                 throw new ArgumentException("cannot be negative", "outOff");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(outBytes.AsSpan(outOff));
+#else
             CheckData();
 
             Array.Clear(mMac, 0, MacSize);
@@ -410,9 +589,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 FinishData(State.DecFinal);
 
                 if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen))
-                {
                     throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
-                }
 
                 break;
             }
@@ -440,7 +617,68 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset(false, true);
 
             return resultLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            CheckData();
+
+            Array.Clear(mMac, 0, MacSize);
+
+            int resultLen = 0;
+
+            switch (mState)
+            {
+            case State.DecData:
+            {
+                if (mBufPos < MacSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                resultLen = mBufPos - MacSize;
+
+                Check.OutputLength(output, resultLen, "output buffer too short");
+
+                if (resultLen > 0)
+                {
+                    mPoly1305.BlockUpdate(mBuf, 0, resultLen);
+                    ProcessData(mBuf.AsSpan(0, resultLen), output);
+                }
+
+                FinishData(State.DecFinal);
+
+                if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen))
+                    throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
+
+                break;
+            }
+            case State.EncData:
+            {
+                resultLen = mBufPos + MacSize;
+
+                Check.OutputLength(output, resultLen, "output buffer too short");
+
+                if (mBufPos > 0)
+                {
+                    ProcessData(mBuf.AsSpan(0, mBufPos), output);
+                    mPoly1305.BlockUpdate(output[..mBufPos]);
+                }
+
+                FinishData(State.EncFinal);
+
+                mMac.AsSpan(0, MacSize).CopyTo(output[mBufPos..]);
+                break;
+            }
+            default:
+                throw new InvalidOperationException();
+            }
+
+            Reset(false, true);
+
+            return resultLen;
         }
+#endif
 
         public virtual byte[] GetMac()
         {
@@ -525,6 +763,18 @@ namespace Org.BouncyCastle.Crypto.Modes
 
         private void InitMac()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> firstBlock = stackalloc byte[64];
+            try
+            {
+                mChacha20.ProcessBytes(firstBlock, firstBlock);
+                mPoly1305.Init(new KeyParameter(firstBlock[..32]));
+            }
+            finally
+            {
+                firstBlock.Fill(0x00);
+            }
+#else
             byte[] firstBlock = new byte[64];
             try
             {
@@ -535,6 +785,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 Array.Clear(firstBlock, 0, 64);
             }
+#endif
         }
 
         private void PadMac(ulong count)
@@ -546,6 +797,34 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, 64, "output buffer too short");
+
+            mChacha20.ProcessBlock(input, output);
+
+            this.mDataCount = IncrementCount(mDataCount, 64U, DataLimit);
+        }
+
+        private void ProcessBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, 128, "output buffer too short");
+
+            mChacha20.ProcessBlocks2(input, output);
+
+            this.mDataCount = IncrementCount(mDataCount, 128U, DataLimit);
+        }
+
+        private void ProcessData(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            mChacha20.ProcessBytes(input, output);
+
+            this.mDataCount = IncrementCount(mDataCount, (uint)input.Length, DataLimit);
+        }
+#else
         private void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
         {
             Check.OutputLength(outBytes, outOff, 64, "output buffer too short");
@@ -572,6 +851,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             this.mDataCount = IncrementCount(mDataCount, (uint)inLen, DataLimit);
         }
+#endif
 
         private void Reset(bool clearMac, bool resetCipher)
         {
diff --git a/crypto/src/crypto/modes/CtsBlockCipher.cs b/crypto/src/crypto/modes/CtsBlockCipher.cs
index ff37844ab..e76049a36 100644
--- a/crypto/src/crypto/modes/CtsBlockCipher.cs
+++ b/crypto/src/crypto/modes/CtsBlockCipher.cs
@@ -1,9 +1,6 @@
 using System;
 using System.Diagnostics;
 
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Parameters;
-
 namespace Org.BouncyCastle.Crypto.Modes
 {
     /**
@@ -15,21 +12,24 @@ namespace Org.BouncyCastle.Crypto.Modes
     {
         private readonly int blockSize;
 
+        public CtsBlockCipher(IBlockCipher cipher)
+            : this(EcbBlockCipher.GetBlockCipherMode(cipher))
+        {
+        }
+
         /**
         * Create a buffered block cipher that uses Cipher Text Stealing
         *
         * @param cipher the underlying block cipher this buffering object wraps.
         */
-        public CtsBlockCipher(
-            IBlockCipher cipher)
+        public CtsBlockCipher(IBlockCipherMode cipherMode)
         {
-			// TODO Should this test for acceptable ones instead?
-			if (cipher is OfbBlockCipher || cipher is CfbBlockCipher)
+            if (!(cipherMode is CbcBlockCipher || cipherMode is EcbBlockCipher))
                 throw new ArgumentException("CtsBlockCipher can only accept ECB, or CBC ciphers");
 
-			this.cipher = cipher;
+			m_cipherMode = cipherMode;
 
-            blockSize = cipher.GetBlockSize();
+            blockSize = cipherMode.GetBlockSize();
 
             buf = new byte[blockSize * 2];
             bufOff = 0;
@@ -80,16 +80,13 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception DataLengthException if there isn't enough space in out.
         * @exception InvalidOperationException if the cipher isn't initialised.
         */
-        public override int ProcessByte(
-            byte	input,
-            byte[]	output,
-            int		outOff)
+        public override int ProcessByte(byte input, byte[] output, int outOff)
         {
             int resultLen = 0;
 
             if (bufOff == buf.Length)
             {
-                resultLen = cipher.ProcessBlock(buf, 0, output, outOff);
+                resultLen = m_cipherMode.ProcessBlock(buf, 0, output, outOff);
 				Debug.Assert(resultLen == blockSize);
 
 				Array.Copy(buf, blockSize, buf, 0, blockSize);
@@ -101,7 +98,27 @@ namespace Org.BouncyCastle.Crypto.Modes
             return resultLen;
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            int resultLen = 0;
+
+            if (bufOff == buf.Length)
+            {
+                resultLen = m_cipherMode.ProcessBlock(buf, output);
+                Debug.Assert(resultLen == blockSize);
+
+                Array.Copy(buf, blockSize, buf, 0, blockSize);
+                bufOff = blockSize;
+            }
+
+            buf[bufOff++] = input;
+
+            return resultLen;
+        }
+#endif
+
+        /**
         * process an array of bytes, producing output if necessary.
         *
         * @param in the input byte array.
@@ -113,27 +130,17 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception DataLengthException if there isn't enough space in out.
         * @exception InvalidOperationException if the cipher isn't initialised.
         */
-        public override int ProcessBytes(
-            byte[]	input,
-            int		inOff,
-            int		length,
-            byte[]	output,
-            int		outOff)
+        public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
         {
             if (length < 0)
-            {
-                throw new ArgumentException("Can't have a negative input outLength!");
-            }
+                throw new ArgumentException("Can't have a negative input length!");
 
             int blockSize = GetBlockSize();
             int outLength = GetUpdateOutputSize(length);
 
             if (outLength > 0)
             {
-                if ((outOff + outLength) > output.Length)
-                {
-                    throw new DataLengthException("output buffer too short");
-                }
+                Check.OutputLength(output, outOff, outLength, "output buffer too short");
             }
 
             int resultLen = 0;
@@ -143,7 +150,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-                resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
+                resultLen = m_cipherMode.ProcessBlock(buf, 0, output, outOff);
                 Array.Copy(buf, blockSize, buf, 0, blockSize);
 
                 bufOff = blockSize;
@@ -154,7 +161,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 while (length > blockSize)
                 {
                     Array.Copy(input, inOff, buf, bufOff, blockSize);
-                    resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen);
+                    resultLen += m_cipherMode.ProcessBlock(buf, 0, output, outOff + resultLen);
                     Array.Copy(buf, blockSize, buf, 0, blockSize);
 
                     length -= blockSize;
@@ -169,6 +176,49 @@ namespace Org.BouncyCastle.Crypto.Modes
             return resultLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int blockSize = GetBlockSize();
+            int outLength = GetUpdateOutputSize(input.Length);
+
+            if (outLength > 0)
+            {
+                Check.OutputLength(output, outLength, "output buffer too short");
+            }
+
+            int resultLen = 0;
+            int gapLen = buf.Length - bufOff;
+
+            if (input.Length > gapLen)
+            {
+                input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+                resultLen = m_cipherMode.ProcessBlock(buf, output);
+                Array.Copy(buf, blockSize, buf, 0, blockSize);
+
+                bufOff = blockSize;
+
+                input = input[gapLen..];
+
+                while (input.Length > blockSize)
+                {
+                    input[..blockSize].CopyTo(buf.AsSpan(bufOff));
+                    resultLen += m_cipherMode.ProcessBlock(buf, output[resultLen..]);
+                    Array.Copy(buf, blockSize, buf, 0, blockSize);
+
+                    input = input[blockSize..];
+                }
+            }
+
+            input.CopyTo(buf.AsSpan(bufOff));
+
+            bufOff += input.Length;
+
+            return resultLen;
+        }
+#endif
+
         /**
         * Process the last block in the buffer.
         *
@@ -182,27 +232,21 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception InvalidCipherTextException if cipher text decrypts wrongly (in
         * case the exception will never Get thrown).
         */
-        public override int DoFinal(
-            byte[]  output,
-            int     outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
             if (bufOff + outOff > output.Length)
-            {
-                throw new DataLengthException("output buffer too small in doFinal");
-            }
+                throw new DataLengthException("output buffer too small in DoFinal");
 
-            int blockSize = cipher.GetBlockSize();
+            int blockSize = m_cipherMode.GetBlockSize();
             int length = bufOff - blockSize;
             byte[] block = new byte[blockSize];
 
             if (forEncryption)
             {
-                cipher.ProcessBlock(buf, 0, block, 0);
+                m_cipherMode.ProcessBlock(buf, 0, block, 0);
 
 				if (bufOff < blockSize)
-				{
 					throw new DataLengthException("need at least one block of input for CTS");
-				}
 
                 for (int i = bufOff; i != buf.Length; i++)
                 {
@@ -214,11 +258,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                     buf[i] ^= block[i - blockSize];
                 }
 
-				IBlockCipher c = (cipher is CbcBlockCipher)
-					?	((CbcBlockCipher)cipher).GetUnderlyingCipher()
-					:	cipher;
-
-				c.ProcessBlock(buf, blockSize, output, outOff);
+                m_cipherMode.UnderlyingCipher.ProcessBlock(buf, blockSize, output, outOff);
 
 				Array.Copy(block, 0, output, outOff + blockSize, length);
             }
@@ -226,11 +266,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 byte[] lastBlock = new byte[blockSize];
 
-				IBlockCipher c = (cipher is CbcBlockCipher)
-					?	((CbcBlockCipher)cipher).GetUnderlyingCipher()
-					:	cipher;
-
-				c.ProcessBlock(buf, 0, block, 0);
+                m_cipherMode.UnderlyingCipher.ProcessBlock(buf, 0, block, 0);
 
 				for (int i = blockSize; i != bufOff; i++)
                 {
@@ -239,7 +275,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 Array.Copy(buf, blockSize, block, 0, length);
 
-                cipher.ProcessBlock(block, 0, output, outOff);
+                m_cipherMode.ProcessBlock(block, 0, output, outOff);
                 Array.Copy(lastBlock, 0, output, outOff + blockSize, length);
             }
 
@@ -249,5 +285,65 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             return offset;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            if (bufOff > output.Length)
+                throw new DataLengthException("output buffer too small in DoFinal");
+
+            int blockSize = m_cipherMode.GetBlockSize();
+            int length = bufOff - blockSize;
+            Span<byte> block = blockSize <= 64
+                ? stackalloc byte[blockSize]
+                : new byte[blockSize];
+
+            if (forEncryption)
+            {
+                m_cipherMode.ProcessBlock(buf, block);
+
+                if (bufOff < blockSize)
+                    throw new DataLengthException("need at least one block of input for CTS");
+
+                for (int i = bufOff; i != buf.Length; i++)
+                {
+                    buf[i] = block[i - blockSize];
+                }
+
+                for (int i = blockSize; i != bufOff; i++)
+                {
+                    buf[i] ^= block[i - blockSize];
+                }
+
+                m_cipherMode.UnderlyingCipher.ProcessBlock(buf.AsSpan(blockSize), output);
+
+                block[..length].CopyTo(output[blockSize..]);
+            }
+            else
+            {
+                Span<byte> lastBlock = blockSize <= 64
+                    ? stackalloc byte[blockSize]
+                    : new byte[blockSize];
+
+                m_cipherMode.UnderlyingCipher.ProcessBlock(buf, block);
+
+                for (int i = blockSize; i != bufOff; i++)
+                {
+                    lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]);
+                }
+
+                buf.AsSpan(blockSize, length).CopyTo(block);
+
+                m_cipherMode.ProcessBlock(block, output);
+                lastBlock[..length].CopyTo(output[blockSize..]);
+            }
+
+            int offset = bufOff;
+
+            Reset();
+
+            return offset;
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/modes/EAXBlockCipher.cs b/crypto/src/crypto/modes/EAXBlockCipher.cs
index 624f385b5..770c432e2 100644
--- a/crypto/src/crypto/modes/EAXBlockCipher.cs
+++ b/crypto/src/crypto/modes/EAXBlockCipher.cs
@@ -59,47 +59,35 @@ namespace Org.BouncyCastle.Crypto.Modes
 			this.cipher = new SicBlockCipher(cipher);
 		}
 
-		public virtual string AlgorithmName
-		{
-			get { return cipher.GetUnderlyingCipher().AlgorithmName + "/EAX"; }
-		}
+		public virtual string AlgorithmName => cipher.UnderlyingCipher.AlgorithmName + "/EAX";
 
-		public virtual IBlockCipher GetUnderlyingCipher()
-		{
-			return cipher;
-		}
+		public virtual IBlockCipher UnderlyingCipher => cipher;
 
 		public virtual int GetBlockSize()
 		{
 			return cipher.GetBlockSize();
 		}
 
-		public virtual void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+		public virtual void Init(bool forEncryption, ICipherParameters parameters)
 		{
 			this.forEncryption = forEncryption;
 
 			byte[] nonce;
 			ICipherParameters keyParam;
 
-			if (parameters is AeadParameters)
+			if (parameters is AeadParameters aeadParameters)
 			{
-				AeadParameters param = (AeadParameters) parameters;
-
-				nonce = param.GetNonce();
-                initialAssociatedText = param.GetAssociatedText();
-				macSize = param.MacSize / 8;
-				keyParam = param.Key;
+				nonce = aeadParameters.GetNonce();
+                initialAssociatedText = aeadParameters.GetAssociatedText();
+				macSize = aeadParameters.MacSize / 8;
+				keyParam = aeadParameters.Key;
 			}
-			else if (parameters is ParametersWithIV)
+			else if (parameters is ParametersWithIV parametersWithIV)
 			{
-				ParametersWithIV param = (ParametersWithIV) parameters;
-
-				nonce = param.GetIV();
+				nonce = parametersWithIV.GetIV();
                 initialAssociatedText = null;
 				macSize = mac.GetMacSize() / 2;
-				keyParam = param.Parameters;
+				keyParam = parametersWithIV.Parameters;
 			}
 			else
 			{
@@ -194,31 +182,48 @@ namespace Org.BouncyCastle.Crypto.Modes
         public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
         {
             if (cipherInitialized)
-            {
                 throw new InvalidOperationException("AAD data cannot be added after encryption/decryption processing has begun.");
-            }
+
             mac.BlockUpdate(inBytes, inOff, len);
         }
 
-        public virtual int ProcessByte(
-			byte	input,
-			byte[]	outBytes,
-			int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
 		{
-            InitCipher();
+			if (cipherInitialized)
+				throw new InvalidOperationException("AAD data cannot be added after encryption/decryption processing has begun.");
 
-            return Process(input, outBytes, outOff);
+			mac.BlockUpdate(input);
 		}
+#endif
 
-        public virtual int ProcessBytes(
-			byte[]	inBytes,
-			int		inOff,
-			int		len,
-			byte[]	outBytes,
-			int		outOff)
+        public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
 		{
             InitCipher();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return Process(input, Spans.FromNullable(outBytes, outOff));
+#else
+			return Process(input, outBytes, outOff);
+#endif
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            InitCipher();
+
+            return Process(input, output);
+        }
+#endif
+
+        public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
+        {
+            InitCipher();
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return ProcessBytes(inBytes.AsSpan(inOff, len), Spans.FromNullable(outBytes, outOff));
+#else
             int resultLen = 0;
 
 			for (int i = 0; i != len; i++)
@@ -227,14 +232,33 @@ namespace Org.BouncyCastle.Crypto.Modes
 			}
 
             return resultLen;
+#endif
 		}
 
-		public virtual int DoFinal(
-			byte[]	outBytes,
-			int		outOff)
-		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
             InitCipher();
 
+			int len = input.Length;
+            int resultLen = 0;
+
+            for (int i = 0; i != len; i++)
+            {
+                resultLen += Process(input[i], output[resultLen..]);
+            }
+
+            return resultLen;
+        }
+#endif
+
+        public virtual int DoFinal(byte[] outBytes, int outOff)
+		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return DoFinal(outBytes.AsSpan(outOff));
+#else
+			InitCipher();
+
             int extra = bufOff;
 			byte[] tmp = new byte[bufBlock.Length];
 
@@ -242,7 +266,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
 			if (forEncryption)
 			{
-                Check.OutputLength(outBytes, outOff, extra + macSize, "Output buffer too short");
+                Check.OutputLength(outBytes, outOff, extra + macSize, "output buffer too short");
 
                 cipher.ProcessBlock(bufBlock, 0, tmp, 0);
 
@@ -263,7 +287,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 if (extra < macSize)
                     throw new InvalidCipherTextException("data too short");
 
-                Check.OutputLength(outBytes, outOff, extra - macSize, "Output buffer too short");
+                Check.OutputLength(outBytes, outOff, extra - macSize, "output buffer too short");
 
                 if (extra > macSize)
 				{
@@ -283,9 +307,70 @@ namespace Org.BouncyCastle.Crypto.Modes
 
 				return extra - macSize;
 			}
+#endif
 		}
 
-		public virtual byte[] GetMac()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+		{
+            InitCipher();
+
+            int extra = bufOff;
+			int tmpLength = bufBlock.Length;
+
+            Span<byte> tmp = tmpLength <= 128
+				? stackalloc byte[tmpLength]
+				: new byte[tmpLength];
+
+            bufOff = 0;
+
+			if (forEncryption)
+			{
+                Check.OutputLength(output, extra + macSize, "output buffer too short");
+
+                cipher.ProcessBlock(bufBlock, tmp);
+
+				tmp[..extra].CopyTo(output);
+
+				mac.BlockUpdate(tmp[..extra]);
+
+				CalculateMac();
+
+				macBlock.AsSpan(0, macSize).CopyTo(output[extra..]);
+
+				Reset(false);
+
+				return extra + macSize;
+			}
+			else
+			{
+                if (extra < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                Check.OutputLength(output, extra - macSize, "output buffer too short");
+
+                if (extra > macSize)
+				{
+					mac.BlockUpdate(bufBlock.AsSpan(0, extra - macSize));
+
+					cipher.ProcessBlock(bufBlock, tmp);
+
+					tmp[..(extra - macSize)].CopyTo(output);
+				}
+
+				CalculateMac();
+
+				if (!VerifyMac(bufBlock, extra - macSize))
+					throw new InvalidCipherTextException("mac check in EAX failed");
+
+				Reset(false);
+
+				return extra - macSize;
+			}
+		}
+#endif
+
+        public virtual byte[] GetMac()
 		{
 			byte[] mac = new byte[macSize];
 
@@ -322,12 +407,49 @@ namespace Org.BouncyCastle.Crypto.Modes
             return totalData < macSize ? 0 : totalData - macSize;
         }
 
-		private int Process(
-			byte	b,
-			byte[]	outBytes,
-			int		outOff)
-		{
-			bufBlock[bufOff++] = b;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int Process(byte b, Span<byte> output)
+        {
+            bufBlock[bufOff++] = b;
+
+            if (bufOff == bufBlock.Length)
+            {
+                Check.OutputLength(output, blockSize, "output buffer too short");
+
+                // TODO Could move the ProcessByte(s) calls to here
+                //InitCipher();
+
+                int size;
+
+                if (forEncryption)
+                {
+                    size = cipher.ProcessBlock(bufBlock, output);
+
+					mac.BlockUpdate(output[..blockSize]);
+                }
+                else
+                {
+                    mac.BlockUpdate(bufBlock.AsSpan(0, blockSize));
+
+                    size = cipher.ProcessBlock(bufBlock, output);
+                }
+
+                bufOff = 0;
+                if (!forEncryption)
+                {
+                    Array.Copy(bufBlock, blockSize, bufBlock, 0, macSize);
+                    bufOff = macSize;
+                }
+
+                return size;
+            }
+
+            return 0;
+        }
+#else
+        private int Process(byte b, byte[] outBytes, int outOff)
+        {
+            bufBlock[bufOff++] = b;
 
 			if (bufOff == bufBlock.Length)
 			{
@@ -363,8 +485,9 @@ namespace Org.BouncyCastle.Crypto.Modes
 
 			return 0;
 		}
+#endif
 
-		private bool VerifyMac(byte[] mac, int off)
+        private bool VerifyMac(byte[] mac, int off)
 		{
             int nonEqual = 0;
 
diff --git a/crypto/src/crypto/modes/EcbBlockCipher.cs b/crypto/src/crypto/modes/EcbBlockCipher.cs
new file mode 100644
index 000000000..96f9811dd
--- /dev/null
+++ b/crypto/src/crypto/modes/EcbBlockCipher.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Modes
+{
+    public class EcbBlockCipher
+        : IBlockCipherMode
+    {
+        internal static IBlockCipherMode GetBlockCipherMode(IBlockCipher blockCipher)
+        {
+            if (blockCipher is IBlockCipherMode blockCipherMode)
+                return blockCipherMode;
+
+            return new EcbBlockCipher(blockCipher);
+        }
+
+        private readonly IBlockCipher m_cipher;
+
+        public EcbBlockCipher(IBlockCipher cipher)
+        {
+            if (cipher == null)
+                throw new ArgumentNullException(nameof(cipher));
+
+            m_cipher = cipher;
+        }
+
+        public bool IsPartialBlockOkay => false;
+
+        public string AlgorithmName => m_cipher.AlgorithmName + "/ECB";
+
+        public int GetBlockSize()
+        {
+            return m_cipher.GetBlockSize();
+        }
+
+        public IBlockCipher UnderlyingCipher => m_cipher;
+
+        public void Init(bool forEncryption, ICipherParameters parameters)
+        {
+            m_cipher.Init(forEncryption, parameters);
+        }
+
+        public int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            return m_cipher.ProcessBlock(inBuf, inOff, outBuf, outOff);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            return m_cipher.ProcessBlock(input, output);
+        }
+#endif
+
+        public void Reset()
+        {
+        }
+    }
+}
diff --git a/crypto/src/crypto/modes/GCMBlockCipher.cs b/crypto/src/crypto/modes/GCMBlockCipher.cs
index ac54e9762..ce5faf91f 100644
--- a/crypto/src/crypto/modes/GCMBlockCipher.cs
+++ b/crypto/src/crypto/modes/GCMBlockCipher.cs
@@ -1,6 +1,9 @@
 using System;
-#if NETCOREAPP3_0_OR_GREATER
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
 using System.Runtime.CompilerServices;
+#endif
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.InteropServices;
 using System.Runtime.Intrinsics;
 using System.Runtime.Intrinsics.X86;
 #endif
@@ -13,17 +16,16 @@ using Org.BouncyCastle.Utilities;
 namespace Org.BouncyCastle.Crypto.Modes
 {
     /// <summary>
-    /// Implements the Galois/Counter mode (GCM) detailed in
-    /// NIST Special Publication 800-38D.
+    /// Implements the Galois/Counter mode (GCM) detailed in NIST Special Publication 800-38D.
     /// </summary>
-    public class GcmBlockCipher
+    public sealed class GcmBlockCipher
         : IAeadBlockCipher
     {
         private static IGcmMultiplier CreateGcmMultiplier()
         {
 #if NETCOREAPP3_0_OR_GREATER
             // TODO Prefer more tightly coupled test
-            if (System.Runtime.Intrinsics.X86.Pclmulqdq.IsSupported)
+            if (Pclmulqdq.IsSupported)
             {
                 return new BasicGcmMultiplier();
             }
@@ -84,17 +86,11 @@ namespace Org.BouncyCastle.Crypto.Modes
             this.multiplier = m;
         }
 
-        public virtual string AlgorithmName
-        {
-            get { return cipher.AlgorithmName + "/GCM"; }
-        }
+        public string AlgorithmName => cipher.AlgorithmName + "/GCM";
 
-        public IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
 
-        public virtual int GetBlockSize()
+        public int GetBlockSize()
         {
             return BlockSize;
         }
@@ -103,16 +99,14 @@ namespace Org.BouncyCastle.Crypto.Modes
         /// MAC sizes from 32 bits to 128 bits (must be a multiple of 8) are supported. The default is 128 bits.
         /// Sizes less than 96 are not recommended, but are supported for specialized applications.
         /// </remarks>
-        public virtual void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
             this.forEncryption = forEncryption;
             this.macBlock = null;
             this.initialised = true;
 
             KeyParameter keyParam;
-            byte[] newNonce = null;
+            byte[] newNonce;
 
             if (parameters is AeadParameters)
             {
@@ -227,15 +221,14 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        public virtual byte[] GetMac()
+        public byte[] GetMac()
         {
             return macBlock == null
                 ?   new byte[macSize]
                 :   Arrays.Clone(macBlock);
         }
 
-        public virtual int GetOutputSize(
-            int len)
+        public int GetOutputSize(int len)
         {
             int totalData = len + bufOff;
 
@@ -247,8 +240,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             return totalData < macSize ? 0 : totalData - macSize;
         }
 
-        public virtual int GetUpdateOutputSize(
-            int len)
+        public int GetUpdateOutputSize(int len)
         {
             int totalData = len + bufOff;
             if (!forEncryption)
@@ -262,7 +254,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             return totalData - totalData % BlockSize;
         }
 
-        public virtual void ProcessAadByte(byte input)
+        public void ProcessAadByte(byte input)
         {
             CheckStatus();
 
@@ -276,8 +268,11 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
+        public void ProcessAadBytes(byte[] inBytes, int inOff, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ProcessAadBytes(inBytes.AsSpan(inOff, len));
+#else
             CheckStatus();
 
             if (atBlockPos > 0)
@@ -309,7 +304,42 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             atBlockPos = BlockSize + inLimit - inOff;
             Array.Copy(inBytes, inOff, atBlock, 0, atBlockPos);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            CheckStatus();
+
+            if (atBlockPos > 0)
+            {
+                int available = BlockSize - atBlockPos;
+                if (input.Length < available)
+                {
+                    input.CopyTo(atBlock.AsSpan(atBlockPos));
+                    atBlockPos += input.Length;
+                    return;
+                }
+
+                input[..available].CopyTo(atBlock.AsSpan(atBlockPos));
+                gHASHBlock(S_at, atBlock);
+                atLength += BlockSize;
+                input = input[available..];
+                //atBlockPos = 0;
+            }
+
+            while (input.Length >= BlockSize)
+            {
+                gHASHBlock(S_at, input);
+                atLength += BlockSize;
+                input = input[BlockSize..];
+            }
+
+            input.CopyTo(atBlock);
+            atBlockPos = input.Length;
         }
+#endif
 
         private void InitCipher()
         {
@@ -332,7 +362,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        public virtual int ProcessByte(byte	input, byte[] output, int outOff)
+        public int ProcessByte(byte	input, byte[] output, int outOff)
         {
             CheckStatus();
 
@@ -341,12 +371,20 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 if (forEncryption)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    EncryptBlock(bufBlock, output.AsSpan(outOff));
+#else
                     EncryptBlock(bufBlock, 0, output, outOff);
+#endif
                     bufOff = 0;
                 }
                 else
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    DecryptBlock(bufBlock, output.AsSpan(outOff));
+#else
                     DecryptBlock(bufBlock, 0, output, outOff);
+#endif
                     Array.Copy(bufBlock, BlockSize, bufBlock, 0, macSize);
                     bufOff = macSize;
                 }
@@ -355,12 +393,40 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
-        public virtual int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessByte(byte input, Span<byte> output)
+        {
+            CheckStatus();
+
+            bufBlock[bufOff] = input;
+            if (++bufOff == bufBlock.Length)
+            {
+                if (forEncryption)
+                {
+                    EncryptBlock(bufBlock, output);
+                    bufOff = 0;
+                }
+                else
+                {
+                    DecryptBlock(bufBlock, output);
+                    Array.Copy(bufBlock, BlockSize, bufBlock, 0, macSize);
+                    bufOff = macSize;
+                }
+                return BlockSize;
+            }
+            return 0;
+        }
+#endif
+
+        public int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
             CheckStatus();
 
             Check.DataLength(input, inOff, len, "input buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(input.AsSpan(inOff, len), Spans.FromNullable(output, outOff));
+#else
             int resultLen = 0;
 
             if (forEncryption)
@@ -457,10 +523,114 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             return resultLen;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            CheckStatus();
+
+            int resultLen = 0;
+
+            if (forEncryption)
+            {
+                if (bufOff > 0)
+                {
+                    int available = BlockSize - bufOff;
+                    if (input.Length < available)
+                    {
+                        input.CopyTo(bufBlock.AsSpan(bufOff));
+                        bufOff += input.Length;
+                        return 0;
+                    }
+
+                    input[..available].CopyTo(bufBlock.AsSpan(bufOff));
+                    EncryptBlock(bufBlock, output);
+                    input = input[available..];
+                    resultLen = BlockSize;
+                    //bufOff = 0;
+                }
+
+                while (input.Length >= BlockSize * 2)
+                {
+                    EncryptBlocks2(input, output[resultLen..]);
+                    input = input[(BlockSize * 2)..];
+                    resultLen += BlockSize * 2;
+                }
+
+                if (input.Length >= BlockSize)
+                {
+                    EncryptBlock(input, output[resultLen..]);
+                    input = input[BlockSize..];
+                    resultLen += BlockSize;
+                }
+
+                bufOff = input.Length;
+                input.CopyTo(bufBlock);
+            }
+            else
+            {
+                int available = bufBlock.Length - bufOff;
+                if (input.Length < available)
+                {
+                    input.CopyTo(bufBlock.AsSpan(bufOff));
+                    bufOff += input.Length;
+                    return 0;
+                }
+
+                if (bufOff >= BlockSize)
+                {
+                    DecryptBlock(bufBlock, output);
+                    Array.Copy(bufBlock, BlockSize, bufBlock, 0, bufOff -= BlockSize);
+                    resultLen = BlockSize;
+
+                    available += BlockSize;
+                    if (input.Length < available)
+                    {
+                        input.CopyTo(bufBlock.AsSpan(bufOff));
+                        bufOff += input.Length;
+                        return resultLen;
+                    }
+                }
+
+                int inLimit1 = bufBlock.Length;
+                int inLimit2 = inLimit1 + BlockSize;
+
+                available = BlockSize - bufOff;
+                input[..available].CopyTo(bufBlock.AsSpan(bufOff));
+                DecryptBlock(bufBlock, output[resultLen..]);
+                input = input[available..];
+                resultLen += BlockSize;
+                //bufOff = 0;
+
+                while (input.Length >= inLimit2)
+                {
+                    DecryptBlocks2(input, output[resultLen..]);
+                    input = input[(BlockSize * 2)..];
+                    resultLen += BlockSize * 2;
+                }
+
+                if (input.Length >= inLimit1)
+                {
+                    DecryptBlock(input, output[resultLen..]);
+                    input = input[BlockSize..];
+                    resultLen += BlockSize;
+                }
+
+                bufOff = input.Length;
+                input.CopyTo(bufBlock);
+            }
+
+            return resultLen;
+        }
+#endif
+
         public int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             CheckStatus();
 
             if (totalLength == 0)
@@ -472,7 +642,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             if (forEncryption)
             {
-                Check.OutputLength(output, outOff, extra + macSize, "Output buffer too short");
+                Check.OutputLength(output, outOff, extra + macSize, "output buffer too short");
             }
             else
             {
@@ -481,7 +651,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 extra -= macSize;
 
-                Check.OutputLength(output, outOff, extra, "Output buffer too short");
+                Check.OutputLength(output, outOff, extra, "output buffer too short");
             }
 
             if (extra > 0)
@@ -567,18 +737,128 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset(false);
 
             return resultLen;
+#endif
         }
 
-        public virtual void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
         {
-            Reset(true);
+            CheckStatus();
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            int extra = bufOff;
+
+            if (forEncryption)
+            {
+                Check.OutputLength(output, extra + macSize, "output buffer too short");
+            }
+            else
+            {
+                if (extra < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                extra -= macSize;
+
+                Check.OutputLength(output, extra, "output buffer too short");
+            }
+
+            if (extra > 0)
+            {
+                ProcessPartial(bufBlock.AsSpan(0, extra), output);
+            }
+
+            atLength += (uint)atBlockPos;
+
+            if (atLength > atLengthPre)
+            {
+                /*
+                 *  Some AAD was sent after the cipher started. We determine the difference b/w the hash value
+                 *  we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at).
+                 *  Then we carry this difference forward by multiplying by H^c, where c is the number of (full or
+                 *  partial) cipher-text blocks produced, and adjust the current hash.
+                 */
+
+                // Finish hash for partial AAD block
+                if (atBlockPos > 0)
+                {
+                    gHASHPartial(S_at, atBlock, 0, atBlockPos);
+                }
+
+                // Find the difference between the AAD hashes
+                if (atLengthPre > 0)
+                {
+                    GcmUtilities.Xor(S_at, S_atPre);
+                }
+
+                // Number of cipher-text blocks produced
+                long c = (long)(((totalLength * 8) + 127) >> 7);
+
+                // Calculate the adjustment factor
+                byte[] H_c = new byte[16];
+                if (exp == null)
+                {
+                    exp = new BasicGcmExponentiator();
+                    exp.Init(H);
+                }
+                exp.ExponentiateX(c, H_c);
+
+                // Carry the difference forward
+                GcmUtilities.Multiply(S_at, H_c);
+
+                // Adjust the current hash
+                GcmUtilities.Xor(S, S_at);
+            }
+
+            // Final gHASH
+            Span<byte> X = stackalloc byte[BlockSize];
+            Pack.UInt64_To_BE(atLength * 8UL, X);
+            Pack.UInt64_To_BE(totalLength * 8UL, X[8..]);
+
+            gHASHBlock(S, X);
+
+            // T = MSBt(GCTRk(J0,S))
+            Span<byte> tag = stackalloc byte[BlockSize];
+            cipher.ProcessBlock(J0, tag);
+            GcmUtilities.Xor(tag, S);
+
+            int resultLen = extra;
+
+            // We place into macBlock our calculated value for T
+            this.macBlock = new byte[macSize];
+            tag[..macSize].CopyTo(macBlock);
+
+            if (forEncryption)
+            {
+                // Append T to the message
+                macBlock.CopyTo(output[bufOff..]);
+                resultLen += macSize;
+            }
+            else
+            {
+                // Retrieve the T value from the message and compare to calculated one
+                Span<byte> msgMac = stackalloc byte[macSize];
+                bufBlock.AsSpan(extra, macSize).CopyTo(msgMac);
+                if (!Arrays.ConstantTimeAreEqual(this.macBlock, msgMac))
+                    throw new InvalidCipherTextException("mac check in GCM failed");
+            }
+
+            Reset(false);
+
+            return resultLen;
         }
+#endif
 
-        private void Reset(
-            bool clearMac)
+        public void Reset()
         {
-            cipher.Reset();
+            Reset(true);
+        }
 
+        private void Reset(bool clearMac)
+        {
             // note: we do not reset the nonce.
 
             S = new byte[BlockSize];
@@ -617,50 +897,51 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        private void DecryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBuf, outOff, BlockSize, "Output buffer too short");
+            Check.OutputLength(output, BlockSize, "output buffer too short");
 
             if (totalLength == 0)
             {
                 InitCipher();
             }
 
-            byte[] ctrBlock = new byte[BlockSize];
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
-                var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
-                var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
+                var t0 = MemoryMarshal.Read<Vector128<byte>>(input);
+                var t1 = MemoryMarshal.Read<Vector128<byte>>(ctrBlock);
+                var t2 = MemoryMarshal.Read<Vector128<byte>>(S.AsSpan());
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t0);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
-                Unsafe.WriteUnaligned(ref S[0], t2);
+                MemoryMarshal.Write(output, ref t1);
+                MemoryMarshal.Write(S.AsSpan(), ref t2);
             }
             else
 #endif
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
-                    byte c0 = inBuf[inOff + i + 0];
-                    byte c1 = inBuf[inOff + i + 1];
-                    byte c2 = inBuf[inOff + i + 2];
-                    byte c3 = inBuf[inOff + i + 3];
+                    byte c0 = input[i + 0];
+                    byte c1 = input[i + 1];
+                    byte c2 = input[i + 2];
+                    byte c3 = input[i + 3];
 
                     S[i + 0] ^= c0;
                     S[i + 1] ^= c1;
                     S[i + 2] ^= c2;
                     S[i + 3] ^= c3;
 
-                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
-                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
-                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
-                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                    output[i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    output[i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    output[i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    output[i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
                 }
             }
             multiplier.MultiplyH(S);
@@ -668,36 +949,287 @@ namespace Org.BouncyCastle.Crypto.Modes
             totalLength += BlockSize;
         }
 
-        private void DecryptBlocks2(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        private void DecryptBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBuf, outOff, BlockSize * 2, "Output buffer too short");
+            Check.OutputLength(output, BlockSize * 2, "output buffer too short");
 
             if (totalLength == 0)
             {
                 InitCipher();
             }
 
-            byte[] ctrBlock = new byte[BlockSize];
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
-                var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
-                var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
+                var t0 = MemoryMarshal.Read<Vector128<byte>>(input);
+                var t1 = MemoryMarshal.Read<Vector128<byte>>(ctrBlock);
+                var t2 = MemoryMarshal.Read<Vector128<byte>>(S.AsSpan());
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t0);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
-                Unsafe.WriteUnaligned(ref S[0], t2);
+                MemoryMarshal.Write(output, ref t1);
+                MemoryMarshal.Write(S.AsSpan(), ref t2);
             }
             else
 #endif
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
+                    byte c0 = input[i + 0];
+                    byte c1 = input[i + 1];
+                    byte c2 = input[i + 2];
+                    byte c3 = input[i + 3];
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    output[i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    output[i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    output[i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    output[i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            input = input[BlockSize..];
+            output = output[BlockSize..];
+
+            GetNextCtrBlock(ctrBlock);
+#if NETCOREAPP3_0_OR_GREATER
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
+            {
+                var t0 = MemoryMarshal.Read<Vector128<byte>>(input);
+                var t1 = MemoryMarshal.Read<Vector128<byte>>(ctrBlock);
+                var t2 = MemoryMarshal.Read<Vector128<byte>>(S.AsSpan());
+
+                t1 = Sse2.Xor(t1, t0);
+                t2 = Sse2.Xor(t2, t0);
+
+                MemoryMarshal.Write(output, ref t1);
+                MemoryMarshal.Write(S.AsSpan(), ref t2);
+            }
+            else
+#endif
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = input[i + 0];
+                    byte c1 = input[i + 1];
+                    byte c2 = input[i + 2];
+                    byte c3 = input[i + 3];
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    output[i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    output[i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    output[i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    output[i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize * 2;
+        }
+
+        private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, BlockSize, "output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+#if NETCOREAPP3_0_OR_GREATER
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
+            {
+                var t0 = MemoryMarshal.Read<Vector128<byte>>(input);
+                var t1 = MemoryMarshal.Read<Vector128<byte>>(ctrBlock);
+                var t2 = MemoryMarshal.Read<Vector128<byte>>(S.AsSpan());
+
+                t1 = Sse2.Xor(t1, t0);
+                t2 = Sse2.Xor(t2, t1);
+
+                MemoryMarshal.Write(output, ref t1);
+                MemoryMarshal.Write(S.AsSpan(), ref t2);
+            }
+            else
+#endif
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ input[i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ input[i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ input[i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ input[i + 3]);
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    output[i + 0] = c0;
+                    output[i + 1] = c1;
+                    output[i + 2] = c2;
+                    output[i + 3] = c3;
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize;
+        }
+
+        private void EncryptBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, BlockSize * 2, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+#if NETCOREAPP3_0_OR_GREATER
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
+            {
+                var t0 = MemoryMarshal.Read<Vector128<byte>>(input);
+                var t1 = MemoryMarshal.Read<Vector128<byte>>(ctrBlock);
+                var t2 = MemoryMarshal.Read<Vector128<byte>>(S.AsSpan());
+
+                t1 = Sse2.Xor(t1, t0);
+                t2 = Sse2.Xor(t2, t1);
+
+                MemoryMarshal.Write(output, ref t1);
+                MemoryMarshal.Write(S.AsSpan(), ref t2);
+            }
+            else
+#endif
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ input[i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ input[i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ input[i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ input[i + 3]);
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    output[i + 0] = c0;
+                    output[i + 1] = c1;
+                    output[i + 2] = c2;
+                    output[i + 3] = c3;
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            input = input[BlockSize..];
+            output = output[BlockSize..];
+
+            GetNextCtrBlock(ctrBlock);
+#if NETCOREAPP3_0_OR_GREATER
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
+            {
+                var t0 = MemoryMarshal.Read<Vector128<byte>>(input);
+                var t1 = MemoryMarshal.Read<Vector128<byte>>(ctrBlock);
+                var t2 = MemoryMarshal.Read<Vector128<byte>>(S.AsSpan());
+
+                t1 = Sse2.Xor(t1, t0);
+                t2 = Sse2.Xor(t2, t1);
+
+                MemoryMarshal.Write(output, ref t1);
+                MemoryMarshal.Write(S.AsSpan(), ref t2);
+            }
+            else
+#endif
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ input[i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ input[i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ input[i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ input[i + 3]);
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    output[i + 0] = c0;
+                    output[i + 1] = c1;
+                    output[i + 2] = c2;
+                    output[i + 3] = c3;
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize * 2;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void GetNextCtrBlock(Span<byte> block)
+        {
+            if (blocksRemaining == 0)
+                throw new InvalidOperationException("Attempt to process too many blocks");
+
+            blocksRemaining--;
+
+            Pack.UInt32_To_BE(++counter32, counter, 12);
+
+            cipher.ProcessBlock(counter, block);
+        }
+
+        private void ProcessPartial(Span<byte> partialBlock, Span<byte> output)
+        {
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
+            GetNextCtrBlock(ctrBlock);
+
+            if (forEncryption)
+            {
+                GcmUtilities.Xor(partialBlock, ctrBlock, partialBlock.Length);
+                gHASHPartial(S, partialBlock);
+            }
+            else
+            {
+                gHASHPartial(S, partialBlock);
+                GcmUtilities.Xor(partialBlock, ctrBlock, partialBlock.Length);
+            }
+
+            partialBlock.CopyTo(output);
+            totalLength += (uint)partialBlock.Length;
+        }
+#else
+        private void DecryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.OutputLength(outBuf, outOff, BlockSize, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            byte[] ctrBlock = new byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
                     byte c0 = inBuf[inOff + i + 0];
                     byte c1 = inBuf[inOff + i + 1];
                     byte c2 = inBuf[inOff + i + 2];
@@ -716,25 +1248,46 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
             multiplier.MultiplyH(S);
 
-            inOff += BlockSize;
-            outOff += BlockSize;
+            totalLength += BlockSize;
+        }
+
+        private void DecryptBlocks2(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.OutputLength(outBuf, outOff, BlockSize * 2, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            byte[] ctrBlock = new byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
-#if NETCOREAPP3_0_OR_GREATER
-            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
-                var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
-                var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = inBuf[inOff + i + 0];
+                    byte c1 = inBuf[inOff + i + 1];
+                    byte c2 = inBuf[inOff + i + 2];
+                    byte c3 = inBuf[inOff + i + 3];
 
-                t1 = Sse2.Xor(t1, t0);
-                t2 = Sse2.Xor(t2, t0);
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
-                Unsafe.WriteUnaligned(ref S[0], t2);
+                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                }
             }
-            else
-#endif
+            multiplier.MultiplyH(S);
+
+            inOff += BlockSize;
+            outOff += BlockSize;
+
+            GetNextCtrBlock(ctrBlock);
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
@@ -771,21 +1324,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             byte[] ctrBlock = new byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
-#if NETCOREAPP3_0_OR_GREATER
-            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
-            {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
-                var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
-                var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
-
-                t1 = Sse2.Xor(t1, t0);
-                t2 = Sse2.Xor(t2, t1);
-
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
-                Unsafe.WriteUnaligned(ref S[0], t2);
-            }
-            else
-#endif
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
@@ -822,21 +1360,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             byte[] ctrBlock = new byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
-#if NETCOREAPP3_0_OR_GREATER
-            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
-            {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
-                var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
-                var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
-
-                t1 = Sse2.Xor(t1, t0);
-                t2 = Sse2.Xor(t2, t1);
-
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
-                Unsafe.WriteUnaligned(ref S[0], t2);
-            }
-            else
-#endif
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
@@ -862,21 +1385,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             outOff += BlockSize;
 
             GetNextCtrBlock(ctrBlock);
-#if NETCOREAPP3_0_OR_GREATER
-            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
-            {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
-                var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
-                var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
-
-                t1 = Sse2.Xor(t1, t0);
-                t2 = Sse2.Xor(t2, t1);
-
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
-                Unsafe.WriteUnaligned(ref S[0], t2);
-            }
-            else
-#endif
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
@@ -901,6 +1409,18 @@ namespace Org.BouncyCastle.Crypto.Modes
             totalLength += BlockSize * 2;
         }
 
+        private void GetNextCtrBlock(byte[] block)
+        {
+            if (blocksRemaining == 0)
+                throw new InvalidOperationException("Attempt to process too many blocks");
+
+            blocksRemaining--;
+
+            Pack.UInt32_To_BE(++counter32, counter, 12);
+
+            cipher.ProcessBlock(counter, 0, block, 0);
+        }
+
         private void ProcessPartial(byte[] buf, int off, int len, byte[] output, int outOff)
         {
             byte[] ctrBlock = new byte[BlockSize];
@@ -920,6 +1440,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             Array.Copy(buf, off, output, outOff, len);
             totalLength += (uint)len;
         }
+#endif
 
         private void gHASH(byte[] Y, byte[] b, int len)
         {
@@ -930,6 +1451,21 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void gHASHBlock(byte[] Y, ReadOnlySpan<byte> b)
+        {
+            GcmUtilities.Xor(Y, b);
+            multiplier.MultiplyH(Y);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void gHASHPartial(byte[] Y, ReadOnlySpan<byte> b)
+        {
+            GcmUtilities.Xor(Y, b, b.Length);
+            multiplier.MultiplyH(Y);
+        }
+#else
         private void gHASHBlock(byte[] Y, byte[] b)
         {
             GcmUtilities.Xor(Y, b);
@@ -941,6 +1477,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             GcmUtilities.Xor(Y, b, off);
             multiplier.MultiplyH(Y);
         }
+#endif
 
         private void gHASHPartial(byte[] Y, byte[] b, int off, int len)
         {
@@ -948,18 +1485,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             multiplier.MultiplyH(Y);
         }
 
-        private void GetNextCtrBlock(byte[] block)
-        {
-            if (blocksRemaining == 0)
-                throw new InvalidOperationException("Attempt to process too many blocks");
-
-            blocksRemaining--;
-
-            Pack.UInt32_To_BE(++counter32, counter, 12);
-
-            cipher.ProcessBlock(counter, 0, block, 0);
-        }
-
         private void CheckStatus()
         {
             if (!initialised)
diff --git a/crypto/src/crypto/modes/GOFBBlockCipher.cs b/crypto/src/crypto/modes/GOFBBlockCipher.cs
index 436b58a1d..cfcbe4fcd 100644
--- a/crypto/src/crypto/modes/GOFBBlockCipher.cs
+++ b/crypto/src/crypto/modes/GOFBBlockCipher.cs
@@ -1,7 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Modes
 {
@@ -9,8 +9,8 @@ namespace Org.BouncyCastle.Crypto.Modes
 	* implements the GOST 28147 OFB counter mode (GCTR).
 	*/
 	public class GOfbBlockCipher
-		: IBlockCipher
-	{
+		: IBlockCipherMode
+    {
 		private byte[]	IV;
 		private byte[]	ofbV;
 		private byte[]	ofbOutV;
@@ -46,15 +46,12 @@ namespace Org.BouncyCastle.Crypto.Modes
 			this.ofbOutV = new byte[cipher.GetBlockSize()];
 		}
 
-		/**
+        /**
 		* return the underlying block cipher that we are wrapping.
 		*
 		* @return the underlying block cipher that we are wrapping.
 		*/
-		public IBlockCipher GetUnderlyingCipher()
-		{
-			return cipher;
-		}
+        public IBlockCipher UnderlyingCipher => cipher;
 
 		/**
 		* Initialise the cipher and, possibly, the initialisation vector (IV).
@@ -131,41 +128,17 @@ namespace Org.BouncyCastle.Crypto.Modes
 			return blockSize;
 		}
 
-		/**
-		* Process one block of input from the array in and write it to
-		* the out array.
-		*
-		* @param in the array containing the input data.
-		* @param inOff offset into the in array the data starts at.
-		* @param out the array the output data will be copied into.
-		* @param outOff the offset into the out array the output will start at.
-		* @exception DataLengthException if there isn't enough data in in, or
-		* space in out.
-		* @exception InvalidOperationException if the cipher isn't initialised.
-		* @return the number of bytes processed and produced.
-		*/
-		public int ProcessBlock(
-			byte[]	input,
-			int		inOff,
-			byte[]	output,
-			int		outOff)
+		public int ProcessBlock(byte[] input, int inOff, byte[]	output, int outOff)
 		{
-			if ((inOff + blockSize) > input.Length)
-			{
-				throw new DataLengthException("input buffer too short");
-			}
-
-			if ((outOff + blockSize) > output.Length)
-			{
-				throw new DataLengthException("output buffer too short");
-			}
+			Check.DataLength(input, inOff, blockSize, "input buffer too short");
+			Check.OutputLength(output, outOff, blockSize, "output buffer too short");
 
 			if (firstStep)
 			{
 				firstStep = false;
 				cipher.ProcessBlock(ofbV, 0, ofbOutV, 0);
-				N3 = bytesToint(ofbOutV, 0);
-				N4 = bytesToint(ofbOutV, 4);
+				N3 = (int)Pack.LE_To_UInt32(ofbOutV, 0);
+				N4 = (int)Pack.LE_To_UInt32(ofbOutV, 4);
 			}
 			N3 += C2;
 			N4 += C1;
@@ -176,8 +149,8 @@ namespace Org.BouncyCastle.Crypto.Modes
                     N4++;
                 }
             }
-            intTobytes(N3, ofbV, 0);
-			intTobytes(N4, ofbV, 4);
+			Pack.UInt32_To_LE((uint)N3, ofbV, 0);
+			Pack.UInt32_To_LE((uint)N4, ofbV, 4);
 
 			cipher.ProcessBlock(ofbV, 0, ofbOutV, 0);
 
@@ -199,6 +172,52 @@ namespace Org.BouncyCastle.Crypto.Modes
 			return blockSize;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			Check.DataLength(input, blockSize, "input buffer too short");
+			Check.OutputLength(output, blockSize, "output buffer too short");
+
+			if (firstStep)
+			{
+				firstStep = false;
+				cipher.ProcessBlock(ofbV, ofbOutV);
+				N3 = (int)Pack.LE_To_UInt32(ofbOutV, 0);
+				N4 = (int)Pack.LE_To_UInt32(ofbOutV, 4);
+			}
+			N3 += C2;
+			N4 += C1;
+			if (N4 < C1)  // addition is mod (2**32 - 1)
+			{
+				if (N4 > 0)
+				{
+					N4++;
+				}
+			}
+			Pack.UInt32_To_LE((uint)N3, ofbV, 0);
+			Pack.UInt32_To_LE((uint)N4, ofbV, 4);
+
+			cipher.ProcessBlock(ofbV, ofbOutV);
+
+			//
+			// XOR the ofbV with the plaintext producing the cipher text (and
+			// the next input block).
+			//
+			for (int i = 0; i < blockSize; i++)
+			{
+				output[i] = (byte)(ofbOutV[i] ^ input[i]);
+			}
+
+			//
+			// change over the input block.
+			//
+			Array.Copy(ofbV, blockSize, ofbV, 0, ofbV.Length - blockSize);
+			Array.Copy(ofbOutV, 0, ofbV, ofbV.Length - blockSize, blockSize);
+
+			return blockSize;
+		}
+#endif
+
 		/**
 		* reset the feedback vector back to the IV and reset the underlying
 		* cipher.
@@ -206,29 +225,6 @@ namespace Org.BouncyCastle.Crypto.Modes
 		public void Reset()
 		{
 			Array.Copy(IV, 0, ofbV, 0, IV.Length);
-
-			cipher.Reset();
-		}
-
-		//array of bytes to type int
-		private int bytesToint(
-			byte[]  inBytes,
-			int     inOff)
-		{
-			return  (int)((inBytes[inOff + 3] << 24) & 0xff000000) + ((inBytes[inOff + 2] << 16) & 0xff0000) +
-					((inBytes[inOff + 1] << 8) & 0xff00) + (inBytes[inOff] & 0xff);
-		}
-
-		//int to array of bytes
-		private void intTobytes(
-				int     num,
-				byte[]  outBytes,
-				int     outOff)
-		{
-				outBytes[outOff + 3] = (byte)(num >> 24);
-				outBytes[outOff + 2] = (byte)(num >> 16);
-				outBytes[outOff + 1] = (byte)(num >> 8);
-				outBytes[outOff] =     (byte)num;
 		}
 	}
 }
diff --git a/crypto/src/crypto/modes/GcmSivBlockCipher.cs b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
index 5af3429f2..14f908da3 100644
--- a/crypto/src/crypto/modes/GcmSivBlockCipher.cs
+++ b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
@@ -1,7 +1,6 @@
 using System;
 using System.IO;
 
-using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Modes.Gcm;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
@@ -156,10 +155,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             theDataHasher = new GcmSivHasher(this);
         }
 
-        public virtual IBlockCipher GetUnderlyingCipher()
-        {
-            return theCipher;
-        }
+        public virtual IBlockCipher UnderlyingCipher => theCipher;
 
         public virtual int GetBlockSize()
         {
@@ -170,8 +166,8 @@ namespace Org.BouncyCastle.Crypto.Modes
         {
             /* Set defaults */
             byte[] myInitialAEAD = null;
-            byte[] myNonce = null;
-            KeyParameter myKey = null;
+            byte[] myNonce;
+            KeyParameter myKey;
 
             /* Access parameters */
             if (cipherParameters is AeadParameters)
@@ -206,8 +202,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             byte[] k = myKey.GetKey();
 
-            if (k.Length != BUFLEN
-            && k.Length != (BUFLEN << 1))
+            if (k.Length != BUFLEN && k.Length != (BUFLEN << 1))
             {
                 throw new ArgumentException("Invalid key");
             }
@@ -218,14 +213,11 @@ namespace Org.BouncyCastle.Crypto.Modes
             theNonce = myNonce;
 
             /* Initialise the keys */
-            deriveKeys(myKey);
+            DeriveKeys(myKey);
             ResetStreams();
         }
 
-        public virtual string AlgorithmName
-        {
-            get { return theCipher.AlgorithmName + "-GCM-SIV"; }
-        }
+        public virtual string AlgorithmName => theCipher.AlgorithmName + "-GCM-SIV";
 
         /**
         * check AEAD status.
@@ -291,20 +283,34 @@ namespace Org.BouncyCastle.Crypto.Modes
             CheckAeadStatus(1);
 
             /* Process the aead */
-            theAEADHasher.updateHash(pByte);
+            theAEADHasher.UpdateHash(pByte);
         }
 
         public virtual void ProcessAadBytes(byte[] pData, int pOffset, int pLen)
         {
+            Check.DataLength(pData, pOffset, pLen, "input buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ProcessAadBytes(pData.AsSpan(pOffset, pLen));
+#else
             /* Check that we can supply AEAD */
             CheckAeadStatus(pLen);
 
-            /* Check input buffer */
-            CheckBuffer(pData, pOffset, pLen, false);
+            /* Process the aead */
+            theAEADHasher.UpdateHash(pData, pOffset, pLen);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            /* Check that we can supply AEAD */
+            CheckAeadStatus(input.Length);
 
             /* Process the aead */
-            theAEADHasher.updateHash(pData, pOffset, pLen);
+            theAEADHasher.UpdateHash(input);
         }
+#endif
 
         public virtual int ProcessByte(byte pByte, byte[] pOutput, int pOutOffset)
         {
@@ -315,7 +321,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             if (forEncryption)
             {
                 thePlain.WriteByte(pByte);
-                theDataHasher.updateHash(pByte);
+                theDataHasher.UpdateHash(pByte);
             }
             else
             {
@@ -326,19 +332,43 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            /* Check that we have initialised */
+            CheckStatus(1);
+
+            /* Store the data */
+            if (forEncryption)
+            {
+                thePlain.WriteByte(input);
+                theDataHasher.UpdateHash(input);
+            }
+            else
+            {
+                theEncData.WriteByte(input);
+            }
+
+            /* No data returned */
+            return 0;
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] pData, int pOffset, int pLen, byte[] pOutput, int pOutOffset)
         {
+            Check.DataLength(pData, pOffset, pLen, "input buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(pData.AsSpan(pOffset, pLen), Spans.FromNullable(pOutput, pOutOffset));
+#else
             /* Check that we have initialised */
             CheckStatus(pLen);
 
-            /* Check input buffer */
-            CheckBuffer(pData, pOffset, pLen, false);
-
             /* Store the data */
             if (forEncryption)
             {
                 thePlain.Write(pData, pOffset, pLen);
-                theDataHasher.updateHash(pData, pOffset, pLen);
+                theDataHasher.UpdateHash(pData, pOffset, pLen);
             }
             else
             {
@@ -347,24 +377,49 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             /* No data returned */
             return 0;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            /* Check that we have initialised */
+            CheckStatus(input.Length);
+
+            /* Store the data */
+            if (forEncryption)
+            {
+                thePlain.Write(input);
+                theDataHasher.UpdateHash(input);
+            }
+            else
+            {
+                theEncData.Write(input);
+            }
+
+            /* No data returned */
+            return 0;
+        }
+#endif
+
         public virtual int DoFinal(byte[] pOutput, int pOffset)
         {
+            Check.OutputLength(pOutput, pOffset, GetOutputSize(0), "output buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(pOutput.AsSpan(pOffset));
+#else
             /* Check that we have initialised */
             CheckStatus(0);
 
-            /* Check output buffer */
-            CheckBuffer(pOutput, pOffset, GetOutputSize(0), true);
-
             /* If we are encrypting */
             if (forEncryption)
             {
                 /* Derive the tag */
-                byte[] myTag = calculateTag();
+                byte[] myTag = CalculateTag();
 
                 /* encrypt the plain text */
-                int myDataLen = BUFLEN + encryptPlain(myTag, pOutput, pOffset);
+                int myDataLen = BUFLEN + EncryptPlain(myTag, pOutput, pOffset);
 
                 /* Add the tag to the output */
                 Array.Copy(myTag, 0, pOutput, pOffset + Convert.ToInt32(thePlain.Length), BUFLEN);
@@ -378,7 +433,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             else
             {
                 /* decrypt to plain text */
-                decryptPlain();
+                DecryptPlain();
 
                 /* Release plain text */
                 int myDataLen = Streams.WriteBufTo(thePlain, pOutput, pOffset);
@@ -387,7 +442,53 @@ namespace Org.BouncyCastle.Crypto.Modes
                 ResetStreams();
                 return myDataLen;
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            /* Check that we have initialised */
+            CheckStatus(0);
+
+            Check.OutputLength(output, GetOutputSize(0), "output buffer too short");
+
+            /* If we are encrypting */
+            if (forEncryption)
+            {
+                /* Derive the tag */
+                byte[] myTag = CalculateTag();
+
+                /* encrypt the plain text */
+                int myDataLen = BUFLEN + EncryptPlain(myTag, output);
+
+                /* Add the tag to the output */
+                myTag.AsSpan(0, BUFLEN).CopyTo(output[Convert.ToInt32(thePlain.Length)..]);
+
+                /* Reset the streams */
+                ResetStreams();
+                return myDataLen;
+
+                /* else we are decrypting */
+            }
+            else
+            {
+                /* decrypt to plain text */
+                DecryptPlain();
+
+                /* Release plain text */
+                if (!thePlain.TryGetBuffer(out var buffer))
+                    throw new InvalidOperationException();
+
+                buffer.AsSpan().CopyTo(output);
+                int myDataLen = buffer.Count;
+
+                /* Reset the streams */
+                ResetStreams();
+                return myDataLen;
+            }
         }
+#endif
 
         public virtual byte[] GetMac()
         {
@@ -440,7 +541,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             Arrays.Fill(theGHash, (byte)0);
             if (theInitialAEAD != null)
             {
-                theAEADHasher.updateHash(theInitialAEAD, 0, theInitialAEAD.Length);
+                theAEADHasher.UpdateHash(theInitialAEAD, 0, theInitialAEAD.Length);
             }
         }
 
@@ -454,37 +555,43 @@ namespace Org.BouncyCastle.Crypto.Modes
             return pBuffer == null ? 0 : pBuffer.Length;
         }
 
-        /**
-        * Check buffer.
-        * @param pBuffer the buffer
-        * @param pOffset the offset
-        * @param pLen the length
-        * @param pOutput is this an output buffer?
-        */
-        private static void CheckBuffer(byte[] pBuffer, int pOffset, int pLen, bool pOutput)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptPlain(byte[] pCounter, Span<byte> target)
         {
-            /* Access lengths */
-            int myBufLen = bufLength(pBuffer);
-            int myLast = pOffset + pLen;
+            byte[] thePlainBuf = thePlain.GetBuffer();
+            int thePlainLen = Convert.ToInt32(thePlain.Length);
 
-            /* Check for negative values and buffer overflow */
-            bool badLen = pLen < 0 || pOffset < 0 || myLast < 0;
-            if (badLen || myLast > myBufLen)
+            byte[] mySrc = thePlainBuf;
+            byte[] myCounter = Arrays.Clone(pCounter);
+            myCounter[BUFLEN - 1] |= MASK;
+            byte[] myMask = new byte[BUFLEN];
+            long myRemaining = thePlainLen;
+            int myOff = 0;
+
+            /* While we have data to process */
+            while (myRemaining > 0)
             {
-                throw pOutput
-                ? new OutputLengthException("Output buffer too short.")
-                : new DataLengthException("Input buffer too short.");
+                /* Generate the next mask */
+                theCipher.ProcessBlock(myCounter, 0, myMask, 0);
+
+                /* Xor data into mask */
+                int myLen = (int)System.Math.Min(BUFLEN, myRemaining);
+                xorBlock(myMask, mySrc, myOff, myLen);
+
+                /* Copy encrypted data to output */
+                myMask.AsSpan(0, myLen).CopyTo(target[myOff..]);
+
+                /* Adjust counters */
+                myRemaining -= myLen;
+                myOff += myLen;
+                incrementCounter(myCounter);
             }
-        }
 
-        /**
-        * encrypt data stream.
-        * @param pCounter the counter
-        * @param pTarget the target buffer
-        * @param pOffset the target offset
-        * @return the length of data encrypted
-        */
-        private int encryptPlain(byte[] pCounter, byte[] pTarget, int pOffset)
+            /* Return the amount of data processed */
+            return thePlainLen;
+        }
+#else
+        private int EncryptPlain(byte[] pCounter, byte[] pTarget, int pOffset)
         {
             byte[] thePlainBuf = thePlain.GetBuffer();
             int thePlainLen = Convert.ToInt32(thePlain.Length);
@@ -518,12 +625,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             /* Return the amount of data processed */
             return thePlainLen;
         }
+#endif
 
-        /**
-        * decrypt data stream.
-        * @throws InvalidCipherTextException on data too short or mac check failed
-        */
-        private void decryptPlain()
+        private void DecryptPlain()
         {
             byte[] theEncDataBuf = theEncData.GetBuffer();
             int theEncDataLen = Convert.ToInt32(theEncData.Length);
@@ -556,7 +660,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 /* Write data to plain dataStream */
                 thePlain.Write(myMask, 0, myLen);
-                theDataHasher.updateHash(myMask, 0, myLen);
+                theDataHasher.UpdateHash(myMask, 0, myLen);
 
                 /* Adjust counters */
                 myRemaining -= myLen;
@@ -565,7 +669,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             /* Derive and check the tag */
-            byte[] myTag = calculateTag();
+            byte[] myTag = CalculateTag();
             if (!Arrays.ConstantTimeAreEqual(myTag, myExpected))
             {
                 Reset();
@@ -577,7 +681,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         * calculate tag.
         * @return the calculated tag
         */
-        private byte[] calculateTag()
+        private byte[] CalculateTag()
         {
             /* Complete the hash */
             theDataHasher.completeHash();
@@ -647,6 +751,18 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void fillReverse(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            /* Loop through the buffer */
+            for (int i = 0, j = BUFLEN - 1; i < input.Length; i++, j--)
+            {
+                /* Copy byte */
+                output[j] = input[i];
+            }
+        }
+#endif
+
         /**
         * xor a full block buffer.
         * @param pLeft the left operand and result
@@ -719,7 +835,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         * Derive Keys.
         * @param pKey the keyGeneration key
         */
-        private void deriveKeys(KeyParameter pKey)
+        private void DeriveKeys(KeyParameter pKey)
         {
             /* Create the buffers */
             byte[] myIn = new byte[BUFLEN];
@@ -836,10 +952,10 @@ namespace Org.BouncyCastle.Crypto.Modes
             * update hash.
             * @param pByte the byte
             */
-            internal void updateHash(byte pByte)
+            internal void UpdateHash(byte pByte)
             {
                 theByte[0] = pByte;
-                updateHash(theByte, 0, 1);
+                UpdateHash(theByte, 0, 1);
             }
 
             /**
@@ -848,7 +964,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             * @param pOffset the offset within the buffer
             * @param pLen the length of data
             */
-            internal void updateHash(byte[] pBuffer, int pOffset, int pLen)
+            internal void UpdateHash(byte[] pBuffer, int pOffset, int pLen)
             {
                 /* If we should process the cache */
                 int mySpace = BUFLEN - numActive;
@@ -875,8 +991,8 @@ namespace Org.BouncyCastle.Crypto.Modes
                     parent.gHASH(parent.theReverse);
 
                     /* Adjust counters */
-                    numProcessed += mySpace;
-                    myRemaining -= mySpace;
+                    numProcessed += BUFLEN;
+                    myRemaining -= BUFLEN;
                 }
 
                 /* If we have remaining data */
@@ -891,6 +1007,49 @@ namespace Org.BouncyCastle.Crypto.Modes
                 numHashed += (ulong)pLen;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            internal void UpdateHash(ReadOnlySpan<byte> buffer)
+            {
+                int pLen = buffer.Length;
+
+                /* If we should process the cache */
+                int mySpace = BUFLEN - numActive;
+                if (numActive > 0 && buffer.Length >= mySpace)
+                {
+                    /* Copy data into the cache and hash it */
+                    buffer[..mySpace].CopyTo(theBuffer.AsSpan(numActive));
+                    fillReverse(theBuffer, parent.theReverse);
+                    parent.gHASH(parent.theReverse);
+
+                    /* Adjust counters */
+                    buffer = buffer[mySpace..];
+                    numActive = 0;
+                }
+
+                /* While we have full blocks */
+                while (buffer.Length >= BUFLEN)
+                {
+                    /* Access the next data */
+                    fillReverse(buffer[..BUFLEN], parent.theReverse);
+                    parent.gHASH(parent.theReverse);
+
+                    /* Adjust counters */
+                    buffer = buffer[BUFLEN..];
+                }
+
+                /* If we have remaining data */
+                if (!buffer.IsEmpty)
+                {
+                    /* Copy data into the cache */
+                    buffer.CopyTo(theBuffer.AsSpan(numActive));
+                    numActive += buffer.Length;
+                }
+
+                /* Adjust the number of bytes processed */
+                numHashed += (ulong)pLen;
+            }
+#endif
+
             /**
             * complete hash.
             */
diff --git a/crypto/src/crypto/modes/IAeadBlockCipher.cs b/crypto/src/crypto/modes/IAeadBlockCipher.cs
index ebe5ef234..a4dc0857c 100644
--- a/crypto/src/crypto/modes/IAeadBlockCipher.cs
+++ b/crypto/src/crypto/modes/IAeadBlockCipher.cs
@@ -10,6 +10,6 @@ namespace Org.BouncyCastle.Crypto.Modes
         int GetBlockSize();
 
         /// <summary>The block cipher underlying this algorithm.</summary>
-		IBlockCipher GetUnderlyingCipher();
+		IBlockCipher UnderlyingCipher { get; }
 	}
 }
diff --git a/crypto/src/crypto/modes/IAeadCipher.cs b/crypto/src/crypto/modes/IAeadCipher.cs
index 437693cb6..5e92c78f3 100644
--- a/crypto/src/crypto/modes/IAeadCipher.cs
+++ b/crypto/src/crypto/modes/IAeadCipher.cs
@@ -41,6 +41,13 @@ namespace Org.BouncyCastle.Crypto.Modes
         /// <param name="len">The number of bytes to be processed.</param>
         void ProcessAadBytes(byte[] inBytes, int inOff, int len);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Add a span of bytes to the associated data check.</summary>
+        /// <remarks>If the implementation supports it, this will be an online operation and will not retain the associated data.</remarks>
+        /// <param name="input">the span containing the data.</param>
+        void ProcessAadBytes(ReadOnlySpan<byte> input);
+#endif
+
         /**
 		* Encrypt/decrypt a single byte.
 		*
@@ -52,6 +59,10 @@ namespace Org.BouncyCastle.Crypto.Modes
 		*/
         int ProcessByte(byte input, byte[] outBytes, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int ProcessByte(byte input, Span<byte> output);
+#endif
+
         /**
         * Process a block of bytes from in putting the result into out.
         *
@@ -65,6 +76,10 @@ namespace Org.BouncyCastle.Crypto.Modes
         */
         int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
+
         /**
         * Finish the operation either appending or verifying the MAC at the end of the data.
         *
@@ -76,6 +91,10 @@ namespace Org.BouncyCastle.Crypto.Modes
         */
         int DoFinal(byte[] outBytes, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int DoFinal(Span<byte> output);
+#endif
+
         /**
         * Return the value of the MAC associated with the last stream processed.
         *
diff --git a/crypto/src/crypto/modes/IBlockCipherMode.cs b/crypto/src/crypto/modes/IBlockCipherMode.cs
new file mode 100644
index 000000000..f6e3991c6
--- /dev/null
+++ b/crypto/src/crypto/modes/IBlockCipherMode.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Modes
+{
+    public interface IBlockCipherMode
+        : IBlockCipher
+    {
+        /// <summary>Return the <code cref="IBlockCipher"/> underlying this cipher mode.</summary>
+        IBlockCipher UnderlyingCipher { get; }
+
+        /// <summary>Indicates whether this cipher mode can handle partial blocks.</summary>
+        bool IsPartialBlockOkay { get; }
+
+        /// <summary>
+        /// Reset the cipher mode to the same state as it was after the last init (if there was one).
+        /// </summary>
+        void Reset();
+    }
+}
diff --git a/crypto/src/crypto/modes/KCcmBlockCipher.cs b/crypto/src/crypto/modes/KCcmBlockCipher.cs
index afa7063a3..5fb8d77c6 100644
--- a/crypto/src/crypto/modes/KCcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/KCcmBlockCipher.cs
@@ -7,7 +7,8 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Modes
 {
-    public class KCcmBlockCipher: IAeadBlockCipher
+    public class KCcmBlockCipher
+        : IAeadBlockCipher
     {
         private static readonly int BYTES_IN_INT = 4;
         private static readonly int BITS_IN_BYTE = 8;
@@ -130,23 +131,14 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        public virtual string AlgorithmName
-        {
-            get
-            {
-                return engine.AlgorithmName + "/KCCM";
-            }
-        }
+        public virtual string AlgorithmName => engine.AlgorithmName + "/KCCM";
 
         public virtual int GetBlockSize()
         {
             return engine.GetBlockSize();
         }
 
-        public virtual IBlockCipher GetUnderlyingCipher()
-        {
-            return engine;
-        }
+        public virtual IBlockCipher UnderlyingCipher => engine;
 
         public virtual void ProcessAadByte(byte input)
         {
@@ -158,6 +150,13 @@ namespace Org.BouncyCastle.Crypto.Modes
             associatedText.Write(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            associatedText.Write(input);
+        }
+#endif
+
         private void ProcessAAD(byte[] assocText, int assocOff, int assocLen, int dataLen)
         {
             if (assocLen - assocOff < engine.GetBlockSize())
@@ -227,6 +226,15 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            data.WriteByte(input);
+
+            return 0;
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] input, int inOff, int inLen, byte[] output, int outOff)
         {
             Check.DataLength(input, inOff, inLen, "input buffer too short");
@@ -236,11 +244,23 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            data.Write(input);
+
+            return 0;
+        }
+#endif
+
         public int ProcessPacket(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
             Check.DataLength(input, inOff, len, "input buffer too short");
             Check.OutputLength(output, outOff, len, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessPacket(input.AsSpan(inOff, len), output.AsSpan(outOff));
+#else
             if (associatedText.Length > 0)
             {
                 byte[] aad = associatedText.GetBuffer();
@@ -261,7 +281,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 int totalLength = len;
                 while (totalLength > 0)
                 {
-                    ProcessBlock(input, inOff, len, output, outOff);
+                    ProcessBlock(input, inOff, output, outOff);
                     totalLength -= engine.GetBlockSize();
                     inOff += engine.GetBlockSize();
                     outOff += engine.GetBlockSize();
@@ -295,7 +315,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 for (int blockNum = 0; blockNum<blocks; blockNum++)
                 {
-                    ProcessBlock(input, inOff, len, output, outOff);
+                    ProcessBlock(input, inOff, output, outOff);
 
                     inOff += engine.GetBlockSize();
                     outOff += engine.GetBlockSize();
@@ -343,43 +363,192 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 return len - macSize;
             }
+#endif
         }
 
-        private void ProcessBlock(byte[] input, int inOff, int len, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            int len = input.Length;
+            Check.OutputLength(output, len, "output buffer too short");
+
+            if (associatedText.Length > 0)
+            {
+                byte[] aad = associatedText.GetBuffer();
+                int aadLen = Convert.ToInt32(associatedText.Length);
+
+                int dataLen = Convert.ToInt32(data.Length) - (forEncryption ? 0 : macSize);
+
+                ProcessAAD(aad, 0, aadLen, dataLen);
+            }
+
+            int blockSize = engine.GetBlockSize(), index = 0;
+            if (forEncryption)
+            {
+                Check.DataLength(len % blockSize != 0, "partial blocks not supported");
+
+                CalculateMac(input);
+                engine.ProcessBlock(nonce, s);
 
+                int totalLength = len;
+                while (totalLength > 0)
+                {
+                    ProcessBlock(input[index..], output[index..]);
+                    totalLength -= blockSize;
+                    index += blockSize;
+                }
+
+                for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+                {
+                    s[byteIndex] += counter[byteIndex];
+                }
+
+                engine.ProcessBlock(s, buffer);
+
+                for (int byteIndex = 0; byteIndex < macSize; byteIndex++)
+                {
+                    output[index + byteIndex] = (byte)(buffer[byteIndex] ^ macBlock[byteIndex]);
+                }
+
+                Array.Copy(macBlock, 0, mac, 0, macSize);
+
+                Reset();
+
+                return len + macSize;
+            }
+            else
+            {
+                Check.DataLength((len - macSize) % blockSize != 0, "partial blocks not supported");
+
+                engine.ProcessBlock(nonce, 0, s, 0);
+
+                int blocks = len / engine.GetBlockSize();
+
+                for (int blockNum = 0; blockNum < blocks; blockNum++)
+                {
+                    ProcessBlock(input[index..], output[index..]);
+                    index += blockSize;
+                }
+
+                if (len > index)
+                {
+                    for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+                    {
+                        s[byteIndex] += counter[byteIndex];
+                    }
+
+                    engine.ProcessBlock(s, buffer);
+
+                    for (int byteIndex = 0; byteIndex < macSize; byteIndex++)
+                    {
+                        output[index + byteIndex] = (byte)(buffer[byteIndex] ^ input[index + byteIndex]);
+                    }
+                    index += macSize;
+                }
+
+                for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+                {
+                    s[byteIndex] += counter[byteIndex];
+                }
+
+                engine.ProcessBlock(s, buffer);
+
+                output[(index - macSize)..index].CopyTo(buffer);
+
+                CalculateMac(output[..(index - macSize)]);
+
+                Array.Copy(macBlock, 0, mac, 0, macSize);
+
+                Span<byte> calculatedMac = macSize <= 64
+                    ? stackalloc byte[macSize]
+                    : new byte[macSize];
+
+                calculatedMac.CopyFrom(buffer);
+
+                if (!Arrays.ConstantTimeAreEqual(mac.AsSpan(0, macSize), calculatedMac))
+                    throw new InvalidCipherTextException("mac check failed");
+
+                Reset();
+
+                return len - macSize;
+            }
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void CalculateMac(ReadOnlySpan<byte> authText)
+        {
+            int blockSize = engine.GetBlockSize();
+
+            while (!authText.IsEmpty)
+            {
+                for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
+                {
+                    macBlock[byteIndex] ^= authText[byteIndex];
+                }
+
+                engine.ProcessBlock(macBlock, macBlock);
+
+                authText = authText[blockSize..];
+            }
+        }
+
+        private void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
             for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
             {
                 s[byteIndex] += counter[byteIndex];
             }
 
-            engine.ProcessBlock(s, 0, buffer, 0);
+            engine.ProcessBlock(s, buffer);
 
-            for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+            int blockSize = engine.GetBlockSize();
+            for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
             {
-                output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+                output[byteIndex] = (byte)(buffer[byteIndex] ^ input[byteIndex]);
             }
         }
-
+#else
         private void CalculateMac(byte[] authText, int authOff, int len)
         {
+            int blockSize = engine.GetBlockSize();
             int totalLen = len;
             while (totalLen > 0)
             {
-                for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+                for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
                 {
                     macBlock[byteIndex] ^= authText[authOff + byteIndex];
                 }
 
                 engine.ProcessBlock(macBlock, 0, macBlock, 0);
 
-                totalLen -= engine.GetBlockSize();
-                authOff += engine.GetBlockSize();
+                totalLen -= blockSize;
+                authOff += blockSize;
             }
         }
 
+        private void ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
+        {
+
+            for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+            {
+                s[byteIndex] += counter[byteIndex];
+            }
+
+            engine.ProcessBlock(s, 0, buffer, 0);
+
+            for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+            {
+                output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+            }
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             byte[] buf = data.GetBuffer();
             int bufLen = Convert.ToInt32(data.Length);
 
@@ -388,7 +557,22 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset();
 
             return len;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            byte[] buf = data.GetBuffer();
+            int bufLen = Convert.ToInt32(data.Length);
+
+            int len = ProcessPacket(buf.AsSpan(0, bufLen), output);
+
+            Reset();
+
+            return len;
         }
+#endif
 
         public virtual byte[] GetMac()
         {
diff --git a/crypto/src/crypto/modes/KCtrBlockCipher.cs b/crypto/src/crypto/modes/KCtrBlockCipher.cs
index ff0249a6c..b0c4054e0 100644
--- a/crypto/src/crypto/modes/KCtrBlockCipher.cs
+++ b/crypto/src/crypto/modes/KCtrBlockCipher.cs
@@ -7,7 +7,8 @@ namespace Org.BouncyCastle.Crypto.Modes
     /**
     * Implements a Gamming or Counter (CTR) mode on top of a DSTU 7624 block cipher.
     */
-    public class KCtrBlockCipher : IStreamCipher, IBlockCipher
+    public class KCtrBlockCipher
+        : IStreamCipher, IBlockCipherMode
     {
         private byte[] IV;
         private byte[] ofbV;
@@ -40,10 +41,8 @@ namespace Org.BouncyCastle.Crypto.Modes
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
+
         /**
         * Initialise the cipher and, possibly, the initialisation vector (IV).
         * If an IV isn't passed as part of the parameter, the IV will be all zeros.
@@ -118,26 +117,31 @@ namespace Org.BouncyCastle.Crypto.Modes
 
         public void ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
-            if (outOff + len > output.Length)
-            {
-               throw new DataLengthException("Output buffer too short");
-            }
-
-            if (inOff + len > input.Length)
-            {
-                    throw new DataLengthException("Input buffer too small");
-            }
+            Check.DataLength(input, inOff, len, "input buffer too small");
+            Check.OutputLength(output, outOff, len, "output buffer too short");
 
             int inStart = inOff;
             int inEnd = inOff + len;
             int outStart = outOff;
 
-            while (inStart<inEnd)
+            while (inStart < inEnd)
             {
-                 output[outStart++] = CalculateByte(input[inStart++]);
+                output[outStart++] = CalculateByte(input[inStart++]);
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.OutputLength(output, input.Length, "output buffer too short");
+
+            for (int i = 0; i < input.Length; ++i)
+            {
+                output[i] = CalculateByte(input[i]);
+            }
+        }
+#endif
+
         protected byte CalculateByte(byte b)
         {
             if (byteCount == 0)
@@ -176,19 +180,27 @@ namespace Org.BouncyCastle.Crypto.Modes
          */
         public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-            if (input.Length - inOff< GetBlockSize())
-            {
-                throw new DataLengthException("Input buffer too short");
-            }
-            if (output.Length - outOff< GetBlockSize())
-            {
-                throw new DataLengthException("Output buffer too short");
-            }
+            int blockSize = GetBlockSize();
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(output, outOff, blockSize, "output buffer too short");
+
+            ProcessBytes(input, inOff, blockSize, output, outOff);
+
+            return blockSize;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int blockSize = GetBlockSize();
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
 
-            ProcessBytes(input, inOff, GetBlockSize(), output, outOff);
+            ProcessBytes(input[..blockSize], output);
 
-            return GetBlockSize();
+            return blockSize;
         }
+#endif
 
         /**
         * reset the chaining vector back to the IV and reset the underlying
@@ -200,7 +212,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 cipher.ProcessBlock(IV, 0, ofbV, 0);
             }
-            cipher.Reset();
             byteCount = 0;
         }
 
diff --git a/crypto/src/crypto/modes/OCBBlockCipher.cs b/crypto/src/crypto/modes/OCBBlockCipher.cs
index 28e88a6c9..e67b4e9af 100644
--- a/crypto/src/crypto/modes/OCBBlockCipher.cs
+++ b/crypto/src/crypto/modes/OCBBlockCipher.cs
@@ -6,19 +6,20 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Modes
 {
-    /**
-     * An implementation of <a href="http://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB
-     * Authenticated-Encryption Algorithm</a>, licensed per:
-     * 
-     * <blockquote><p><a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for
-     * Open-Source Software Implementations of OCB</a> (Jan 9, 2013) - 'License 1'<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
-     * software implementation of OCB claiming that you have a patent covering their implementation.
-     * </p><p>
-     * This is a non-binding summary of a legal document (the link above). The parameters of the license
-     * are specified in the license document and that document is controlling.</p></blockquote>
-     */
+    /// <summary>An implementation of <a href="https://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB
+    /// Authenticated-Encryption Algorithm</a>.</summary>
+    /// <remarks>
+    /// For those still concerned about the original patents around this, please see:
+    /// <para>https://mailarchive.ietf.org/arch/msg/cfrg/qLTveWOdTJcLn4HP3ev-vrj05Vg/</para>
+    /// Text reproduced below:
+    /// <para>
+    /// Phillip Rogaway&lt;rogaway@cs.ucdavis.edu&gt; Sat, 27 February 2021 02:46 UTC
+    ///
+    /// I can confirm that I have abandoned all OCB patents and placed into the public domain all OCB-related IP of
+    /// mine. While I have been telling people this for quite some time, I don't think I ever made a proper announcement
+    /// to the CFRG or on the OCB webpage. Consider that done.
+    /// </para>
+    /// </remarks>
     public class OcbBlockCipher
         : IAeadBlockCipher
     {
@@ -80,15 +81,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             this.mainCipher = mainCipher;
         }
 
-        public virtual IBlockCipher GetUnderlyingCipher()
-        {
-            return mainCipher;
-        }
+        public virtual string AlgorithmName => mainCipher.AlgorithmName + "/OCB";
 
-        public virtual string AlgorithmName
-        {
-            get { return mainCipher.AlgorithmName + "/OCB"; }
-        }
+        public virtual IBlockCipher UnderlyingCipher => mainCipher;
 
         public virtual void Init(bool forEncryption, ICipherParameters parameters)
         {
@@ -99,10 +94,8 @@ namespace Org.BouncyCastle.Crypto.Modes
             KeyParameter keyParameter;
 
             byte[] N;
-            if (parameters is AeadParameters)
+            if (parameters is AeadParameters aeadParameters)
             {
-                AeadParameters aeadParameters = (AeadParameters) parameters;
-
                 N = aeadParameters.GetNonce();
                 initialAssociatedText = aeadParameters.GetAssociatedText();
 
@@ -113,10 +106,8 @@ namespace Org.BouncyCastle.Crypto.Modes
                 macSize = macSizeBits / 8;
                 keyParameter = aeadParameters.Key;
             }
-            else if (parameters is ParametersWithIV)
+            else if (parameters is ParametersWithIV parametersWithIV)
             {
-                ParametersWithIV parametersWithIV = (ParametersWithIV) parameters;
-
                 N = parametersWithIV.GetIV();
                 initialAssociatedText = null;
                 macSize = 16;
@@ -287,6 +278,20 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            for (int i = 0; i < input.Length; ++i)
+            {
+                hashBlock[hashBlockPos] = input[i];
+                if (++hashBlockPos == hashBlock.Length)
+                {
+                    ProcessHashBlock();
+                }
+            }
+        }
+#endif
+
         public virtual int ProcessByte(byte input, byte[] output, int outOff)
         {
             mainBlock[mainBlockPos] = input;
@@ -298,8 +303,24 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            mainBlock[mainBlockPos] = input;
+            if (++mainBlockPos == mainBlock.Length)
+            {
+                ProcessMainBlock(output);
+                return BLOCK_SIZE;
+            }
+            return 0;
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(input.AsSpan(inOff, len), Spans.FromNullable(output, outOff));
+#else
             int resultLen = 0;
 
             for (int i = 0; i < len; ++i)
@@ -313,10 +334,34 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             return resultLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int len = input.Length;
+            int resultLen = 0;
+
+            for (int i = 0; i < len; ++i)
+            {
+                mainBlock[mainBlockPos] = input[i];
+                if (++mainBlockPos == mainBlock.Length)
+                {
+                    ProcessMainBlock(output[resultLen..]);
+                    resultLen += BLOCK_SIZE;
+                }
+            }
+
+            return resultLen;
         }
+#endif
 
         public virtual int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             /*
              * For decryption, get the tag from the end of the message
              */
@@ -357,7 +402,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 Xor(mainBlock, Pad);
 
-                Check.OutputLength(output, outOff, mainBlockPos, "Output buffer too short");
+                Check.OutputLength(output, outOff, mainBlockPos, "output buffer too short");
                 Array.Copy(mainBlock, 0, output, outOff, mainBlockPos);
 
                 if (!forEncryption)
@@ -385,7 +430,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             if (forEncryption)
             {
-                Check.OutputLength(output, outOff, resultLen + macSize, "Output buffer too short");
+                Check.OutputLength(output, outOff, resultLen + macSize, "output buffer too short");
 
                 // Append tag to the message
                 Array.Copy(macBlock, 0, output, outOff + resultLen, macSize);
@@ -401,8 +446,99 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset(false);
 
             return resultLen;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            /*
+             * For decryption, get the tag from the end of the message
+             */
+            byte[] tag = null;
+            if (!forEncryption)
+            {
+                if (mainBlockPos < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                mainBlockPos -= macSize;
+                tag = new byte[macSize];
+                Array.Copy(mainBlock, mainBlockPos, tag, 0, macSize);
+            }
+
+            /*
+             * HASH: Process any final partial block; compute final hash value
+             */
+            if (hashBlockPos > 0)
+            {
+                OCB_extend(hashBlock, hashBlockPos);
+                UpdateHASH(L_Asterisk);
+            }
+
+            /*
+             * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block
+             */
+            if (mainBlockPos > 0)
+            {
+                if (forEncryption)
+                {
+                    OCB_extend(mainBlock, mainBlockPos);
+                    Xor(Checksum, mainBlock);
+                }
+
+                Xor(OffsetMAIN, L_Asterisk);
+
+                byte[] Pad = new byte[16];
+                hashCipher.ProcessBlock(OffsetMAIN, 0, Pad, 0);
+
+                Xor(mainBlock, Pad);
+
+                Check.OutputLength(output, mainBlockPos, "output buffer too short");
+                mainBlock.AsSpan(0, mainBlockPos).CopyTo(output);
+
+                if (!forEncryption)
+                {
+                    OCB_extend(mainBlock, mainBlockPos);
+                    Xor(Checksum, mainBlock);
+                }
+            }
+
+            /*
+             * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag
+             */
+            Xor(Checksum, OffsetMAIN);
+            Xor(Checksum, L_Dollar);
+            hashCipher.ProcessBlock(Checksum, 0, Checksum, 0);
+            Xor(Checksum, Sum);
+
+            this.macBlock = new byte[macSize];
+            Array.Copy(Checksum, 0, macBlock, 0, macSize);
+
+            /*
+             * Validate or append tag and reset this cipher for the next run
+             */
+            int resultLen = mainBlockPos;
+
+            if (forEncryption)
+            {
+                // Append tag to the message
+                Check.OutputLength(output, resultLen + macSize, "output buffer too short");
+                macBlock.AsSpan(0, macSize).CopyTo(output[resultLen..]);
+                resultLen += macSize;
+            }
+            else
+            {
+                // Compare the tag from the message with the calculated one
+                if (!Arrays.ConstantTimeAreEqual(macBlock, tag))
+                    throw new InvalidCipherTextException("mac check in OCB failed");
+            }
+
+            Reset(false);
+
+            return resultLen;
+        }
+#endif
+
         public virtual void Reset()
         {
             Reset(true);
@@ -464,11 +600,40 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        protected virtual void Reset(bool clearMac)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected virtual void ProcessMainBlock(Span<byte> output)
         {
-            hashCipher.Reset();
-            mainCipher.Reset();
+            Check.DataLength(output, BLOCK_SIZE, "output buffer too short");
 
+            /*
+             * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks
+             */
+
+            if (forEncryption)
+            {
+                Xor(Checksum, mainBlock);
+                mainBlockPos = 0;
+            }
+
+            Xor(OffsetMAIN, GetLSub(OCB_ntz(++mainBlockCount)));
+
+            Xor(mainBlock, OffsetMAIN);
+            mainCipher.ProcessBlock(mainBlock, 0, mainBlock, 0);
+            Xor(mainBlock, OffsetMAIN);
+
+            mainBlock.AsSpan(0, BLOCK_SIZE).CopyTo(output);
+
+            if (!forEncryption)
+            {
+                Xor(Checksum, mainBlock);
+                Array.Copy(mainBlock, BLOCK_SIZE, mainBlock, 0, macSize);
+                mainBlockPos = macSize;
+            }
+        }
+#endif
+
+        protected virtual void Reset(bool clearMac)
+        {
             Clear(hashBlock);
             Clear(mainBlock);
 
diff --git a/crypto/src/crypto/modes/OfbBlockCipher.cs b/crypto/src/crypto/modes/OfbBlockCipher.cs
index a99f8c5d7..9bf4c25c7 100644
--- a/crypto/src/crypto/modes/OfbBlockCipher.cs
+++ b/crypto/src/crypto/modes/OfbBlockCipher.cs
@@ -8,7 +8,7 @@ namespace Org.BouncyCastle.Crypto.Modes
     * implements a Output-FeedBack (OFB) mode on top of a simple cipher.
     */
     public class OfbBlockCipher
-		: IBlockCipher
+		: IBlockCipherMode
     {
         private byte[]	IV;
         private byte[]	ofbV;
@@ -41,10 +41,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
 
         /**
         * Initialise the cipher and, possibly, the initialisation vector (IV).
@@ -61,9 +58,8 @@ namespace Org.BouncyCastle.Crypto.Modes
             bool				forEncryption, //ignored by this OFB mode
             ICipherParameters	parameters)
         {
-			if (parameters is ParametersWithIV)
+			if (parameters is ParametersWithIV ivParam)
             {
-                ParametersWithIV ivParam = (ParametersWithIV)parameters;
                 byte[] iv = ivParam.GetIV();
 
                 if (iv.Length < IV.Length)
@@ -118,36 +114,38 @@ namespace Org.BouncyCastle.Crypto.Modes
             return blockSize;
         }
 
-        /**
-        * Process one block of input from the array in and write it to
-        * the out array.
-        *
-        * @param in the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-            if ((inOff + blockSize) > input.Length)
-            {
-                throw new DataLengthException("input buffer too short");
-            }
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(output, outOff, blockSize, "output buffer too short");
 
-            if ((outOff + blockSize) > output.Length)
+            cipher.ProcessBlock(ofbV, 0, ofbOutV, 0);
+
+            //
+            // XOR the ofbV with the plaintext producing the cipher text (and
+            // the next input block).
+            //
+            for (int i = 0; i < blockSize; i++)
             {
-                throw new DataLengthException("output buffer too short");
+                output[outOff + i] = (byte)(ofbOutV[i] ^ input[inOff + i]);
             }
 
-            cipher.ProcessBlock(ofbV, 0, ofbOutV, 0);
+            //
+            // change over the input block.
+            //
+            Array.Copy(ofbV, blockSize, ofbV, 0, ofbV.Length - blockSize);
+            Array.Copy(ofbOutV, 0, ofbV, ofbV.Length - blockSize, blockSize);
+
+            return blockSize;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            cipher.ProcessBlock(ofbV, ofbOutV);
 
             //
             // XOR the ofbV with the plaintext producing the cipher text (and
@@ -155,7 +153,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             //
             for (int i = 0; i < blockSize; i++)
             {
-                output[outOff + i] = (byte)(ofbOutV[i] ^ input[inOff + i]);
+                output[i] = (byte)(ofbOutV[i] ^ input[i]);
             }
 
             //
@@ -166,6 +164,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             return blockSize;
         }
+#endif
 
         /**
         * reset the feedback vector back to the IV and reset the underlying
@@ -174,9 +173,6 @@ namespace Org.BouncyCastle.Crypto.Modes
         public void Reset()
         {
             Array.Copy(IV, 0, ofbV, 0, IV.Length);
-
-            cipher.Reset();
         }
     }
-
 }
diff --git a/crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs b/crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs
index 038ca783d..4e6e0ffaa 100644
--- a/crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs
+++ b/crypto/src/crypto/modes/OpenPgpCfbBlockCipher.cs
@@ -14,7 +14,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 	* </p>
     */
     public class OpenPgpCfbBlockCipher
-        : IBlockCipher
+        : IBlockCipherMode
     {
         private byte[] IV;
         private byte[] FR;
@@ -43,15 +43,12 @@ namespace Org.BouncyCastle.Crypto.Modes
             this.FRE = new byte[blockSize];
         }
 
-		/**
+        /**
         * return the underlying block cipher that we are wrapping.
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
 
 		/**
         * return the algorithm name and mode.
@@ -79,29 +76,29 @@ namespace Org.BouncyCastle.Crypto.Modes
             return cipher.GetBlockSize();
         }
 
-		/**
-        * Process one block of input from the array in and write it to
-        * the out array.
-        *
-        * @param in the array containing the input data.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the output data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        public int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
-            return (forEncryption) ? EncryptBlock(input, inOff, output, outOff) : DecryptBlock(input, inOff, output, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return forEncryption
+                ? EncryptBlock(input.AsSpan(inOff), output.AsSpan(outOff))
+                : DecryptBlock(input.AsSpan(inOff), output.AsSpan(outOff));
+#else
+            return forEncryption
+                ? EncryptBlock(input, inOff, output, outOff)
+                : DecryptBlock(input, inOff, output, outOff);
+#endif
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            return forEncryption
+                ? EncryptBlock(input, output)
+                : DecryptBlock(input, output);
+        }
+#endif
+
+        /**
         * reset the chaining vector back to the IV and reset the underlying
         * cipher.
         */
@@ -110,8 +107,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             count = 0;
 
 			Array.Copy(IV, 0, FR, 0, FR.Length);
-
-			cipher.Reset();
         }
 
         /**
@@ -125,15 +120,12 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception ArgumentException if the parameters argument is
         * inappropriate.
         */
-        public void Init(
-            bool forEncryption,
-            ICipherParameters parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
             this.forEncryption = forEncryption;
 
-            if (parameters is ParametersWithIV)
+            if (parameters is ParametersWithIV ivParam)
             {
-                ParametersWithIV ivParam = (ParametersWithIV)parameters;
                 byte[] iv = ivParam.GetIV();
 
                 if (iv.Length < IV.Length)
@@ -169,34 +161,132 @@ namespace Org.BouncyCastle.Crypto.Modes
             return (byte)(FRE[blockOff] ^ data);
         }
 
-		/**
-        * Do the appropriate processing for CFB IV mode encryption.
-        *
-        * @param in the array containing the data to be encrypted.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the encrypted data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        private int EncryptBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	outBytes,
-            int		outOff)
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            if (count > blockSize)
+            {
+                FR[blockSize - 2] = output[0] = EncryptByte(input[0], blockSize - 2);
+                FR[blockSize - 1] = output[1] = EncryptByte(input[1], blockSize - 1);
+
+                cipher.ProcessBlock(FR, FRE);
+
+                for (int n = 2; n < blockSize; n++)
+                {
+					FR[n - 2] = output[n] = EncryptByte(input[n], n - 2);
+                }
+            }
+            else if (count == 0)
+            {
+                cipher.ProcessBlock(FR, FRE);
+
+				for (int n = 0; n < blockSize; n++)
+                {
+					FR[n] = output[n] = EncryptByte(input[n], n);
+                }
+
+				count += blockSize;
+            }
+            else if (count == blockSize)
+            {
+                cipher.ProcessBlock(FR, FRE);
+
+                output[0] = EncryptByte(input[0], 0);
+                output[1] = EncryptByte(input[1], 1);
+
+                //
+                // do reset
+                //
+                Array.Copy(FR, 2, FR, 0, blockSize - 2);
+                output[..2].CopyTo(FR.AsSpan(blockSize - 2));
+
+                cipher.ProcessBlock(FR, FRE);
+
+                for (int n = 2; n < blockSize; n++)
+                {
+					FR[n - 2] = output[n] = EncryptByte(input[n], n - 2);
+                }
+
+				count += blockSize;
+            }
+
+            return blockSize;
+        }
+
+        private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            if ((inOff + blockSize) > input.Length)
+            Check.DataLength(input, blockSize, "input buffer too short");
+            Check.OutputLength(output, blockSize, "output buffer too short");
+
+            if (count > blockSize)
             {
-                throw new DataLengthException("input buffer too short");
+				byte inVal = input[0];
+				FR[blockSize - 2] = inVal;
+				output[0] = EncryptByte(inVal, blockSize - 2);
+
+				inVal = input[1];
+				FR[blockSize - 1] = inVal;
+				output[1] = EncryptByte(inVal, blockSize - 1);
+
+                cipher.ProcessBlock(FR, FRE);
+
+                for (int n = 2; n < blockSize; n++)
+                {
+					inVal = input[n];
+					FR[n - 2] = inVal;
+					output[n] = EncryptByte(inVal, n - 2);
+				}
             }
+            else if (count == 0)
+            {
+                cipher.ProcessBlock(FR, FRE);
 
-            if ((outOff + blockSize) > outBytes.Length)
+                for (int n = 0; n < blockSize; n++)
+                {
+                    FR[n] = input[n];
+                    output[n] = EncryptByte(input[n], n);
+                }
+
+                count += blockSize;
+            }
+            else if (count == blockSize)
             {
-                throw new DataLengthException("output buffer too short");
+                cipher.ProcessBlock(FR, 0, FRE, 0);
+
+				byte inVal1 = input[0];
+				byte inVal2 = input[1];
+				output[0] = EncryptByte(inVal1, 0);
+				output[1] = EncryptByte(inVal2, 1);
+
+                Array.Copy(FR, 2, FR, 0, blockSize - 2);
+
+				FR[blockSize - 2] = inVal1;
+				FR[blockSize - 1] = inVal2;
+
+                cipher.ProcessBlock(FR, 0, FRE, 0);
+
+                for (int n = 2; n < blockSize; n++)
+                {
+					byte inVal = input[n];
+					FR[n - 2] = inVal;
+					output[n] = EncryptByte(inVal, n - 2);
+                }
+
+                count += blockSize;
             }
 
+            return blockSize;
+        }
+#else
+        private int EncryptBlock(byte[]	input, int inOff, byte[] outBytes, int outOff)
+        {
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
+
             if (count > blockSize)
             {
                 FR[blockSize - 2] = outBytes[outOff] = EncryptByte(input[inOff], blockSize - 2);
@@ -246,33 +336,10 @@ namespace Org.BouncyCastle.Crypto.Modes
             return blockSize;
         }
 
-        /**
-        * Do the appropriate processing for CFB IV mode decryption.
-        *
-        * @param in the array containing the data to be decrypted.
-        * @param inOff offset into the in array the data starts at.
-        * @param out the array the encrypted data will be copied into.
-        * @param outOff the offset into the out array the output will start at.
-        * @exception DataLengthException if there isn't enough data in in, or
-        * space in out.
-        * @exception InvalidOperationException if the cipher isn't initialised.
-        * @return the number of bytes processed and produced.
-        */
-        private int DecryptBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	outBytes,
-            int		outOff)
+        private int DecryptBlock(byte[] input, int inOff, byte[] outBytes, int outOff)
         {
-            if ((inOff + blockSize) > input.Length)
-            {
-                throw new DataLengthException("input buffer too short");
-            }
-
-            if ((outOff + blockSize) > outBytes.Length)
-            {
-                throw new DataLengthException("output buffer too short");
-            }
+            Check.DataLength(input, inOff, blockSize, "input buffer too short");
+            Check.OutputLength(outBytes, outOff, blockSize, "output buffer too short");
 
             if (count > blockSize)
             {
@@ -300,7 +367,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 for (int n = 0; n < blockSize; n++)
                 {
                     FR[n] = input[inOff + n];
-                    outBytes[n] = EncryptByte(input[inOff + n], n);
+                    outBytes[outOff + n] = EncryptByte(input[inOff + n], n);
                 }
 
                 count += blockSize;
@@ -333,5 +400,6 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             return blockSize;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/modes/SicBlockCipher.cs b/crypto/src/crypto/modes/SicBlockCipher.cs
index 0bea4a455..fee8bb028 100644
--- a/crypto/src/crypto/modes/SicBlockCipher.cs
+++ b/crypto/src/crypto/modes/SicBlockCipher.cs
@@ -12,7 +12,7 @@ namespace Org.BouncyCastle.Crypto.Modes
     * block cipher.
     */
     public class SicBlockCipher
-        : IBlockCipher
+        : IBlockCipherMode
     {
         private readonly IBlockCipher cipher;
         private readonly int blockSize;
@@ -39,10 +39,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         *
         * @return the underlying block cipher that we are wrapping.
         */
-        public virtual IBlockCipher GetUnderlyingCipher()
-        {
-            return cipher;
-        }
+        public IBlockCipher UnderlyingCipher => cipher;
 
         public virtual void Init(
             bool				forEncryption, //ignored by this CTR mode
@@ -85,11 +82,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             return cipher.GetBlockSize();
         }
 
-        public virtual int ProcessBlock(
-            byte[]	input,
-            int		inOff,
-            byte[]	output,
-            int		outOff)
+        public virtual int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
         {
             cipher.ProcessBlock(counter, 0, counterOut, 0);
 
@@ -110,11 +103,33 @@ namespace Org.BouncyCastle.Crypto.Modes
             return counter.Length;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            cipher.ProcessBlock(counter, 0, counterOut, 0);
+
+            //
+            // XOR the counterOut with the plaintext producing the cipher text
+            //
+            for (int i = 0; i < counterOut.Length; i++)
+            {
+                output[i] = (byte)(counterOut[i] ^ input[i]);
+            }
+
+            // Increment the counter
+            int j = counter.Length;
+            while (--j >= 0 && ++counter[j] == 0)
+            {
+            }
+
+            return counter.Length;
+        }
+#endif
+
         public virtual void Reset()
         {
             Arrays.Fill(counter, (byte)0);
             Array.Copy(IV, 0, counter, 0, IV.Length);
-            cipher.Reset();
         }
     }
 }
diff --git a/crypto/src/crypto/modes/gcm/GcmUtilities.cs b/crypto/src/crypto/modes/gcm/GcmUtilities.cs
index 4528e172a..97b34fb61 100644
--- a/crypto/src/crypto/modes/gcm/GcmUtilities.cs
+++ b/crypto/src/crypto/modes/gcm/GcmUtilities.cs
@@ -16,12 +16,6 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
 {
     internal abstract class GcmUtilities
     {
-#if NETCOREAPP3_0_OR_GREATER
-        private static readonly Vector128<byte> EndianMask = Vector128.Create(
-            (byte)0x07, (byte)0x06, (byte)0x05, (byte)0x04, (byte)0x03, (byte)0x02, (byte)0x01, (byte)0x00,
-            (byte)0x0F, (byte)0x0E, (byte)0x0D, (byte)0x0C, (byte)0x0B, (byte)0x0A, (byte)0x09, (byte)0x08);
-#endif
-
         internal struct FieldElement
         {
             internal ulong n0, n1;
@@ -41,16 +35,6 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
 #endif
         internal static void AsBytes(ulong x0, ulong x1, byte[] z)
         {
-#if NETCOREAPP3_0_OR_GREATER
-            if (Ssse3.IsSupported && BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
-            {
-                var X = Vector128.Create(x0, x1).AsByte();
-                var Z = Ssse3.Shuffle(X, EndianMask);
-                Unsafe.WriteUnaligned(ref z[0], Z);
-                return;
-            }
-#endif
-
             Pack.UInt64_To_BE(x0, z, 0);
             Pack.UInt64_To_BE(x1, z, 8);
         }
@@ -68,17 +52,6 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
 #endif
         internal static void AsFieldElement(byte[] x, out FieldElement z)
         {
-#if NETCOREAPP3_0_OR_GREATER
-            if (Ssse3.IsSupported && BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
-            {
-                var X = Unsafe.ReadUnaligned<Vector128<byte>>(ref x[0]);
-                var Z = Ssse3.Shuffle(X, EndianMask).AsUInt64();
-                z.n0 = Z.GetElement(0);
-                z.n1 = Z.GetElement(1);
-                return;
-            }
-#endif
-
             z.n0 = Pack.BE_To_UInt64(x, 0);
             z.n1 = Pack.BE_To_UInt64(x, 8);
         }
@@ -274,6 +247,29 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
             z.n1 = x.n1 ^ y.n1;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void Xor(Span<byte> x, ReadOnlySpan<byte> y)
+        {
+            int i = 0;
+            do
+            {
+                x[i] ^= y[i]; ++i;
+                x[i] ^= y[i]; ++i;
+                x[i] ^= y[i]; ++i;
+                x[i] ^= y[i]; ++i;
+            }
+            while (i < 16);
+        }
+
+        internal static void Xor(Span<byte> x, ReadOnlySpan<byte> y, int len)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                x[i] ^= y[i];
+            }
+        }
+#endif
+
         private static ulong ImplMul64(ulong x, ulong y)
         {
             ulong x0 = x & 0x1111111111111111UL;
diff --git a/crypto/src/crypto/operators/Asn1CipherBuilder.cs b/crypto/src/crypto/operators/Asn1CipherBuilder.cs
index 0561bee4f..b151dfcc2 100644
--- a/crypto/src/crypto/operators/Asn1CipherBuilder.cs
+++ b/crypto/src/crypto/operators/Asn1CipherBuilder.cs
@@ -18,10 +18,7 @@ namespace Org.BouncyCastle.Crypto.Operators
 
         public Asn1CipherBuilderWithKey(DerObjectIdentifier encryptionOID, int keySize, SecureRandom random)
         {
-            if (random == null)
-            {
-                random = new SecureRandom();
-            }
+            random = CryptoServicesRegistrar.GetSecureRandom(random);
 
             CipherKeyGenerator keyGen = CipherKeyGeneratorFactory.CreateKeyGenerator(encryptionOID, random);
 
diff --git a/crypto/src/crypto/operators/Asn1DigestFactory.cs b/crypto/src/crypto/operators/Asn1DigestFactory.cs
index 16bd33fdf..0c1b6fb41 100644
--- a/crypto/src/crypto/operators/Asn1DigestFactory.cs
+++ b/crypto/src/crypto/operators/Asn1DigestFactory.cs
@@ -8,7 +8,8 @@ using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Crypto.Operators
 {
-    public class Asn1DigestFactory : IDigestFactory
+    public class Asn1DigestFactory
+        : IDigestFactory
     {
         public static Asn1DigestFactory Get(DerObjectIdentifier oid)
         {
@@ -40,13 +41,14 @@ namespace Org.BouncyCastle.Crypto.Operators
             get { return mDigest.GetDigestSize(); }
         }
 
-        public virtual IStreamCalculator CreateCalculator()
+        public virtual IStreamCalculator<IBlockResult> CreateCalculator()
         {
             return new DfDigestStream(mDigest);
         }
     }
 
-    internal class DfDigestStream : IStreamCalculator
+    internal class DfDigestStream
+        : IStreamCalculator<SimpleBlockResult>
     {
         private readonly DigestSink mStream;
 
@@ -60,7 +62,7 @@ namespace Org.BouncyCastle.Crypto.Operators
             get { return mStream; }
         }
 
-        public object GetResult()
+        public SimpleBlockResult GetResult()
         {
             byte[] result = new byte[mStream.Digest.GetDigestSize()];
             mStream.Digest.DoFinal(result, 0);
diff --git a/crypto/src/crypto/operators/Asn1Signature.cs b/crypto/src/crypto/operators/Asn1Signature.cs
index 674e717b1..db2d0759e 100644
--- a/crypto/src/crypto/operators/Asn1Signature.cs
+++ b/crypto/src/crypto/operators/Asn1Signature.cs
@@ -306,7 +306,7 @@ namespace Org.BouncyCastle.Crypto.Operators
 			get { return this.algID; }
 		}
 
-        public IStreamCalculator CreateCalculator()
+        public IStreamCalculator<IBlockResult> CreateCalculator()
         {
             ISigner signer = SignerUtilities.InitSigner(algorithm, true, privateKey, random);
 
@@ -363,9 +363,8 @@ namespace Org.BouncyCastle.Crypto.Operators
 			get { return this.algID; }
 		}
 
-        public IStreamCalculator CreateCalculator()
+        public IStreamCalculator<IVerifier> CreateCalculator()
         {       
-           
             ISigner verifier = SignerUtilities.InitSigner(X509Utilities.GetSignatureName(algID), false, publicKey, null);
 
             return new DefaultVerifierCalculator(verifier);
diff --git a/crypto/src/crypto/operators/DefaultSignatureCalculator.cs b/crypto/src/crypto/operators/DefaultSignatureCalculator.cs
index 8ca1c01d9..851662622 100644
--- a/crypto/src/crypto/operators/DefaultSignatureCalculator.cs
+++ b/crypto/src/crypto/operators/DefaultSignatureCalculator.cs
@@ -6,7 +6,7 @@ using Org.BouncyCastle.Crypto.IO;
 namespace Org.BouncyCastle.Crypto.Operators
 {
     public class DefaultSignatureCalculator
-        : IStreamCalculator
+        : IStreamCalculator<IBlockResult>
     {
         private readonly SignerSink mSignerSink;
 
@@ -20,7 +20,7 @@ namespace Org.BouncyCastle.Crypto.Operators
             get { return mSignerSink; }
         }
 
-        public object GetResult()
+        public IBlockResult GetResult()
         {
             return new DefaultSignatureResult(mSignerSink.Signer);
         }
diff --git a/crypto/src/crypto/operators/DefaultSignatureResult.cs b/crypto/src/crypto/operators/DefaultSignatureResult.cs
index 615f67dcb..a236838d6 100644
--- a/crypto/src/crypto/operators/DefaultSignatureResult.cs
+++ b/crypto/src/crypto/operators/DefaultSignatureResult.cs
@@ -23,5 +23,14 @@ namespace Org.BouncyCastle.Crypto.Operators
             signature.CopyTo(sig, sigOff);
             return signature.Length;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Collect(Span<byte> destination)
+        {
+            byte[] result = Collect();
+            result.CopyTo(destination);
+            return result.Length;
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/operators/DefaultVerifierCalculator.cs b/crypto/src/crypto/operators/DefaultVerifierCalculator.cs
index c985e81a5..cbf4e77d6 100644
--- a/crypto/src/crypto/operators/DefaultVerifierCalculator.cs
+++ b/crypto/src/crypto/operators/DefaultVerifierCalculator.cs
@@ -6,7 +6,7 @@ using Org.BouncyCastle.Crypto.IO;
 namespace Org.BouncyCastle.Crypto.Operators
 {
     public class DefaultVerifierCalculator
-        : IStreamCalculator
+        : IStreamCalculator<IVerifier>
     {
         private readonly SignerSink mSignerSink;
 
@@ -20,7 +20,7 @@ namespace Org.BouncyCastle.Crypto.Operators
             get { return mSignerSink; }
         }
 
-        public object GetResult()
+        public IVerifier GetResult()
         {
             return new DefaultVerifierResult(mSignerSink.Signer);
         }
diff --git a/crypto/src/crypto/paddings/BlockCipherPadding.cs b/crypto/src/crypto/paddings/BlockCipherPadding.cs
deleted file mode 100644
index 33a5f9f0f..000000000
--- a/crypto/src/crypto/paddings/BlockCipherPadding.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-
-
-namespace Org.BouncyCastle.Crypto.Paddings
-{
-    /**
-     * Block cipher padders are expected to conform to this interface
-     */
-    public interface IBlockCipherPadding
-    {
-        /**
-         * Initialise the padder.
-         *
-         * @param param parameters, if any required.
-         */
-        void Init(SecureRandom random);
-            //throws ArgumentException;
-
-        /**
-         * Return the name of the algorithm the cipher implements.
-         *
-         * @return the name of the algorithm the cipher implements.
-         */
-        string PaddingName { get; }
-
-		/**
-         * add the pad bytes to the passed in block, returning the
-         * number of bytes added.
-         */
-        int AddPadding(byte[] input, int inOff);
-
-        /**
-         * return the number of pad bytes present in the block.
-         * @exception InvalidCipherTextException if the padding is badly formed
-         * or invalid.
-         */
-        int PadCount(byte[] input);
-        //throws InvalidCipherTextException;
-    }
-
-}
diff --git a/crypto/src/crypto/paddings/IBlockCipherPadding.cs b/crypto/src/crypto/paddings/IBlockCipherPadding.cs
new file mode 100644
index 000000000..235b61886
--- /dev/null
+++ b/crypto/src/crypto/paddings/IBlockCipherPadding.cs
@@ -0,0 +1,43 @@
+using System;
+
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Crypto.Paddings
+{
+    /// <summary>Block cipher padders are expected to conform to this interface.</summary>
+    public interface IBlockCipherPadding
+    {
+        /// <summary>Initialise the padder.</summary>
+        /// <param name="random">A source of randomness, if any required.</param>
+        void Init(SecureRandom random);
+
+        /// <summary>The name of the algorithm this padder implements.</summary>
+        string PaddingName { get; }
+
+        /// <summary>Add padding to the passed in block.</summary>
+        /// <param name="input">the block to add padding to.</param>
+        /// <param name="inOff">the offset into the block the padding is to start at.</param>
+        /// <returns>the number of bytes of padding added.</returns>
+        int AddPadding(byte[] input, int inOff);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Add padding to the passed in block.</summary>
+        /// <param name="block">the block to add padding to.</param>
+        /// <param name="position">the offset into the block the padding is to start at.</param>
+        /// <returns>the number of bytes of padding added.</returns>
+        int AddPadding(Span<byte> block, int position);
+#endif
+
+        /// <summary>Determine the length of padding present in the passed in block.</summary>
+        /// <param name="input">the block to check padding for.</param>
+        /// <returns>the number of bytes of padding present.</returns>
+        int PadCount(byte[] input);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Determine the length of padding present in the passed in block.</summary>
+        /// <param name="block">the block to check padding for.</param>
+        /// <returns>the number of bytes of padding present.</returns>
+        int PadCount(ReadOnlySpan<byte> block);
+#endif
+    }
+}
diff --git a/crypto/src/crypto/paddings/ISO10126d2Padding.cs b/crypto/src/crypto/paddings/ISO10126d2Padding.cs
index e132a62dd..21e007f1b 100644
--- a/crypto/src/crypto/paddings/ISO10126d2Padding.cs
+++ b/crypto/src/crypto/paddings/ISO10126d2Padding.cs
@@ -1,7 +1,6 @@
 using System;
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
 
+using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Crypto.Paddings
 {
@@ -22,7 +21,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			SecureRandom random)
             //throws ArgumentException
         {
-			this.random = (random != null) ? random : new SecureRandom();
+            this.random = CryptoServicesRegistrar.GetSecureRandom(random);
         }
 
 		/**
@@ -35,42 +34,56 @@ namespace Org.BouncyCastle.Crypto.Paddings
             get { return "ISO10126-2"; }
         }
 
-		/**
-        * add the pad bytes to the passed in block, returning the
-        * number of bytes added.
-        */
-        public int AddPadding(
-            byte[]	input,
-            int		inOff)
+        public int AddPadding(byte[] input, int inOff)
         {
-            byte code = (byte)(input.Length - inOff);
-
-            while (inOff < (input.Length - 1))
+            int count = input.Length - inOff;
+            if (count > 1)
             {
-                input[inOff] = (byte)random.NextInt();
-                inOff++;
+                random.NextBytes(input, inOff, count - 1);
             }
+            input[input.Length - 1] = (byte)count;
+
+            return count;
+        }
 
-            input[inOff] = code;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int AddPadding(Span<byte> block, int position)
+        {
+            int count = block.Length - position;
+            if (count > 1)
+            {
+                random.NextBytes(block[position..(block.Length - 1)]);
+            }
+            block[block.Length - 1] = (byte)count;
 
-            return code;
+            return count;
         }
+#endif
 
-        /**
-        * return the number of pad bytes present in the block.
-        */
         public int PadCount(byte[] input)
-            //throws InvalidCipherTextException
         {
-            int count = input[input.Length - 1] & 0xff;
+            int count = input[input.Length -1];
+            int position = input.Length - count;
 
-            if (count > input.Length)
-            {
+            int failed = (position | (count - 1)) >> 31;
+            if (failed != 0)
                 throw new InvalidCipherTextException("pad block corrupted");
-            }
 
             return count;
         }
-    }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int PadCount(ReadOnlySpan<byte> block)
+        {
+            int count = block[block.Length - 1];
+            int position = block.Length - count;
+
+            int failed = (position | (count - 1)) >> 31;
+            if (failed != 0)
+                throw new InvalidCipherTextException("pad block corrupted");
+
+            return count;
+        }
+#endif
+    }
 }
diff --git a/crypto/src/crypto/paddings/ISO7816d4Padding.cs b/crypto/src/crypto/paddings/ISO7816d4Padding.cs
index 016b25a81..7b1834626 100644
--- a/crypto/src/crypto/paddings/ISO7816d4Padding.cs
+++ b/crypto/src/crypto/paddings/ISO7816d4Padding.cs
@@ -33,47 +33,65 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			get { return "ISO7816-4"; }
 		}
 
-		/**
-		 * add the pad bytes to the passed in block, returning the
-		 * number of bytes added.
-		 */
-		public int AddPadding(
-			byte[]	input,
-			int		inOff)
+		public int AddPadding(byte[] input, int inOff)
 		{
-			int added = (input.Length - inOff);
+			int count = input.Length - inOff;
 
-			input[inOff]= (byte) 0x80;
-			inOff ++;
-
-			while (inOff < input.Length)
+			input[inOff]= 0x80;
+			while (++inOff < input.Length)
 			{
-				input[inOff] = (byte) 0;
-				inOff++;
+				input[inOff] = 0x00;
 			}
 
-			return added;
+			return count;
 		}
 
-		/**
-		 * return the number of pad bytes present in the block.
-		 */
-		public int PadCount(
-			byte[] input)
-		{
-			int count = input.Length - 1;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int AddPadding(Span<byte> block, int position)
+        {
+            int count = block.Length - position;
+			block[position++] = 0x80;
+            block[position..].Fill(0x00);
+            return count;
+        }
+#endif
 
-			while (count > 0 && input[count] == 0)
+        public int PadCount(byte[] input)
+		{
+			int position = -1, still00Mask = -1;
+			int i = input.Length;
+			while (--i >= 0)
 			{
-				count--;
+				int next = input[i];
+				int match00Mask = ((next ^ 0x00) - 1) >> 31;
+				int match80Mask = ((next ^ 0x80) - 1) >> 31;
+				position ^= (i ^ position) & still00Mask & match80Mask;
+				still00Mask &= match00Mask;
 			}
-
-			if (input[count] != (byte)0x80)
-			{
+			if (position < 0)
 				throw new InvalidCipherTextException("pad block corrupted");
-			}
 
-			return input.Length - count;
+			return input.Length - position;
 		}
-	}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int PadCount(ReadOnlySpan<byte> block)
+		{
+            int position = -1, still00Mask = -1;
+            int i = block.Length;
+            while (--i >= 0)
+            {
+                int next = block[i];
+                int match00Mask = ((next ^ 0x00) - 1) >> 31;
+                int match80Mask = ((next ^ 0x80) - 1) >> 31;
+                position ^= (i ^ position) & still00Mask & match80Mask;
+                still00Mask &= match00Mask;
+            }
+            if (position < 0)
+                throw new InvalidCipherTextException("pad block corrupted");
+
+            return block.Length - position;
+        }
+#endif
+    }
 }
diff --git a/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs b/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs
index 5d2f8cf15..fb000ff8b 100644
--- a/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs
+++ b/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs
@@ -1,6 +1,6 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Modes;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
 
@@ -16,22 +16,25 @@ namespace Org.BouncyCastle.Crypto.Paddings
 	public class PaddedBufferedBlockCipher
 		: BufferedBlockCipher
 	{
-		private readonly IBlockCipherPadding padding;
+        private readonly IBlockCipherPadding padding;
 
-		/**
+        public PaddedBufferedBlockCipher(IBlockCipher cipher, IBlockCipherPadding padding)
+			: this(EcbBlockCipher.GetBlockCipherMode(cipher), padding)
+        {
+        }
+
+        /**
 		* Create a buffered block cipher with the desired padding.
 		*
 		* @param cipher the underlying block cipher this buffering object wraps.
 		* @param padding the padding type.
 		*/
-		public PaddedBufferedBlockCipher(
-			IBlockCipher		cipher,
-			IBlockCipherPadding	padding)
+        public PaddedBufferedBlockCipher(IBlockCipherMode cipherMode, IBlockCipherPadding padding)
 		{
-			this.cipher = cipher;
+			m_cipherMode = cipherMode;
 			this.padding = padding;
 
-			buf = new byte[cipher.GetBlockSize()];
+			buf = new byte[m_cipherMode.GetBlockSize()];
 			bufOff = 0;
 		}
 
@@ -40,9 +43,10 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		*
 		* @param cipher the underlying block cipher this buffering object wraps.
 		*/
-		public PaddedBufferedBlockCipher(
-			IBlockCipher cipher)
-			: this(cipher, new Pkcs7Padding())    { }
+		public PaddedBufferedBlockCipher(IBlockCipherMode cipherMode)
+			: this(cipherMode, new Pkcs7Padding())
+		{
+		}
 
 		/**
 		* initialise the cipher.
@@ -53,23 +57,20 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* @exception ArgumentException if the parameters argument is
 		* inappropriate.
 		*/
-		public override void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+		public override void Init(bool forEncryption, ICipherParameters parameters)
 		{
 			this.forEncryption = forEncryption;
 
 			SecureRandom initRandom = null;
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom withRandom)
 			{
-				ParametersWithRandom p = (ParametersWithRandom)parameters;
-				initRandom = p.Random;
-				parameters = p.Parameters;
+				initRandom = withRandom.Random;
+				parameters = withRandom.Parameters;
 			}
 
 			Reset();
 			padding.Init(initRandom);
-			cipher.Init(forEncryption, parameters);
+			m_cipherMode.Init(forEncryption, parameters);
 		}
 
 		/**
@@ -110,8 +111,8 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		public override int GetUpdateOutputSize(
 			int length)
 		{
-			int total       = length + bufOff;
-			int leftOver    = total % buf.Length;
+			int total = length + bufOff;
+			int leftOver = total % buf.Length;
 
 			if (leftOver == 0)
 			{
@@ -131,16 +132,13 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
+		public override int ProcessByte(byte input, byte[] output, int outOff)
 		{
 			int resultLen = 0;
 
 			if (bufOff == buf.Length)
 			{
-				resultLen = cipher.ProcessBlock(buf, 0, output, outOff);
+				resultLen = m_cipherMode.ProcessBlock(buf, 0, output, outOff);
 				bufOff = 0;
 			}
 
@@ -149,6 +147,23 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			return resultLen;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int ProcessByte(byte input, Span<byte> output)
+		{
+			int resultLen = 0;
+
+			if (bufOff == buf.Length)
+			{
+				resultLen = m_cipherMode.ProcessBlock(buf, output);
+				bufOff = 0;
+			}
+
+			buf[bufOff++] = input;
+
+			return resultLen;
+		}
+#endif
+
 		/**
 		* process an array of bytes, producing output if necessary.
 		*
@@ -161,24 +176,17 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length,
-			byte[]	output,
-			int		outOff)
+		public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
 		{
 			if (length < 0)
-			{
 				throw new ArgumentException("Can't have a negative input length!");
-			}
 
 			int blockSize = GetBlockSize();
 			int outLength = GetUpdateOutputSize(length);
 
 			if (outLength > 0)
 			{
-                Check.OutputLength(output, outOff, outLength, "output buffer too short");
+				Check.OutputLength(output, outOff, outLength, "output buffer too short");
 			}
 
 			int resultLen = 0;
@@ -188,7 +196,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			{
 				Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-				resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
+				resultLen = m_cipherMode.ProcessBlock(buf, 0, output, outOff);
 
 				bufOff = 0;
 				length -= gapLen;
@@ -196,7 +204,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
 
 				while (length > buf.Length)
 				{
-					resultLen += cipher.ProcessBlock(input, inOff, output, outOff + resultLen);
+					resultLen += m_cipherMode.ProcessBlock(input, inOff, output, outOff + resultLen);
 
 					length -= blockSize;
 					inOff += blockSize;
@@ -210,7 +218,46 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			return resultLen;
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			int blockSize = GetBlockSize();
+			int outLength = GetUpdateOutputSize(input.Length);
+
+			if (outLength > 0)
+			{
+				Check.OutputLength(output, outLength, "output buffer too short");
+			}
+
+			int resultLen = 0;
+			int gapLen = buf.Length - bufOff;
+
+			if (input.Length > gapLen)
+			{
+				input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+				resultLen = m_cipherMode.ProcessBlock(buf, output);
+
+				bufOff = 0;
+				input = input[gapLen..];
+
+				while (input.Length > buf.Length)
+				{
+					resultLen += m_cipherMode.ProcessBlock(input, output[resultLen..]);
+
+					input = input[blockSize..];
+				}
+			}
+
+			input.CopyTo(buf.AsSpan(bufOff));
+
+			bufOff += input.Length;
+
+			return resultLen;
+		}
+#endif
+
+        /**
 		* Process the last block in the buffer. If the buffer is currently
 		* full and padding needs to be added a call to doFinal will produce
 		* 2 * GetBlockSize() bytes.
@@ -224,11 +271,9 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* initialised.
 		* @exception InvalidCipherTextException if padding is expected and not found.
 		*/
-		public override int DoFinal(
-			byte[]  output,
-			int     outOff)
-		{
-			int blockSize = cipher.GetBlockSize();
+        public override int DoFinal(byte[] output, int outOff)
+        {
+            int blockSize = m_cipherMode.GetBlockSize();
 			int resultLen = 0;
 
 			if (forEncryption)
@@ -242,13 +287,13 @@ namespace Org.BouncyCastle.Crypto.Paddings
 						throw new OutputLengthException("output buffer too short");
 					}
 
-					resultLen = cipher.ProcessBlock(buf, 0, output, outOff);
+					resultLen = m_cipherMode.ProcessBlock(buf, 0, output, outOff);
 					bufOff = 0;
 				}
 
 				padding.AddPadding(buf, bufOff);
 
-				resultLen += cipher.ProcessBlock(buf, 0, output, outOff + resultLen);
+				resultLen += m_cipherMode.ProcessBlock(buf, 0, output, outOff + resultLen);
 
 				Reset();
 			}
@@ -256,7 +301,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			{
 				if (bufOff == blockSize)
 				{
-					resultLen = cipher.ProcessBlock(buf, 0, buf, 0);
+					resultLen = m_cipherMode.ProcessBlock(buf, 0, buf, 0);
 					bufOff = 0;
 				}
 				else
@@ -280,6 +325,60 @@ namespace Org.BouncyCastle.Crypto.Paddings
 
 			return resultLen;
 		}
-	}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int DoFinal(Span<byte> output)
+		{
+            int blockSize = m_cipherMode.GetBlockSize();
+			int resultLen = 0;
+
+			if (forEncryption)
+			{
+				if (bufOff == blockSize)
+				{
+					if ((2 * blockSize) > output.Length)
+					{
+						Reset();
+
+						throw new OutputLengthException("output buffer too short");
+					}
+
+					resultLen = m_cipherMode.ProcessBlock(buf, output);
+					bufOff = 0;
+				}
+
+				padding.AddPadding(buf, bufOff);
+
+				resultLen += m_cipherMode.ProcessBlock(buf, output[resultLen..]);
+
+				Reset();
+			}
+			else
+			{
+				if (bufOff != blockSize)
+                {
+                    Reset();
+
+                    throw new DataLengthException("last block incomplete in decryption");
+                }
+
+                resultLen = m_cipherMode.ProcessBlock(buf, buf);
+				bufOff = 0;
+
+				try
+				{
+					resultLen -= padding.PadCount(buf);
+
+					buf.AsSpan(0, resultLen).CopyTo(output);
+				}
+				finally
+				{
+					Reset();
+				}
+			}
+
+			return resultLen;
+		}
+#endif
+	}
 }
diff --git a/crypto/src/crypto/paddings/Pkcs7Padding.cs b/crypto/src/crypto/paddings/Pkcs7Padding.cs
index 11585647a..46d97c9eb 100644
--- a/crypto/src/crypto/paddings/Pkcs7Padding.cs
+++ b/crypto/src/crypto/paddings/Pkcs7Padding.cs
@@ -32,45 +32,63 @@ namespace Org.BouncyCastle.Crypto.Paddings
             get { return "PKCS7"; }
         }
 
-        /**
-        * add the pad bytes to the passed in block, returning the
-        * number of bytes added.
-        */
-        public int AddPadding(
-            byte[]  input,
-            int     inOff)
+        public int AddPadding(byte[] input, int inOff)
         {
-            byte code = (byte)(input.Length - inOff);
+            int count = input.Length - inOff;
+            byte padValue = (byte)count;
 
             while (inOff < input.Length)
             {
-                input[inOff] = code;
-                inOff++;
+                input[inOff++] = padValue;
             }
 
-            return code;
+            return count;
         }
 
-        /**
-        * return the number of pad bytes present in the block.
-        */
-        public int PadCount(
-            byte[] input)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int AddPadding(Span<byte> block, int position)
+        {
+            int count = block.Length - position;
+            byte padValue = (byte)count;
+            block[position..].Fill(padValue);
+            return count;
+        }
+#endif
+
+        public int PadCount(byte[] input)
         {
-            byte countAsByte = input[input.Length - 1];
-            int count = countAsByte;
+            byte padValue = input[input.Length - 1];
+            int count = padValue;
+            int position = input.Length - count;
 
-            if (count < 1 || count > input.Length)
+            int failed = (position | (count - 1)) >> 31;
+            for (int i = 0; i < input.Length; ++i)
+            {
+                failed |= (input[i] ^ padValue) & ~((i - position) >> 31);
+            }
+            if (failed != 0)
                 throw new InvalidCipherTextException("pad block corrupted");
 
-            for (int i = 2; i <= count; i++)
+            return count;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int PadCount(ReadOnlySpan<byte> block)
+        {
+            byte padValue = block[block.Length - 1];
+            int count = padValue;
+            int position = block.Length - count;
+
+            int failed = (position | (count - 1)) >> 31;
+            for (int i = 0; i < block.Length; ++i)
             {
-                if (input[input.Length - i] != countAsByte)
-                    throw new InvalidCipherTextException("pad block corrupted");
+                failed |= (block[i] ^ padValue) & ~((i - position) >> 31);
             }
+            if (failed != 0)
+                throw new InvalidCipherTextException("pad block corrupted");
 
             return count;
         }
+#endif
     }
-
 }
diff --git a/crypto/src/crypto/paddings/TbcPadding.cs b/crypto/src/crypto/paddings/TbcPadding.cs
index 74b64e8e1..b54c5f4d0 100644
--- a/crypto/src/crypto/paddings/TbcPadding.cs
+++ b/crypto/src/crypto/paddings/TbcPadding.cs
@@ -1,5 +1,5 @@
 using System;
-using Org.BouncyCastle.Crypto;
+
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Crypto.Paddings
@@ -30,50 +30,72 @@ namespace Org.BouncyCastle.Crypto.Paddings
             // nothing to do.
         }
 
-        /// <summary> add the pad bytes to the passed in block, returning the
-        /// number of bytes added.
-        /// <p>
-        /// Note: this assumes that the last block of plain text is always
-        /// passed to it inside in. i.e. if inOff is zero, indicating the
-        /// entire block is to be overwritten with padding the value of in
-        /// should be the same as the last block of plain text.
-        /// </p>
-        /// </summary>
+        /// <summary> add the pad bytes to the passed in block, returning the number of bytes added.</summary>
+        /// <remarks>
+        /// This assumes that the last block of plain text is always passed to it inside <paramref name="input"/>.
+        /// i.e. if <paramref name="inOff"/> is zero, indicating the padding will fill the entire block,the value of
+        /// <paramref name="input"/> should be the same as the last block of plain text.
+        /// </remarks>
         public virtual int AddPadding(byte[] input, int inOff)
         {
             int count = input.Length - inOff;
-            byte code;
-
-            if (inOff > 0)
-            {
-                code = (byte)((input[inOff - 1] & 0x01) == 0?0xff:0x00);
-            }
-            else
-            {
-                code = (byte)((input[input.Length - 1] & 0x01) == 0?0xff:0x00);
-            }
+            byte lastByte = inOff > 0 ? input[inOff - 1] : input[input.Length - 1];
+            byte padValue = (byte)((lastByte & 1) - 1);
 
             while (inOff < input.Length)
             {
-                input[inOff] = code;
-                inOff++;
+                input[inOff++] = padValue;
             }
 
             return count;
         }
 
-        /// <summary> return the number of pad bytes present in the block.</summary>
-        public virtual int PadCount(byte[] input)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary> add the pad bytes to the passed in block, returning the number of bytes added.</summary>
+        /// <remarks>
+        /// This assumes that the last block of plain text is always passed to it inside <paramref name="block"/>.
+        /// i.e. if <paramref name="position"/> is zero, indicating the padding will fill the entire block,the value of
+        /// <paramref name="block"/> should be the same as the last block of plain text.
+        /// </remarks>
+        public virtual int AddPadding(Span<byte> block, int position)
         {
-            byte code = input[input.Length - 1];
+            byte lastByte = position > 0 ? block[position - 1] : block[block.Length - 1];
+            byte padValue = (byte)((lastByte & 1) - 1);
+
+            var padding = block[position..];
+            padding.Fill(padValue);
+            return padding.Length;
+        }
+#endif
 
-            int index = input.Length - 1;
-            while (index > 0 && input[index - 1] == code)
+        public virtual int PadCount(byte[] input)
+        {
+            int i = input.Length;
+            int code = input[--i], count = 1, countingMask = -1;
+            while (--i >= 0)
             {
-                index--;
+                int next = input[i];
+                int matchMask = ((next ^ code) - 1) >> 31;
+                countingMask &= matchMask;
+                count -= countingMask;
             }
+            return count;
+        }
 
-            return input.Length - index;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int PadCount(ReadOnlySpan<byte> block)
+        {
+            int i = block.Length;
+            int code = block[--i], count = 1, countingMask = -1;
+            while (--i >= 0)
+            {
+                int next = block[i];
+                int matchMask = ((next ^ code) - 1) >> 31;
+                countingMask &= matchMask;
+                count -= countingMask;
+            }
+            return count;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/paddings/X923Padding.cs b/crypto/src/crypto/paddings/X923Padding.cs
index cc1b52b3e..12338aa04 100644
--- a/crypto/src/crypto/paddings/X923Padding.cs
+++ b/crypto/src/crypto/paddings/X923Padding.cs
@@ -1,7 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Paddings
 {
@@ -35,48 +35,69 @@ namespace Org.BouncyCastle.Crypto.Paddings
             get { return "X9.23"; }
         }
 
-		/**
-        * add the pad bytes to the passed in block, returning the
-        * number of bytes added.
-        */
-        public int AddPadding(
-            byte[]  input,
-            int     inOff)
+        public int AddPadding(byte[] input, int inOff)
         {
-            byte code = (byte)(input.Length - inOff);
+            int count = input.Length - inOff;
+            if (count > 1)
+            {
+                if (random == null)
+                {
+                    Arrays.Fill(input, inOff, input.Length - 1, 0x00);
+                }
+                else
+                {
+                    random.NextBytes(input, inOff, count - 1);
+                }
+            }
+            input[input.Length - 1] = (byte)count;
+            return count;
+        }
 
-            while (inOff < input.Length - 1)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int AddPadding(Span<byte> block, int position)
+        {
+            int count = block.Length - position;
+            if (count > 1)
             {
+                var body = block[position..(block.Length - 1)];
                 if (random == null)
                 {
-                    input[inOff] = 0;
+                    body.Fill(0x00);
                 }
                 else
                 {
-                    input[inOff] = (byte)random.NextInt();
+                    random.NextBytes(body);
                 }
-                inOff++;
             }
+            block[block.Length - 1] = (byte)count;
+            return count;
+        }
+#endif
 
-            input[inOff] = code;
+        public int PadCount(byte[] input)
+        {
+            int count = input[input.Length - 1];
+            int position = input.Length - count;
 
-            return code;
+            int failed = (position | (count - 1)) >> 31;
+            if (failed != 0)
+                throw new InvalidCipherTextException("pad block corrupted");
+
+            return count;
         }
 
-        /**
-        * return the number of pad bytes present in the block.
-        */
-        public int PadCount(
-			byte[] input)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int PadCount(ReadOnlySpan<byte> block)
         {
-            int count = input[input.Length - 1] & 0xff;
+            int count = block[block.Length - 1];
+            int position = block.Length - count;
 
-            if (count > input.Length)
-            {
+            int failed = (position | (count - 1)) >> 31;
+            if (failed != 0)
                 throw new InvalidCipherTextException("pad block corrupted");
-            }
 
             return count;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/paddings/ZeroBytePadding.cs b/crypto/src/crypto/paddings/ZeroBytePadding.cs
index 0d55ca4c2..910fe7154 100644
--- a/crypto/src/crypto/paddings/ZeroBytePadding.cs
+++ b/crypto/src/crypto/paddings/ZeroBytePadding.cs
@@ -1,5 +1,5 @@
 using System;
-using Org.BouncyCastle.Crypto;
+
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Crypto.Paddings
@@ -28,41 +28,55 @@ namespace Org.BouncyCastle.Crypto.Paddings
             // nothing to do.
         }
 
-        /// <summary> add the pad bytes to the passed in block, returning the
-        /// number of bytes added.
-        /// </summary>
-        public int AddPadding(
-			byte[]	input,
-			int		inOff)
+        public int AddPadding(byte[] input, int inOff)
         {
-            int added = (input.Length - inOff);
+            int added = input.Length - inOff;
 
             while (inOff < input.Length)
             {
-                input[inOff] = (byte) 0;
-                inOff++;
+                input[inOff++] = 0x00;
             }
 
             return added;
         }
 
-		/// <summary> return the number of pad bytes present in the block.</summary>
-        public int PadCount(
-			byte[] input)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int AddPadding(Span<byte> block, int position)
         {
-            int count = input.Length;
+            int count = block.Length - position;
+            block[position..].Fill(0x00);
+            return count;
+        }
+#endif
 
-            while (count > 0)
+        public int PadCount(byte[] input)
+        {
+            int count = 0, still00Mask = -1;
+            int i = input.Length;
+            while (--i >= 0)
             {
-                if (input[count - 1] != 0)
-                {
-                    break;
-                }
-
-                count--;
+                int next = input[i];
+                int match00Mask = ((next ^ 0x00) - 1) >> 31;
+                still00Mask &= match00Mask;
+                count -= still00Mask;
             }
+            return count;
+        }
 
-            return input.Length - count;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int PadCount(ReadOnlySpan<byte> block)
+        {
+            int count = 0, still00Mask = -1;
+            int i = block.Length;
+            while (--i >= 0)
+            {
+                int next = block[i];
+                int match00Mask = ((next ^ 0x00) - 1) >> 31;
+                still00Mask &= match00Mask;
+                count -= still00Mask;
+            }
+            return count;
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/parameters/Blake3Parameters.cs b/crypto/src/crypto/parameters/Blake3Parameters.cs
new file mode 100644
index 000000000..03aed1ede
--- /dev/null
+++ b/crypto/src/crypto/parameters/Blake3Parameters.cs
@@ -0,0 +1,58 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Parameters
+{
+    /// <summary>Blake3 Parameters.</summary>
+    public sealed class Blake3Parameters
+        : ICipherParameters
+    {
+        private const int KeyLen = 32;
+
+        private byte[] m_theKey;
+        private byte[] m_theContext;
+
+        /// <summary>Create a key parameter.</summary>
+        /// <param name="pContext">the context</param>
+        /// <returns>the parameter</returns>
+        public static Blake3Parameters Context(byte[] pContext)
+        {
+            if (pContext == null)
+                throw new ArgumentNullException(nameof(pContext));
+
+            Blake3Parameters myParams = new Blake3Parameters();
+            myParams.m_theContext = Arrays.Clone(pContext);
+            return myParams;
+        }
+
+        /// <summary>Create a key parameter.</summary>
+        /// <param name="pKey">the key</param>
+        /// <returns>the parameter</returns>
+        public static Blake3Parameters Key(byte[] pKey)
+        {
+            if (pKey == null)
+                throw new ArgumentNullException(nameof(pKey));
+            if (pKey.Length != KeyLen)
+                throw new ArgumentException("Invalid key length", nameof(pKey));
+
+            Blake3Parameters myParams = new Blake3Parameters();
+            myParams.m_theKey = Arrays.Clone(pKey);
+            return myParams;
+        }
+
+        /// <summary>Obtain the key.</summary>
+        /// <returns>the key</returns>
+        public byte[] GetKey() => Arrays.Clone(m_theKey);
+
+        /// <summary>Clear the key bytes.</summary>
+        public void ClearKey()
+        {
+            Arrays.Fill(m_theKey, 0);
+        }
+
+        /// <summary>Obtain the salt.</summary>
+        /// <returns>the salt</returns>
+        public byte[] GetContext() => Arrays.Clone(m_theContext);
+    }
+}
diff --git a/crypto/src/crypto/parameters/DesParameters.cs b/crypto/src/crypto/parameters/DesParameters.cs
index a1f67e2b1..28881f21c 100644
--- a/crypto/src/crypto/parameters/DesParameters.cs
+++ b/crypto/src/crypto/parameters/DesParameters.cs
@@ -135,5 +135,15 @@ namespace Org.BouncyCastle.Crypto.Parameters
                 bytes[off + i] = SetOddParity(bytes[off + i]);
             }
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void SetOddParity(Span<byte> bytes)
+        {
+            for (int i = 0; i < bytes.Length; i++)
+            {
+                bytes[i] = SetOddParity(bytes[i]);
+            }
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs b/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
index 56ca19ed2..a50f71972 100644
--- a/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
@@ -35,6 +35,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed25519PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed25519PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -47,6 +58,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -58,9 +76,15 @@ namespace Org.BouncyCastle.Crypto.Parameters
             {
                 if (null == cachedPublicKey)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    Span<byte> publicKey = stackalloc byte[Ed25519.PublicKeySize];
+                    Ed25519.GeneratePublicKey(data, publicKey);
+                    cachedPublicKey = new Ed25519PublicKeyParameters(publicKey);
+#else
                     byte[] publicKey = new byte[Ed25519.PublicKeySize];
                     Ed25519.GeneratePublicKey(data, 0, publicKey, 0);
                     cachedPublicKey = new Ed25519PublicKeyParameters(publicKey, 0);
+#endif
                 }
 
                 return cachedPublicKey;
@@ -108,7 +132,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs b/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
index 8a9139e8d..9b94635d5 100644
--- a/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed25519PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed25519PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -37,6 +48,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -45,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs b/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
index a6a1b72a1..ac12a2f1d 100644
--- a/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
@@ -35,6 +35,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed448PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed448PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -47,6 +58,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -58,9 +76,15 @@ namespace Org.BouncyCastle.Crypto.Parameters
             {
                 if (null == cachedPublicKey)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    Span<byte> publicKey = stackalloc byte[Ed448.PublicKeySize];
+                    Ed448.GeneratePublicKey(data, publicKey);
+                    cachedPublicKey = new Ed448PublicKeyParameters(publicKey);
+#else
                     byte[] publicKey = new byte[Ed448.PublicKeySize];
                     Ed448.GeneratePublicKey(data, 0, publicKey, 0);
                     cachedPublicKey = new Ed448PublicKeyParameters(publicKey, 0);
+#endif
                 }
 
                 return cachedPublicKey;
@@ -100,7 +124,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs b/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
index 8a89be08f..26f6b5ba9 100644
--- a/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed448PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed448PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -37,6 +48,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -45,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/KdfParameters.cs b/crypto/src/crypto/parameters/KdfParameters.cs
index bc5c905d0..78cf81855 100644
--- a/crypto/src/crypto/parameters/KdfParameters.cs
+++ b/crypto/src/crypto/parameters/KdfParameters.cs
@@ -6,28 +6,26 @@ namespace Org.BouncyCastle.Crypto.Parameters
     /**
      * parameters for Key derivation functions for IEEE P1363a
      */
-    public class KdfParameters : IDerivationParameters
+    public class KdfParameters
+        : IDerivationParameters
     {
-        byte[]  iv;
-        byte[]  shared;
+        private readonly byte[] m_iv;
+        private readonly byte[] m_shared;
 
-        public KdfParameters(
-            byte[]  shared,
-            byte[]  iv)
+        public KdfParameters(byte[] shared, byte[] iv)
         {
-            this.shared = shared;
-            this.iv = iv;
+            m_shared = shared;
+            m_iv = iv;
         }
 
         public byte[] GetSharedSecret()
         {
-            return shared;
+            return m_shared;
         }
 
         public byte[] GetIV()
         {
-            return iv;
+            return m_iv;
         }
     }
-
 }
diff --git a/crypto/src/crypto/parameters/KeyParameter.cs b/crypto/src/crypto/parameters/KeyParameter.cs
index 043adf276..bc6c28368 100644
--- a/crypto/src/crypto/parameters/KeyParameter.cs
+++ b/crypto/src/crypto/parameters/KeyParameter.cs
@@ -7,37 +7,39 @@ namespace Org.BouncyCastle.Crypto.Parameters
     public class KeyParameter
 		: ICipherParameters
     {
-        private readonly byte[] key;
+        private readonly byte[] m_key;
 
-		public KeyParameter(
-			byte[] key)
+		public KeyParameter(byte[] key)
 		{
 			if (key == null)
-				throw new ArgumentNullException("key");
+				throw new ArgumentNullException(nameof(key));
 
-			this.key = (byte[]) key.Clone();
+			m_key = (byte[])key.Clone();
 		}
 
-		public KeyParameter(
-            byte[]	key,
-            int		keyOff,
-            int		keyLen)
+		public KeyParameter(byte[] key, int keyOff, int keyLen)
         {
 			if (key == null)
-				throw new ArgumentNullException("key");
+				throw new ArgumentNullException(nameof(key));
 			if (keyOff < 0 || keyOff > key.Length)
-				throw new ArgumentOutOfRangeException("keyOff");
+				throw new ArgumentOutOfRangeException(nameof(keyOff));
             if (keyLen < 0 || keyLen > (key.Length - keyOff))
-				throw new ArgumentOutOfRangeException("keyLen");
+				throw new ArgumentOutOfRangeException(nameof(keyLen));
 
-			this.key = new byte[keyLen];
-            Array.Copy(key, keyOff, this.key, 0, keyLen);
+			m_key = new byte[keyLen];
+            Array.Copy(key, keyOff, m_key, 0, keyLen);
         }
 
-		public byte[] GetKey()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public KeyParameter(ReadOnlySpan<byte> key)
         {
-			return (byte[]) key.Clone();
+            m_key = key.ToArray();
         }
-    }
+#endif
 
+        public byte[] GetKey()
+        {
+			return (byte[])m_key.Clone();
+        }
+    }
 }
diff --git a/crypto/src/crypto/parameters/MgfParameters.cs b/crypto/src/crypto/parameters/MgfParameters.cs
index 11983b877..7915567e2 100644
--- a/crypto/src/crypto/parameters/MgfParameters.cs
+++ b/crypto/src/crypto/parameters/MgfParameters.cs
@@ -1,31 +1,42 @@
 using System;
 
+using Org.BouncyCastle.Utilities;
+
 namespace Org.BouncyCastle.Crypto.Parameters
 {
 	/// <remarks>Parameters for mask derivation functions.</remarks>
-    public class MgfParameters
+    public sealed class MgfParameters
 		: IDerivationParameters
     {
-        private readonly byte[] seed;
+        private readonly byte[] m_seed;
 
-		public MgfParameters(
-            byte[] seed)
+		public MgfParameters(byte[] seed)
 			: this(seed, 0, seed.Length)
         {
         }
 
-		public MgfParameters(
-            byte[]  seed,
-            int     off,
-            int     len)
+		public MgfParameters(byte[] seed, int off, int len)
+        {
+            m_seed = Arrays.CopyOfRange(seed, off, len);
+        }
+
+        public byte[] GetSeed()
         {
-            this.seed = new byte[len];
-            Array.Copy(seed, off, this.seed, 0, len);
+            return (byte[])m_seed.Clone();
         }
 
-		public byte[] GetSeed()
+        public void GetSeed(byte[] buffer, int offset)
         {
-            return (byte[]) seed.Clone();
+            m_seed.CopyTo(buffer, offset);
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void GetSeed(Span<byte> output)
+        {
+            m_seed.CopyTo(output);
+        }
+#endif
+
+        public int SeedLength => m_seed.Length;
     }
 }
diff --git a/crypto/src/crypto/parameters/ParametersWithID.cs b/crypto/src/crypto/parameters/ParametersWithID.cs
index 37f68705b..2bc4ac86c 100644
--- a/crypto/src/crypto/parameters/ParametersWithID.cs
+++ b/crypto/src/crypto/parameters/ParametersWithID.cs
@@ -7,30 +7,33 @@ namespace Org.BouncyCastle.Crypto.Parameters
     public class ParametersWithID
         : ICipherParameters
     {
-        private readonly ICipherParameters parameters;
-        private readonly byte[] id;
+        private readonly ICipherParameters m_parameters;
+        private readonly byte[] m_id;
 
-        public ParametersWithID(ICipherParameters parameters,
-            byte[] id)
+        public ParametersWithID(ICipherParameters parameters, byte[] id)
             : this(parameters, id, 0, id.Length)
         {
         }
 
-        public ParametersWithID(ICipherParameters parameters,
-            byte[] id, int idOff, int idLen)
+        public ParametersWithID(ICipherParameters parameters, byte[] id, int idOff, int idLen)
         {
-            this.parameters = parameters;
-            this.id = Arrays.CopyOfRange(id, idOff, idOff + idLen);
+            m_parameters = parameters;
+            m_id = Arrays.CopyOfRange(id, idOff, idOff + idLen);
         }
 
-        public byte[] GetID()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public ParametersWithID(ICipherParameters parameters, ReadOnlySpan<byte> id)
         {
-            return id;
+            m_parameters = parameters;
+            m_id = id.ToArray();
         }
+#endif
 
-        public ICipherParameters Parameters
+        public byte[] GetID()
         {
-            get { return parameters; }
+            return m_id;
         }
+
+        public ICipherParameters Parameters => m_parameters;
     }
 }
diff --git a/crypto/src/crypto/parameters/ParametersWithIV.cs b/crypto/src/crypto/parameters/ParametersWithIV.cs
index 4b2eb930f..ac55afc8d 100644
--- a/crypto/src/crypto/parameters/ParametersWithIV.cs
+++ b/crypto/src/crypto/parameters/ParametersWithIV.cs
@@ -7,34 +7,33 @@ namespace Org.BouncyCastle.Crypto.Parameters
     public class ParametersWithIV
         : ICipherParameters
     {
-        private readonly ICipherParameters parameters;
-        private readonly byte[] iv;
+        private readonly ICipherParameters m_parameters;
+        private readonly byte[] m_iv;
 
-        public ParametersWithIV(ICipherParameters parameters,
-            byte[] iv)
+        public ParametersWithIV(ICipherParameters parameters, byte[] iv)
             : this(parameters, iv, 0, iv.Length)
         {
         }
 
-        public ParametersWithIV(ICipherParameters parameters,
-            byte[] iv, int ivOff, int ivLen)
+        public ParametersWithIV(ICipherParameters parameters, byte[] iv, int ivOff, int ivLen)
         {
-            // NOTE: 'parameters' may be null to imply key re-use
-            if (iv == null)
-                throw new ArgumentNullException("iv");
-
-            this.parameters = parameters;
-            this.iv = Arrays.CopyOfRange(iv, ivOff, ivOff + ivLen);
+            m_parameters = parameters;
+            m_iv = Arrays.CopyOfRange(iv, ivOff, ivOff + ivLen);
         }
 
-        public byte[] GetIV()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public ParametersWithIV(ICipherParameters parameters, ReadOnlySpan<byte> iv)
         {
-            return (byte[])iv.Clone();
+            m_parameters = parameters;
+            m_iv = iv.ToArray();
         }
+#endif
 
-        public ICipherParameters Parameters
+        public byte[] GetIV()
         {
-            get { return parameters; }
+            return (byte[])m_iv.Clone();
         }
+
+        public ICipherParameters Parameters => m_parameters;
     }
 }
diff --git a/crypto/src/crypto/parameters/ParametersWithRandom.cs b/crypto/src/crypto/parameters/ParametersWithRandom.cs
index e19c9125f..2fe885a8a 100644
--- a/crypto/src/crypto/parameters/ParametersWithRandom.cs
+++ b/crypto/src/crypto/parameters/ParametersWithRandom.cs
@@ -7,36 +7,27 @@ namespace Org.BouncyCastle.Crypto.Parameters
     public class ParametersWithRandom
 		: ICipherParameters
     {
-        private readonly ICipherParameters	parameters;
-		private readonly SecureRandom		random;
+        private readonly ICipherParameters m_parameters;
+		private readonly SecureRandom m_random;
 
-		public ParametersWithRandom(
-            ICipherParameters	parameters,
-            SecureRandom		random)
+        public ParametersWithRandom(ICipherParameters parameters)
+            : this(parameters, CryptoServicesRegistrar.GetSecureRandom())
+        {
+        }
+
+        public ParametersWithRandom(ICipherParameters parameters, SecureRandom random)
         {
 			if (parameters == null)
-				throw new ArgumentNullException("parameters");
+				throw new ArgumentNullException(nameof(parameters));
 			if (random == null)
-				throw new ArgumentNullException("random");
-
-			this.parameters = parameters;
-			this.random = random;
-		}
+				throw new ArgumentNullException(nameof(random));
 
-		public ParametersWithRandom(
-            ICipherParameters parameters)
-			: this(parameters, new SecureRandom())
-        {
+			m_parameters = parameters;
+			m_random = random;
 		}
 
-		public SecureRandom Random
-        {
-			get { return random; }
-        }
+        public ICipherParameters Parameters => m_parameters;
 
-		public ICipherParameters Parameters
-        {
-            get { return parameters; }
-        }
+        public SecureRandom Random => m_random;
     }
 }
diff --git a/crypto/src/crypto/parameters/ParametersWithSalt.cs b/crypto/src/crypto/parameters/ParametersWithSalt.cs
index 7f4cd6cd1..277cd213c 100644
--- a/crypto/src/crypto/parameters/ParametersWithSalt.cs
+++ b/crypto/src/crypto/parameters/ParametersWithSalt.cs
@@ -1,39 +1,42 @@
 using System;
 
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Parameters
 {
 
     /// <summary> Cipher parameters with a fixed salt value associated with them.</summary>
-    public class ParametersWithSalt : ICipherParameters
+    public class ParametersWithSalt
+        : ICipherParameters
     {
-        private byte[] salt;
-        private ICipherParameters parameters;
+        private readonly ICipherParameters m_parameters;
+        private readonly byte[] m_salt;
 
-        public ParametersWithSalt(ICipherParameters parameters, byte[] salt):this(parameters, salt, 0, salt.Length)
+        public ParametersWithSalt(ICipherParameters parameters, byte[] salt)
+            : this(parameters, salt, 0, salt.Length)
         {
         }
 
         public ParametersWithSalt(ICipherParameters parameters, byte[] salt, int saltOff, int saltLen)
         {
-            this.salt = new byte[saltLen];
-            this.parameters = parameters;
-
-            Array.Copy(salt, saltOff, this.salt, 0, saltLen);
+            m_parameters = parameters;
+            m_salt = Arrays.CopyOfRange(salt, saltOff, saltOff + saltLen);
         }
 
-        public byte[] GetSalt()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public ParametersWithSalt(ICipherParameters parameters, ReadOnlySpan<byte> salt)
         {
-            return salt;
+            m_parameters = parameters;
+            m_salt = salt.ToArray();
         }
+#endif
 
-        public ICipherParameters Parameters
+        public byte[] GetSalt()
         {
-            get
-            {
-                return parameters;
-            }
+            return m_salt;
         }
+
+        public ICipherParameters Parameters => m_parameters;
     }
 }
diff --git a/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs b/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs
index 63e72d3a7..8b263c861 100644
--- a/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs
@@ -33,6 +33,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X25519PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X25519PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -45,6 +56,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -52,23 +70,43 @@ namespace Org.BouncyCastle.Crypto.Parameters
 
         public X25519PublicKeyParameters GeneratePublicKey()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> publicKey = stackalloc byte[X25519.PointSize];
+            X25519.GeneratePublicKey(data, publicKey);
+            return new X25519PublicKeyParameters(publicKey);
+#else
             byte[] publicKey = new byte[X25519.PointSize];
             X25519.GeneratePublicKey(data, 0, publicKey, 0);
             return new X25519PublicKeyParameters(publicKey, 0);
+#endif
         }
 
         public void GenerateSecret(X25519PublicKeyParameters publicKey, byte[] buf, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GenerateSecret(publicKey, buf.AsSpan(off));
+#else
             byte[] encoded = new byte[X25519.PointSize];
             publicKey.Encode(encoded, 0);
             if (!X25519.CalculateAgreement(data, 0, encoded, 0, buf, off))
                 throw new InvalidOperationException("X25519 agreement failed");
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void GenerateSecret(X25519PublicKeyParameters publicKey, Span<byte> buf)
+        {
+            Span<byte> encoded = stackalloc byte[X25519.PointSize];
+            publicKey.Encode(encoded);
+            if (!X25519.CalculateAgreement(data, encoded, buf))
+                throw new InvalidOperationException("X25519 agreement failed");
         }
+#endif
 
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs b/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs
index c28e4de16..5d94ac10a 100644
--- a/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X25519PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X25519PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -37,6 +48,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -45,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs b/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs
index d10a9035f..555773b10 100644
--- a/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs
@@ -33,6 +33,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X448PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X448PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -45,6 +56,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -52,23 +70,43 @@ namespace Org.BouncyCastle.Crypto.Parameters
 
         public X448PublicKeyParameters GeneratePublicKey()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> publicKey = stackalloc byte[X448.PointSize];
+            X448.GeneratePublicKey(data, publicKey);
+            return new X448PublicKeyParameters(publicKey);
+#else
             byte[] publicKey = new byte[X448.PointSize];
             X448.GeneratePublicKey(data, 0, publicKey, 0);
             return new X448PublicKeyParameters(publicKey, 0);
+#endif
         }
 
         public void GenerateSecret(X448PublicKeyParameters publicKey, byte[] buf, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GenerateSecret(publicKey, buf.AsSpan(off));
+#else
             byte[] encoded = new byte[X448.PointSize];
             publicKey.Encode(encoded, 0);
             if (!X448.CalculateAgreement(data, 0, encoded, 0, buf, off))
                 throw new InvalidOperationException("X448 agreement failed");
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void GenerateSecret(X448PublicKeyParameters publicKey, Span<byte> buf)
+        {
+            Span<byte> encoded = stackalloc byte[X448.PointSize];
+            publicKey.Encode(encoded);
+            if (!X448.CalculateAgreement(data, encoded, buf))
+                throw new InvalidOperationException("X448 agreement failed");
         }
+#endif
 
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X448PublicKeyParameters.cs b/crypto/src/crypto/parameters/X448PublicKeyParameters.cs
index 704d2f7c1..94db22147 100644
--- a/crypto/src/crypto/parameters/X448PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X448PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X448PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X448PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -37,6 +48,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -45,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/prng/BasicEntropySourceProvider.cs b/crypto/src/crypto/prng/BasicEntropySourceProvider.cs
index 31a8461f0..5de1e4e5e 100644
--- a/crypto/src/crypto/prng/BasicEntropySourceProvider.cs
+++ b/crypto/src/crypto/prng/BasicEntropySourceProvider.cs
@@ -21,6 +21,9 @@ namespace Org.BouncyCastle.Crypto.Prng
          */
         public BasicEntropySourceProvider(SecureRandom secureRandom, bool isPredictionResistant)
         {
+            if (secureRandom == null)
+                throw new ArgumentNullException(nameof(secureRandom));
+
             mSecureRandom = secureRandom;
             mPredictionResistant = isPredictionResistant;
         }
@@ -46,6 +49,9 @@ namespace Org.BouncyCastle.Crypto.Prng
 
             internal BasicEntropySource(SecureRandom secureRandom, bool predictionResistant, int entropySize)
             {
+                if (secureRandom == null)
+                    throw new ArgumentNullException(nameof(secureRandom));
+
                 this.mSecureRandom = secureRandom;
                 this.mPredictionResistant = predictionResistant;
                 this.mEntropySize = entropySize;
@@ -62,6 +68,15 @@ namespace Org.BouncyCastle.Crypto.Prng
                 return SecureRandom.GetNextBytes(mSecureRandom, (mEntropySize + 7) / 8);
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int IEntropySource.GetEntropy(Span<byte> output)
+            {
+                int length = (mEntropySize + 7) / 8;
+                mSecureRandom.NextBytes(output[..length]);
+                return length;
+            }
+#endif
+
             int IEntropySource.EntropySize
             {
                 get { return mEntropySize; }
diff --git a/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs b/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs
index 635af2bd9..9a2f6de2c 100644
--- a/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs
+++ b/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs
@@ -56,6 +56,15 @@ namespace Org.BouncyCastle.Crypto.Prng
                 return result;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int IEntropySource.GetEntropy(Span<byte> output)
+            {
+                int length = (mEntropySize + 7) / 8;
+                mRng.GetBytes(output[..length]);
+                return length;
+            }
+#endif
+
             int IEntropySource.EntropySize
             {
                 get { return mEntropySize; }
diff --git a/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs b/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs
index 7803ddd3d..44a9c261f 100644
--- a/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs
+++ b/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs
@@ -6,40 +6,50 @@ namespace Org.BouncyCastle.Crypto.Prng
     /// <summary>
     /// Uses RandomNumberGenerator.Create() to get randomness generator
     /// </summary>
-    public class CryptoApiRandomGenerator
+    public sealed class CryptoApiRandomGenerator
         : IRandomGenerator
     {
-        private readonly RandomNumberGenerator rndProv;
+        private readonly RandomNumberGenerator m_randomNumberGenerator;
 
         public CryptoApiRandomGenerator()
             : this(RandomNumberGenerator.Create())
         {
         }
 
-        public CryptoApiRandomGenerator(RandomNumberGenerator rng)
+        public CryptoApiRandomGenerator(RandomNumberGenerator randomNumberGenerator)
         {
-            this.rndProv = rng;
+            m_randomNumberGenerator = randomNumberGenerator;
         }
 
         #region IRandomGenerator Members
 
-        public virtual void AddSeedMaterial(byte[] seed)
+        public void AddSeedMaterial(byte[] seed)
         {
             // We don't care about the seed
         }
 
-        public virtual void AddSeedMaterial(long seed)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void AddSeedMaterial(ReadOnlySpan<byte> inSeed)
         {
             // We don't care about the seed
         }
+#endif
 
-        public virtual void NextBytes(byte[] bytes)
+        public void AddSeedMaterial(long seed)
         {
-            rndProv.GetBytes(bytes);
+            // We don't care about the seed
+        }
+
+        public void NextBytes(byte[] bytes)
+        {
+            m_randomNumberGenerator.GetBytes(bytes);
         }
 
-        public virtual void NextBytes(byte[] bytes, int start, int len)
+        public void NextBytes(byte[] bytes, int start, int len)
         {
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_0_OR_GREATER
+            m_randomNumberGenerator.GetBytes(bytes, start, len);
+#else
             if (start < 0)
                 throw new ArgumentException("Start offset cannot be negative", "start");
             if (bytes.Length < (start + len))
@@ -55,7 +65,15 @@ namespace Org.BouncyCastle.Crypto.Prng
                 NextBytes(tmpBuf);
                 Array.Copy(tmpBuf, 0, bytes, start, len);
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void NextBytes(Span<byte> bytes)
+        {
+            m_randomNumberGenerator.GetBytes(bytes);
         }
+#endif
 
         #endregion
     }
diff --git a/crypto/src/crypto/prng/DigestRandomGenerator.cs b/crypto/src/crypto/prng/DigestRandomGenerator.cs
index 024db2852..c466a1585 100644
--- a/crypto/src/crypto/prng/DigestRandomGenerator.cs
+++ b/crypto/src/crypto/prng/DigestRandomGenerator.cs
@@ -12,7 +12,7 @@ namespace Org.BouncyCastle.Crypto.Prng
 	 * Internal access to the digest is synchronized so a single one of these can be shared.
 	 * </p>
 	 */
-	public class DigestRandomGenerator
+	public sealed class DigestRandomGenerator
 		: IRandomGenerator
 	{
 		private const long CYCLE_COUNT = 10;
@@ -23,8 +23,7 @@ namespace Org.BouncyCastle.Crypto.Prng
 		private byte[]	state;
 		private byte[]	seed;
 
-		public DigestRandomGenerator(
-			IDigest digest)
+		public DigestRandomGenerator(IDigest digest)
 		{
 			this.digest = digest;
 
@@ -35,8 +34,7 @@ namespace Org.BouncyCastle.Crypto.Prng
 			this.stateCounter = 1;
 		}
 
-		public void AddSeedMaterial(
-			byte[] inSeed)
+		public void AddSeedMaterial(byte[] inSeed)
 		{
 			lock (this)
 			{
@@ -49,8 +47,22 @@ namespace Org.BouncyCastle.Crypto.Prng
 			}
 		}
 
-		public void AddSeedMaterial(
-			long rSeed)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void AddSeedMaterial(ReadOnlySpan<byte> inSeed)
+        {
+            lock (this)
+            {
+                if (!inSeed.IsEmpty)
+                {
+                    DigestUpdate(inSeed);
+                }
+                DigestUpdate(seed);
+                DigestDoFinal(seed);
+            }
+        }
+#endif
+
+        public void AddSeedMaterial(long rSeed)
 		{
 			lock (this)
 			{
@@ -60,17 +72,16 @@ namespace Org.BouncyCastle.Crypto.Prng
 			}
 		}
 
-		public void NextBytes(
-			byte[] bytes)
+		public void NextBytes(byte[] bytes)
 		{
 			NextBytes(bytes, 0, bytes.Length);
 		}
 
-		public void NextBytes(
-			byte[]	bytes,
-			int		start,
-			int		len)
+		public void NextBytes(byte[] bytes, int start, int len)
 		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			NextBytes(bytes.AsSpan(start, len));
+#else
 			lock (this)
 			{
 				int stateOff = 0;
@@ -88,7 +99,30 @@ namespace Org.BouncyCastle.Crypto.Prng
 					bytes[i] = state[stateOff++];
 				}
 			}
+#endif
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void NextBytes(Span<byte> bytes)
+		{
+			lock (this)
+			{
+				int stateOff = 0;
+
+				GenerateState();
+
+				for (int i = 0; i < bytes.Length; ++i)
+				{
+					if (stateOff == state.Length)
+					{
+						GenerateState();
+						stateOff = 0;
+					}
+					bytes[i] = state[stateOff++];
+				}
+			}
 		}
+#endif
 
 		private void CycleSeed()
 		{
@@ -110,21 +144,40 @@ namespace Org.BouncyCastle.Crypto.Prng
 			}
 		}
 
-		private void DigestAddCounter(long seedVal)
-		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void DigestAddCounter(long seedVal)
+        {
+            Span<byte> bytes = stackalloc byte[8];
+            Pack.UInt64_To_LE((ulong)seedVal, bytes);
+            digest.BlockUpdate(bytes);
+        }
+
+        private void DigestUpdate(ReadOnlySpan<byte> inSeed)
+        {
+            digest.BlockUpdate(inSeed);
+        }
+
+        private void DigestDoFinal(Span<byte> result)
+        {
+            digest.DoFinal(result);
+        }
+#else
+        private void DigestAddCounter(long seedVal)
+        {
             byte[] bytes = new byte[8];
             Pack.UInt64_To_LE((ulong)seedVal, bytes);
             digest.BlockUpdate(bytes, 0, bytes.Length);
-		}
+        }
 
-        private void DigestUpdate(byte[] inSeed)
+		private void DigestUpdate(byte[] inSeed)
 		{
 			digest.BlockUpdate(inSeed, 0, inSeed.Length);
 		}
 
-		private void DigestDoFinal(byte[] result)
+        private void DigestDoFinal(byte[] result)
 		{
 			digest.DoFinal(result, 0);
 		}
-	}
+#endif
+    }
 }
diff --git a/crypto/src/crypto/prng/IRandomGenerator.cs b/crypto/src/crypto/prng/IRandomGenerator.cs
index 8dbe4068f..c22d22f61 100644
--- a/crypto/src/crypto/prng/IRandomGenerator.cs
+++ b/crypto/src/crypto/prng/IRandomGenerator.cs
@@ -9,9 +9,13 @@ namespace Org.BouncyCastle.Crypto.Prng
 		/// <param name="seed">A byte array to be mixed into the generator's state.</param>
 		void AddSeedMaterial(byte[] seed);
 
-		/// <summary>Add more seed material to the generator.</summary>
-		/// <param name="seed">A long value to be mixed into the generator's state.</param>
-		void AddSeedMaterial(long seed);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void AddSeedMaterial(ReadOnlySpan<byte> seed);
+#endif
+
+        /// <summary>Add more seed material to the generator.</summary>
+        /// <param name="seed">A long value to be mixed into the generator's state.</param>
+        void AddSeedMaterial(long seed);
 
 		/// <summary>Fill byte array with random values.</summary>
 		/// <param name="bytes">Array to be filled.</param>
@@ -22,5 +26,9 @@ namespace Org.BouncyCastle.Crypto.Prng
 		/// <param name="start">Index to start filling at.</param>
 		/// <param name="len">Length of segment to fill.</param>
 		void NextBytes(byte[] bytes, int start, int len);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		void NextBytes(Span<byte> bytes);
+#endif
 	}
 }
diff --git a/crypto/src/crypto/prng/ReversedWindowGenerator.cs b/crypto/src/crypto/prng/ReversedWindowGenerator.cs
deleted file mode 100644
index dd28c525a..000000000
--- a/crypto/src/crypto/prng/ReversedWindowGenerator.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-
-namespace Org.BouncyCastle.Crypto.Prng
-{
-	/// <remarks>
-	/// Takes bytes generated by an underling RandomGenerator and reverses the order in
-	/// each small window (of configurable size).
-	/// <p>
-	/// Access to internals is synchronized so a single one of these can be shared.
-	/// </p>
-	/// </remarks>
-	public class ReversedWindowGenerator
-		: IRandomGenerator
-	{
-		private readonly IRandomGenerator generator;
-
-		private byte[] window;
-		private int windowCount;
-
-		public ReversedWindowGenerator(
-			IRandomGenerator	generator,
-			int					windowSize)
-		{
-			if (generator == null)
-				throw new ArgumentNullException("generator");
-			if (windowSize < 2)
-				throw new ArgumentException("Window size must be at least 2", "windowSize");
-
-			this.generator = generator;
-			this.window = new byte[windowSize];
-		}
-
-		/// <summary>Add more seed material to the generator.</summary>
-		/// <param name="seed">A byte array to be mixed into the generator's state.</param>
-		public virtual void AddSeedMaterial(
-			byte[] seed)
-		{
-			lock (this)
-			{
-				windowCount = 0;
-				generator.AddSeedMaterial(seed);
-			}
-		}
-
-		/// <summary>Add more seed material to the generator.</summary>
-		/// <param name="seed">A long value to be mixed into the generator's state.</param>
-		public virtual void AddSeedMaterial(
-			long seed)
-		{
-			lock (this)
-			{
-				windowCount = 0;
-				generator.AddSeedMaterial(seed);
-			}
-		}
-
-		/// <summary>Fill byte array with random values.</summary>
-		/// <param name="bytes">Array to be filled.</param>
-		public virtual void NextBytes(
-			byte[] bytes)
-		{
-			doNextBytes(bytes, 0, bytes.Length);
-		}
-
-		/// <summary>Fill byte array with random values.</summary>
-		/// <param name="bytes">Array to receive bytes.</param>
-		/// <param name="start">Index to start filling at.</param>
-		/// <param name="len">Length of segment to fill.</param>
-		public virtual void NextBytes(
-			byte[]	bytes,
-			int		start,
-			int		len)
-		{
-			doNextBytes(bytes, start, len);
-		}
-
-		private void doNextBytes(
-			byte[]	bytes,
-			int		start,
-			int		len)
-		{
-			lock (this)
-			{
-				int done = 0;
-				while (done < len)
-				{
-					if (windowCount < 1)
-					{
-						generator.NextBytes(window, 0, window.Length);
-						windowCount = window.Length;
-					}
-
-					bytes[start + done++] = window[--windowCount];
-				}
-			}
-		}
-	}
-}
diff --git a/crypto/src/crypto/prng/SP800SecureRandom.cs b/crypto/src/crypto/prng/SP800SecureRandom.cs
index 2e1484125..a18576d03 100644
--- a/crypto/src/crypto/prng/SP800SecureRandom.cs
+++ b/crypto/src/crypto/prng/SP800SecureRandom.cs
@@ -15,8 +15,9 @@ namespace Org.BouncyCastle.Crypto.Prng
 
         private ISP80090Drbg mDrbg;
 
-        internal SP800SecureRandom(SecureRandom randomSource, IEntropySource entropySource, IDrbgProvider drbgProvider, bool predictionResistant)
-            : base((IRandomGenerator)null)
+        internal SP800SecureRandom(SecureRandom randomSource, IEntropySource entropySource, IDrbgProvider drbgProvider,
+            bool predictionResistant)
+            : base(null)
         {
             this.mRandomSource = randomSource;
             this.mEntropySource = entropySource;
@@ -35,6 +36,19 @@ namespace Org.BouncyCastle.Crypto.Prng
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void SetSeed(Span<byte> seed)
+        {
+            lock (this)
+            {
+                if (mRandomSource != null)
+                {
+                    this.mRandomSource.SetSeed(seed);
+                }
+            }
+        }
+#endif
+
         public override void SetSeed(long seed)
         {
             lock (this)
@@ -54,6 +68,9 @@ namespace Org.BouncyCastle.Crypto.Prng
 
         public override void NextBytes(byte[] buf, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            NextBytes(buf.AsSpan(off, len));
+#else
             lock (this)
             {
                 if (mDrbg == null)
@@ -68,7 +85,28 @@ namespace Org.BouncyCastle.Crypto.Prng
                     mDrbg.Generate(buf, off, len, null, mPredictionResistant);
                 }
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void NextBytes(Span<byte> buffer)
+        {
+            lock (this)
+            {
+                if (mDrbg == null)
+                {
+                    mDrbg = mDrbgProvider.Get(mEntropySource);
+                }
+
+                // check if a reseed is required...
+                if (mDrbg.Generate(buffer, mPredictionResistant) < 0)
+                {
+                    mDrbg.Reseed(ReadOnlySpan<byte>.Empty);
+                    mDrbg.Generate(buffer, mPredictionResistant);
+                }
+            }
         }
+#endif
 
         public override byte[] GenerateSeed(int numBytes)
         {
diff --git a/crypto/src/crypto/prng/SP800SecureRandomBuilder.cs b/crypto/src/crypto/prng/SP800SecureRandomBuilder.cs
index 7199f1ae7..8159f4822 100644
--- a/crypto/src/crypto/prng/SP800SecureRandomBuilder.cs
+++ b/crypto/src/crypto/prng/SP800SecureRandomBuilder.cs
@@ -26,7 +26,7 @@ namespace Org.BouncyCastle.Crypto.Prng
          * </p>
          */
         public SP800SecureRandomBuilder()
-            : this(new SecureRandom(), false)
+            : this(CryptoServicesRegistrar.GetSecureRandom(), false)
         {
         }
 
@@ -42,6 +42,9 @@ namespace Org.BouncyCastle.Crypto.Prng
          */
         public SP800SecureRandomBuilder(SecureRandom entropySource, bool predictionResistant)
         {
+            if (entropySource == null)
+                throw new ArgumentNullException(nameof(entropySource));
+
             this.mRandom = entropySource;
             this.mEntropySourceProvider = new BasicEntropySourceProvider(entropySource, predictionResistant);
         }
diff --git a/crypto/src/crypto/prng/VMPCRandomGenerator.cs b/crypto/src/crypto/prng/VMPCRandomGenerator.cs
index 64f287d13..92d163710 100644
--- a/crypto/src/crypto/prng/VMPCRandomGenerator.cs
+++ b/crypto/src/crypto/prng/VMPCRandomGenerator.cs
@@ -4,11 +4,9 @@ using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Prng
 {
-    public class VmpcRandomGenerator
+    public sealed class VmpcRandomGenerator
         : IRandomGenerator 
     {
-        private byte n = 0;
-
         /// <remarks>
         /// Permutation generated by code:
         /// <code>
@@ -28,87 +26,115 @@ namespace Org.BouncyCastle.Crypto.Prng
         ///     P[s &amp; 0xff] = temp;
         /// } </code>
         /// </remarks>
-        private byte[] P =
+        private readonly byte[] P =
         {
-            (byte) 0xbb, (byte) 0x2c, (byte) 0x62, (byte) 0x7f, (byte) 0xb5, (byte) 0xaa, (byte) 0xd4,
-            (byte) 0x0d, (byte) 0x81, (byte) 0xfe, (byte) 0xb2, (byte) 0x82, (byte) 0xcb, (byte) 0xa0, (byte) 0xa1,
-            (byte) 0x08, (byte) 0x18, (byte) 0x71, (byte) 0x56, (byte) 0xe8, (byte) 0x49, (byte) 0x02, (byte) 0x10,
-            (byte) 0xc4, (byte) 0xde, (byte) 0x35, (byte) 0xa5, (byte) 0xec, (byte) 0x80, (byte) 0x12, (byte) 0xb8,
-            (byte) 0x69, (byte) 0xda, (byte) 0x2f, (byte) 0x75, (byte) 0xcc, (byte) 0xa2, (byte) 0x09, (byte) 0x36,
-            (byte) 0x03, (byte) 0x61, (byte) 0x2d, (byte) 0xfd, (byte) 0xe0, (byte) 0xdd, (byte) 0x05, (byte) 0x43,
-            (byte) 0x90, (byte) 0xad, (byte) 0xc8, (byte) 0xe1, (byte) 0xaf, (byte) 0x57, (byte) 0x9b, (byte) 0x4c,
-            (byte) 0xd8, (byte) 0x51, (byte) 0xae, (byte) 0x50, (byte) 0x85, (byte) 0x3c, (byte) 0x0a, (byte) 0xe4,
-            (byte) 0xf3, (byte) 0x9c, (byte) 0x26, (byte) 0x23, (byte) 0x53, (byte) 0xc9, (byte) 0x83, (byte) 0x97,
-            (byte) 0x46, (byte) 0xb1, (byte) 0x99, (byte) 0x64, (byte) 0x31, (byte) 0x77, (byte) 0xd5, (byte) 0x1d,
-            (byte) 0xd6, (byte) 0x78, (byte) 0xbd, (byte) 0x5e, (byte) 0xb0, (byte) 0x8a, (byte) 0x22, (byte) 0x38,
-            (byte) 0xf8, (byte) 0x68, (byte) 0x2b, (byte) 0x2a, (byte) 0xc5, (byte) 0xd3, (byte) 0xf7, (byte) 0xbc,
-            (byte) 0x6f, (byte) 0xdf, (byte) 0x04, (byte) 0xe5, (byte) 0x95, (byte) 0x3e, (byte) 0x25, (byte) 0x86,
-            (byte) 0xa6, (byte) 0x0b, (byte) 0x8f, (byte) 0xf1, (byte) 0x24, (byte) 0x0e, (byte) 0xd7, (byte) 0x40,
-            (byte) 0xb3, (byte) 0xcf, (byte) 0x7e, (byte) 0x06, (byte) 0x15, (byte) 0x9a, (byte) 0x4d, (byte) 0x1c,
-            (byte) 0xa3, (byte) 0xdb, (byte) 0x32, (byte) 0x92, (byte) 0x58, (byte) 0x11, (byte) 0x27, (byte) 0xf4,
-            (byte) 0x59, (byte) 0xd0, (byte) 0x4e, (byte) 0x6a, (byte) 0x17, (byte) 0x5b, (byte) 0xac, (byte) 0xff,
-            (byte) 0x07, (byte) 0xc0, (byte) 0x65, (byte) 0x79, (byte) 0xfc, (byte) 0xc7, (byte) 0xcd, (byte) 0x76,
-            (byte) 0x42, (byte) 0x5d, (byte) 0xe7, (byte) 0x3a, (byte) 0x34, (byte) 0x7a, (byte) 0x30, (byte) 0x28,
-            (byte) 0x0f, (byte) 0x73, (byte) 0x01, (byte) 0xf9, (byte) 0xd1, (byte) 0xd2, (byte) 0x19, (byte) 0xe9,
-            (byte) 0x91, (byte) 0xb9, (byte) 0x5a, (byte) 0xed, (byte) 0x41, (byte) 0x6d, (byte) 0xb4, (byte) 0xc3,
-            (byte) 0x9e, (byte) 0xbf, (byte) 0x63, (byte) 0xfa, (byte) 0x1f, (byte) 0x33, (byte) 0x60, (byte) 0x47,
-            (byte) 0x89, (byte) 0xf0, (byte) 0x96, (byte) 0x1a, (byte) 0x5f, (byte) 0x93, (byte) 0x3d, (byte) 0x37,
-            (byte) 0x4b, (byte) 0xd9, (byte) 0xa8, (byte) 0xc1, (byte) 0x1b, (byte) 0xf6, (byte) 0x39, (byte) 0x8b,
-            (byte) 0xb7, (byte) 0x0c, (byte) 0x20, (byte) 0xce, (byte) 0x88, (byte) 0x6e, (byte) 0xb6, (byte) 0x74,
-            (byte) 0x8e, (byte) 0x8d, (byte) 0x16, (byte) 0x29, (byte) 0xf2, (byte) 0x87, (byte) 0xf5, (byte) 0xeb,
-            (byte) 0x70, (byte) 0xe3, (byte) 0xfb, (byte) 0x55, (byte) 0x9f, (byte) 0xc6, (byte) 0x44, (byte) 0x4a,
-            (byte) 0x45, (byte) 0x7d, (byte) 0xe2, (byte) 0x6b, (byte) 0x5c, (byte) 0x6c, (byte) 0x66, (byte) 0xa9,
-            (byte) 0x8c, (byte) 0xee, (byte) 0x84, (byte) 0x13, (byte) 0xa7, (byte) 0x1e, (byte) 0x9d, (byte) 0xdc,
-            (byte) 0x67, (byte) 0x48, (byte) 0xba, (byte) 0x2e, (byte) 0xe6, (byte) 0xa4, (byte) 0xab, (byte) 0x7c,
-            (byte) 0x94, (byte) 0x00, (byte) 0x21, (byte) 0xef, (byte) 0xea, (byte) 0xbe, (byte) 0xca, (byte) 0x72,
-            (byte) 0x4f, (byte) 0x52, (byte) 0x98, (byte) 0x3f, (byte) 0xc2, (byte) 0x14, (byte) 0x7b, (byte) 0x3b,
-            (byte) 0x54
+            0xbb, 0x2c, 0x62, 0x7f, 0xb5, 0xaa, 0xd4, 0x0d, 0x81, 0xfe, 0xb2, 0x82, 0xcb, 0xa0, 0xa1, 0x08,
+            0x18, 0x71, 0x56, 0xe8, 0x49, 0x02, 0x10, 0xc4, 0xde, 0x35, 0xa5, 0xec, 0x80, 0x12, 0xb8, 0x69,
+            0xda, 0x2f, 0x75, 0xcc, 0xa2, 0x09, 0x36, 0x03, 0x61, 0x2d, 0xfd, 0xe0, 0xdd, 0x05, 0x43, 0x90,
+            0xad, 0xc8, 0xe1, 0xaf, 0x57, 0x9b, 0x4c, 0xd8, 0x51, 0xae, 0x50, 0x85, 0x3c, 0x0a, 0xe4, 0xf3,
+            0x9c, 0x26, 0x23, 0x53, 0xc9, 0x83, 0x97, 0x46, 0xb1, 0x99, 0x64, 0x31, 0x77, 0xd5, 0x1d, 0xd6,
+            0x78, 0xbd, 0x5e, 0xb0, 0x8a, 0x22, 0x38, 0xf8, 0x68, 0x2b, 0x2a, 0xc5, 0xd3, 0xf7, 0xbc, 0x6f,
+            0xdf, 0x04, 0xe5, 0x95, 0x3e, 0x25, 0x86, 0xa6, 0x0b, 0x8f, 0xf1, 0x24, 0x0e, 0xd7, 0x40, 0xb3,
+            0xcf, 0x7e, 0x06, 0x15, 0x9a, 0x4d, 0x1c, 0xa3, 0xdb, 0x32, 0x92, 0x58, 0x11, 0x27, 0xf4, 0x59,
+            0xd0, 0x4e, 0x6a, 0x17, 0x5b, 0xac, 0xff, 0x07, 0xc0, 0x65, 0x79, 0xfc, 0xc7, 0xcd, 0x76, 0x42,
+            0x5d, 0xe7, 0x3a, 0x34, 0x7a, 0x30, 0x28, 0x0f, 0x73, 0x01, 0xf9, 0xd1, 0xd2, 0x19, 0xe9, 0x91,
+            0xb9, 0x5a, 0xed, 0x41, 0x6d, 0xb4, 0xc3, 0x9e, 0xbf, 0x63, 0xfa, 0x1f, 0x33, 0x60, 0x47, 0x89,
+            0xf0, 0x96, 0x1a, 0x5f, 0x93, 0x3d, 0x37, 0x4b, 0xd9, 0xa8, 0xc1, 0x1b, 0xf6, 0x39, 0x8b, 0xb7,
+            0x0c, 0x20, 0xce, 0x88, 0x6e, 0xb6, 0x74, 0x8e, 0x8d, 0x16, 0x29, 0xf2, 0x87, 0xf5, 0xeb, 0x70,
+            0xe3, 0xfb, 0x55, 0x9f, 0xc6, 0x44, 0x4a, 0x45, 0x7d, 0xe2, 0x6b, 0x5c, 0x6c, 0x66, 0xa9, 0x8c,
+            0xee, 0x84, 0x13, 0xa7, 0x1e, 0x9d, 0xdc, 0x67, 0x48, 0xba, 0x2e, 0xe6, 0xa4, 0xab, 0x7c, 0x94,
+            0x00, 0x21, 0xef, 0xea, 0xbe, 0xca, 0x72, 0x4f, 0x52, 0x98, 0x3f, 0xc2, 0x14, 0x7b, 0x3b, 0x54,
         };
 
         /// <remarks>Value generated in the same way as <c>P</c>.</remarks>
-        private byte s = (byte) 0xbe;
+        private byte s = 0xbe;
+        private byte n = 0;
 
         public VmpcRandomGenerator()
         {
         }
 
-        public virtual void AddSeedMaterial(byte[] seed) 
+        public void AddSeedMaterial(byte[] seed) 
         {
             for (int m = 0; m < seed.Length; m++) 
             {
-                s = P[(s + P[n & 0xff] + seed[m]) & 0xff];
-                byte temp = P[n & 0xff];
-                P[n & 0xff] = P[s & 0xff];
-                P[s & 0xff] = temp;
-                n = (byte) ((n + 1) & 0xff);
+                byte pn = P[n];
+                s = P[(s + pn + seed[m]) & 0xff];
+                P[n] = P[s];
+                P[s] = pn;
+                n = (byte)(n + 1);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void AddSeedMaterial(ReadOnlySpan<byte> seed)
+        {
+            for (int m = 0; m < seed.Length; m++)
+            {
+                byte pn = P[n];
+                s = P[(s + pn + seed[m]) & 0xff];
+                P[n] = P[s];
+                P[s] = pn;
+                n = (byte)(n + 1);
             }
         }
+#endif
 
-        public virtual void AddSeedMaterial(long seed) 
+        public void AddSeedMaterial(long seed) 
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = stackalloc byte[8];
+            Pack.UInt64_To_BE((ulong)seed, bytes);
+            AddSeedMaterial(bytes);
+#else
             AddSeedMaterial(Pack.UInt64_To_BE((ulong)seed));
+#endif
         }
 
-        public virtual void NextBytes(byte[] bytes) 
+        public void NextBytes(byte[] bytes) 
         {
             NextBytes(bytes, 0, bytes.Length);
         }
 
-        public virtual void NextBytes(byte[] bytes, int start, int len) 
+        public void NextBytes(byte[] bytes, int start, int len) 
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            NextBytes(bytes.AsSpan(start, len));
+#else
             lock (P) 
             {
                 int end = start + len;
                 for (int i = start; i != end; i++) 
                 {
-                    s = P[(s + P[n & 0xff]) & 0xff];
-                    bytes[i] = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff];
-                    byte temp = P[n & 0xff];
-                    P[n & 0xff] = P[s & 0xff];
-                    P[s & 0xff] = temp;
-                    n = (byte) ((n + 1) & 0xff);
+                    byte pn = P[n];
+                    s = P[(s + pn) & 0xFF];
+                    byte ps = P[s];
+                    bytes[i] = P[(P[ps] + 1) & 0xFF];
+                    P[s] = pn;
+                    P[n] = ps;
+                    n = (byte)(n + 1);
+                }
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void NextBytes(Span<byte> bytes)
+        {
+            lock (P) 
+            {
+                for (int i = 0; i < bytes.Length; ++i) 
+                {
+                    byte pn = P[n];
+                    s = P[(s + pn) & 0xFF];
+                    byte ps = P[s];
+                    bytes[i] = P[(P[ps] + 1) & 0xFF];
+                    P[s] = pn;
+                    P[n] = ps;
+                    n = (byte)(n + 1);
                 }
             }
         }
+#endif
     }
 }
diff --git a/crypto/src/crypto/prng/X931Rng.cs b/crypto/src/crypto/prng/X931Rng.cs
index 53c982c25..25dba89f5 100644
--- a/crypto/src/crypto/prng/X931Rng.cs
+++ b/crypto/src/crypto/prng/X931Rng.cs
@@ -38,6 +38,66 @@ namespace Org.BouncyCastle.Crypto.Prng
             this.mR = new byte[engine.GetBlockSize()];
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal int Generate(Span<byte> output, bool predictionResistant)
+        {
+            int outputLen = output.Length;
+
+            if (mR.Length == 8) // 64 bit block size
+            {
+                if (mReseedCounter > BLOCK64_RESEED_MAX)
+                    return -1;
+
+                if (outputLen > BLOCK64_MAX_BITS_REQUEST / 8)
+                    throw new ArgumentException("Number of bits per request limited to " + BLOCK64_MAX_BITS_REQUEST, "output");
+            }
+            else
+            {
+                if (mReseedCounter > BLOCK128_RESEED_MAX)
+                    return -1;
+
+                if (outputLen > BLOCK128_MAX_BITS_REQUEST / 8)
+                    throw new ArgumentException("Number of bits per request limited to " + BLOCK128_MAX_BITS_REQUEST, "output");
+            }
+
+            if (predictionResistant || mV == null)
+            {
+                mV = mEntropySource.GetEntropy();
+                if (mV.Length != mEngine.GetBlockSize())
+                    throw new InvalidOperationException("Insufficient entropy returned");
+            }
+
+            int m = outputLen / mR.Length;
+
+            for (int i = 0; i < m; i++)
+            {
+                mEngine.ProcessBlock(mDT, mI);
+                Process(mR, mI, mV);
+                Process(mV, mR, mI);
+
+                mR.CopyTo(output[(i * mR.Length)..]);
+
+                Increment(mDT);
+            }
+
+            int bytesToCopy = outputLen - m * mR.Length;
+
+            if (bytesToCopy > 0)
+            {
+                mEngine.ProcessBlock(mDT, mI);
+                Process(mR, mI, mV);
+                Process(mV, mR, mI);
+
+                mR.AsSpan(0, bytesToCopy).CopyTo(output[(m * mR.Length)..]);
+
+                Increment(mDT);
+            }
+
+            mReseedCounter++;
+
+            return outputLen * 8;
+        }
+#else
         /**
          * Populate a passed in array with random data.
          *
@@ -85,7 +145,7 @@ namespace Org.BouncyCastle.Crypto.Prng
                 Increment(mDT);
             }
 
-            int bytesToCopy = (outputLen - m * mR.Length);
+            int bytesToCopy = outputLen - m * mR.Length;
 
             if (bytesToCopy > 0)
             {
@@ -102,6 +162,7 @@ namespace Org.BouncyCastle.Crypto.Prng
 
             return outputLen * 8;
         }
+#endif
 
         /**
          * Reseed the RNG.
diff --git a/crypto/src/crypto/prng/X931SecureRandom.cs b/crypto/src/crypto/prng/X931SecureRandom.cs
index 1402e5c31..d40134851 100644
--- a/crypto/src/crypto/prng/X931SecureRandom.cs
+++ b/crypto/src/crypto/prng/X931SecureRandom.cs
@@ -12,7 +12,7 @@ namespace Org.BouncyCastle.Crypto.Prng
         private readonly X931Rng        mDrbg;
 
         internal X931SecureRandom(SecureRandom randomSource, X931Rng drbg, bool predictionResistant)
-            : base((IRandomGenerator)null)
+            : base(null)
         {
             this.mRandomSource = randomSource;
             this.mDrbg = drbg;
@@ -30,6 +30,19 @@ namespace Org.BouncyCastle.Crypto.Prng
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void SetSeed(Span<byte> seed)
+        {
+            lock (this)
+            {
+                if (mRandomSource != null)
+                {
+                    this.mRandomSource.SetSeed(seed);
+                }
+            }
+        }
+#endif
+
         public override void SetSeed(long seed)
         {
             lock (this)
@@ -49,6 +62,9 @@ namespace Org.BouncyCastle.Crypto.Prng
 
         public override void NextBytes(byte[] buf, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            NextBytes(buf.AsSpan(off, len));
+#else
             lock (this)
             {
                 // check if a reseed is required...
@@ -58,7 +74,23 @@ namespace Org.BouncyCastle.Crypto.Prng
                     mDrbg.Generate(buf, off, len, mPredictionResistant);
                 }
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void NextBytes(Span<byte> buffer)
+        {
+            lock (this)
+            {
+                // check if a reseed is required...
+                if (mDrbg.Generate(buffer, mPredictionResistant) < 0)
+                {
+                    mDrbg.Reseed();
+                    mDrbg.Generate(buffer, mPredictionResistant);
+                }
+            }
         }
+#endif
 
         public override byte[] GenerateSeed(int numBytes)
         {
diff --git a/crypto/src/crypto/prng/X931SecureRandomBuilder.cs b/crypto/src/crypto/prng/X931SecureRandomBuilder.cs
index 31e94312e..025eac1bb 100644
--- a/crypto/src/crypto/prng/X931SecureRandomBuilder.cs
+++ b/crypto/src/crypto/prng/X931SecureRandomBuilder.cs
@@ -23,7 +23,7 @@ namespace Org.BouncyCastle.Crypto.Prng
          * </p>
          */
         public X931SecureRandomBuilder()
-            : this(new SecureRandom(), false)
+            : this(CryptoServicesRegistrar.GetSecureRandom(), false)
         {
         }
 
@@ -39,6 +39,9 @@ namespace Org.BouncyCastle.Crypto.Prng
          */
         public X931SecureRandomBuilder(SecureRandom entropySource, bool predictionResistant)
         {
+            if (entropySource == null)
+                throw new ArgumentNullException(nameof(entropySource));
+
             this.mRandom = entropySource;
             this.mEntropySourceProvider = new BasicEntropySourceProvider(mRandom, predictionResistant);
         }
diff --git a/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs b/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs
index a7b1326c3..cf566ff9c 100644
--- a/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs
+++ b/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 
@@ -9,7 +10,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	/**
 	 * A SP800-90A CTR DRBG.
 	 */
-	public class CtrSP800Drbg
+	public sealed class CtrSP800Drbg
         :   ISP80090Drbg
 	{
 	    private static readonly long TDEA_RESEED_MAX = 1L << (32 - 1);
@@ -59,20 +60,19 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        mSeedLength = keySizeInBits + engine.GetBlockSize() * 8;
 	        mIsTdea = IsTdea(engine);
 
-	        byte[] entropy = GetEntropy();  // Get_entropy_input
-
-            CTR_DRBG_Instantiate_algorithm(entropy, nonce, personalizationString);
+            CTR_DRBG_Instantiate_algorithm(nonce, personalizationString);
 	    }
 
-        private void CTR_DRBG_Instantiate_algorithm(byte[] entropy, byte[] nonce, byte[] personalisationString)
+        private void CTR_DRBG_Instantiate_algorithm(byte[] nonce, byte[] personalisationString)
 	    {
-	        byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalisationString);
-	        byte[] seed = Block_Cipher_df(seedMaterial, mSeedLength);
+            byte[] entropy = GetEntropy();  // Get_entropy_input
+            byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalisationString);
+	        byte[] seed = BlockCipherDF(seedMaterial, mSeedLength / 8);
 
-            int outlen = mEngine.GetBlockSize();
+            int blockSize = mEngine.GetBlockSize();
 
             mKey = new byte[(mKeySizeInBits + 7) / 8];
-	        mV = new byte[outlen];
+	        mV = new byte[blockSize];
 
 	        // mKey & mV are modified by this call
 	        CTR_DRBG_Update(seed, mKey, mV); 
@@ -80,55 +80,130 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
             mReseedCounter = 1;
 	    }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void CTR_DRBG_Update(ReadOnlySpan<byte> seed, Span<byte> key, Span<byte> v)
+        {
+			int seedLength = seed.Length;
+            Span<byte> temp = seedLength <= 256
+				? stackalloc byte[seedLength]
+                : new byte[seedLength];
+
+			int blockSize = mEngine.GetBlockSize();
+            Span<byte> block = blockSize <= 64
+				? stackalloc byte[blockSize]
+                : new byte[blockSize];
+
+			mEngine.Init(true, ExpandToKeyParameter(key));
+            for (int i = 0; i * blockSize < seed.Length; ++i)
+            {
+                AddOneTo(v);
+                mEngine.ProcessBlock(v, block);
+
+                int bytesToCopy = System.Math.Min(blockSize, temp.Length - i * blockSize);
+				block[..bytesToCopy].CopyTo(temp[(i * blockSize)..]);
+            }
+
+			XorWith(seed, temp);
+
+			key.CopyFrom(temp);
+			v.CopyFrom(temp[key.Length..]);
+        }
+#else
         private void CTR_DRBG_Update(byte[] seed, byte[] key, byte[] v)
 	    {
-	        byte[] temp = new byte[seed.Length];
+			byte[] temp = new byte[seed.Length];
 	        byte[] outputBlock = new byte[mEngine.GetBlockSize()];
 
             int i = 0;
 	        int outLen = mEngine.GetBlockSize();
 
-            mEngine.Init(true, new KeyParameter(ExpandKey(key)));
-	        while (i*outLen < seed.Length)
+			mEngine.Init(true, ExpandToKeyParameter(key));
+	        while (i * outLen < seed.Length)
 	        {
 	            AddOneTo(v);
 	            mEngine.ProcessBlock(v, 0, outputBlock, 0);
 
-	            int bytesToCopy = ((temp.Length - i * outLen) > outLen)
-	                    ? outLen : (temp.Length - i * outLen);
-	            
+				int bytesToCopy = System.Math.Min(outLen, temp.Length - i * outLen);
 	            Array.Copy(outputBlock, 0, temp, i * outLen, bytesToCopy);
 	            ++i;
 	        }
 
-	        XOR(temp, seed, temp, 0);
+	        Xor(temp, seed, temp, 0);
 
 	        Array.Copy(temp, 0, key, 0, key.Length);
 	        Array.Copy(temp, key.Length, v, 0, v.Length);
-	    }
-	    
-	    private void CTR_DRBG_Reseed_algorithm(byte[] additionalInput)
-	    {
-	        byte[] seedMaterial = Arrays.Concatenate(GetEntropy(), additionalInput);
+        }
+#endif
+
+        private void CTR_DRBG_Reseed_algorithm(byte[] additionalInput)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			CTR_DRBG_Reseed_algorithm(Spans.FromNullableReadOnly(additionalInput));
+#else
+			byte[] seedMaterial = Arrays.Concatenate(GetEntropy(), additionalInput);
 
-	        seedMaterial = Block_Cipher_df(seedMaterial, mSeedLength);
+            seedMaterial = BlockCipherDF(seedMaterial, mSeedLength / 8);
 
             CTR_DRBG_Update(seedMaterial, mKey, mV);
 
             mReseedCounter = 1;
-	    }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void CTR_DRBG_Reseed_algorithm(ReadOnlySpan<byte> additionalInput)
+        {
+			int entropyLength = GetEntropyLength();
+			int seedLength = entropyLength + additionalInput.Length;
+
+			Span<byte> seedMaterial = seedLength <= 256
+				? stackalloc byte[seedLength]
+				: new byte[seedLength];
+
+			GetEntropy(seedMaterial[..entropyLength]);
+			additionalInput.CopyTo(seedMaterial[entropyLength..]);
+
+            seedMaterial = BlockCipherDF(seedMaterial, mSeedLength / 8);
 
-        private void XOR(byte[] output, byte[] a, byte[] b, int bOff)
+            CTR_DRBG_Update(seedMaterial, mKey, mV);
+
+            mReseedCounter = 1;
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Xor(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y, Span<byte> z)
+        {
+            for (int i = 0; i < z.Length; ++i)
+            {
+                z[i] = (byte)(x[i] ^ y[i]);
+            }
+        }
+
+        private void XorWith(ReadOnlySpan<byte> x, Span<byte> z)
+        {
+            for (int i = 0; i < z.Length; ++i)
+            {
+				z[i] ^= x[i];
+            }
+        }
+#else
+        private void Xor(byte[] output, byte[] a, byte[] b, int bOff)
 	    {
             for (int i = 0; i < output.Length; i++) 
 	        {
                 output[i] = (byte)(a[i] ^ b[bOff + i]);
 	        }
 	    }
-
-        private void AddOneTo(byte[] longer)
-	    {
-	        uint carry = 1;
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void AddOneTo(Span<byte> longer)
+#else
+		private void AddOneTo(byte[] longer)
+#endif
+        {
+            uint carry = 1;
             int i = longer.Length;
             while (--i >= 0)
             {
@@ -146,83 +221,101 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        return entropy;
 	    }
 
-	    // -- Internal state migration ---
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int GetEntropy(Span<byte> output)
+        {
+			int length = mEntropySource.GetEntropy(output);
+            if (length < (mSecurityStrength + 7) / 8)
+                throw new InvalidOperationException("Insufficient entropy provided by entropy source");
+			return length;
+        }
 
-        private static readonly byte[] K_BITS = Hex.DecodeStrict("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
+		private int GetEntropyLength()
+		{
+			return (mEntropySource.EntropySize + 7) / 8;
+		}
+#endif
+
+        // -- Internal state migration ---
+
+        private static readonly byte[] K_BITS = Hex.DecodeStrict(
+			"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
 
         // 1. If (number_of_bits_to_return > max_number_of_bits), then return an
-	    // ERROR_FLAG.
-	    // 2. L = len (input_string)/8.
-	    // 3. N = number_of_bits_to_return/8.
-	    // Comment: L is the bitstring represention of
-	    // the integer resulting from len (input_string)/8.
-	    // L shall be represented as a 32-bit integer.
-	    //
-	    // Comment : N is the bitstring represention of
-	    // the integer resulting from
-	    // number_of_bits_to_return/8. N shall be
-	    // represented as a 32-bit integer.
-	    //
-	    // 4. S = L || N || input_string || 0x80.
-	    // 5. While (len (S) mod outlen)
-	    // Comment : Pad S with zeros, if necessary.
-	    // 0, S = S || 0x00.
-	    //
-	    // Comment : Compute the starting value.
-	    // 6. temp = the Null string.
-	    // 7. i = 0.
-	    // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F.
-	    // 9. While len (temp) < keylen + outlen, do
-	    //
-	    // IV = i || 0outlen - len (i).
-	    //
-	    // 9.1
-	    //
-	    // temp = temp || BCC (K, (IV || S)).
-	    //
-	    // 9.2
-	    //
-	    // i = i + 1.
-	    //
-	    // 9.3
-	    //
-	    // Comment : i shall be represented as a 32-bit
-	    // integer, i.e., len (i) = 32.
-	    //
-	    // Comment: The 32-bit integer represenation of
-	    // i is padded with zeros to outlen bits.
-	    //
-	    // Comment: Compute the requested number of
-	    // bits.
-	    //
-	    // 10. K = Leftmost keylen bits of temp.
-	    //
-	    // 11. X = Next outlen bits of temp.
-	    //
-	    // 12. temp = the Null string.
-	    //
-	    // 13. While len (temp) < number_of_bits_to_return, do
-	    //
-	    // 13.1 X = Block_Encrypt (K, X).
-	    //
-	    // 13.2 temp = temp || X.
-	    //
-	    // 14. requested_bits = Leftmost number_of_bits_to_return of temp.
-	    //
-	    // 15. Return SUCCESS and requested_bits.
-	    private byte[] Block_Cipher_df(byte[] inputString, int bitLength)
-	    {
-	        int outLen = mEngine.GetBlockSize();
-	        int L = inputString.Length; // already in bytes
-	        int N = bitLength / 8;
-	        // 4 S = L || N || inputstring || 0x80
+        // ERROR_FLAG.
+        // 2. L = len (input_string)/8.
+        // 3. N = number_of_bits_to_return/8.
+        // Comment: L is the bitstring represention of
+        // the integer resulting from len (input_string)/8.
+        // L shall be represented as a 32-bit integer.
+        //
+        // Comment : N is the bitstring represention of
+        // the integer resulting from
+        // number_of_bits_to_return/8. N shall be
+        // represented as a 32-bit integer.
+        //
+        // 4. S = L || N || input_string || 0x80.
+        // 5. While (len (S) mod outlen)
+        // Comment : Pad S with zeros, if necessary.
+        // 0, S = S || 0x00.
+        //
+        // Comment : Compute the starting value.
+        // 6. temp = the Null string.
+        // 7. i = 0.
+        // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F.
+        // 9. While len (temp) < keylen + outlen, do
+        //
+        // IV = i || 0outlen - len (i).
+        //
+        // 9.1
+        //
+        // temp = temp || BCC (K, (IV || S)).
+        //
+        // 9.2
+        //
+        // i = i + 1.
+        //
+        // 9.3
+        //
+        // Comment : i shall be represented as a 32-bit
+        // integer, i.e., len (i) = 32.
+        //
+        // Comment: The 32-bit integer represenation of
+        // i is padded with zeros to outlen bits.
+        //
+        // Comment: Compute the requested number of
+        // bits.
+        //
+        // 10. K = Leftmost keylen bits of temp.
+        //
+        // 11. X = Next outlen bits of temp.
+        //
+        // 12. temp = the Null string.
+        //
+        // 13. While len (temp) < number_of_bits_to_return, do
+        //
+        // 13.1 X = Block_Encrypt (K, X).
+        //
+        // 13.2 temp = temp || X.
+        //
+        // 14. requested_bits = Leftmost number_of_bits_to_return of temp.
+        //
+        // 15. Return SUCCESS and requested_bits.
+		private byte[] BlockCipherDF(byte[] input, int N)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BlockCipherDF(input.AsSpan(), N);
+#else
+            int outLen = mEngine.GetBlockSize();
+	        int L = input.Length; // already in bytes
+	        // 4 S = L || N || input || 0x80
 	        int sLen = 4 + 4 + L + 1;
 	        int blockLen = ((sLen + outLen - 1) / outLen) * outLen;
 	        byte[] S = new byte[blockLen];
-	        copyIntToByteArray(S, L, 0);
-	        copyIntToByteArray(S, N, 4);
-	        Array.Copy(inputString, 0, S, 8, L);
-	        S[8 + L] = (byte)0x80;
+            Pack.UInt32_To_BE((uint)L, S, 0);
+            Pack.UInt32_To_BE((uint)N, S, 4);
+			Array.Copy(input, 0, S, 8, L);
+	        S[8 + L] = 0x80;
 	        // S already padded with zeros
 
 	        byte[] temp = new byte[mKeySizeInBits / 8 + outLen];
@@ -233,16 +326,15 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        int i = 0;
 	        byte[] K = new byte[mKeySizeInBits / 8];
 	        Array.Copy(K_BITS, 0, K, 0, K.Length);
+            var K1 = ExpandToKeyParameter(K);
+	        mEngine.Init(true, K1);
 
 	        while (i*outLen*8 < mKeySizeInBits + outLen *8)
 	        {
-	            copyIntToByteArray(IV, i, 0);
-	            BCC(bccOut, K, IV, S);
+                Pack.UInt32_To_BE((uint)i, IV, 0);
+                BCC(bccOut, IV, S);
 
-	            int bytesToCopy = ((temp.Length - i * outLen) > outLen)
-	                    ? outLen
-	                    : (temp.Length - i * outLen);
-	            
+                int bytesToCopy = System.Math.Min(outLen, temp.Length - i * outLen);
 	            Array.Copy(bccOut, 0, temp, i * outLen, bytesToCopy);
 	            ++i;
 	        }
@@ -251,39 +343,120 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        Array.Copy(temp, 0, K, 0, K.Length);
 	        Array.Copy(temp, K.Length, X, 0, X.Length);
 
-	        temp = new byte[bitLength / 2];
+	        temp = new byte[N];
 
 	        i = 0;
-	        mEngine.Init(true, new KeyParameter(ExpandKey(K)));
+	        mEngine.Init(true, ExpandToKeyParameter(K));
 
 	        while (i * outLen < temp.Length)
 	        {
 	            mEngine.ProcessBlock(X, 0, X, 0);
 
-	            int bytesToCopy = ((temp.Length - i * outLen) > outLen)
-	                    ? outLen
-	                    : (temp.Length - i * outLen);
-
+				int bytesToCopy = System.Math.Min(outLen, temp.Length - i * outLen);
 	            Array.Copy(X, 0, temp, i * outLen, bytesToCopy);
 	            i++;
 	        }
 
 	        return temp;
-	    }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private byte[] BlockCipherDF(ReadOnlySpan<byte> input, int N)
+        {
+            int blockSize = mEngine.GetBlockSize();
+            int L = input.Length; // already in bytes
+            // 4 S = L || N || input || 0x80
+            int sLen = 4 + 4 + L + 1;
+            int blockLen = ((sLen + blockSize - 1) / blockSize) * blockSize;
+            Span<byte> S = blockLen <= 256
+                ? stackalloc byte[blockLen]
+                : new byte[blockLen];
+            Pack.UInt32_To_BE((uint)L, S);
+            Pack.UInt32_To_BE((uint)N, S[4..]);
+            input.CopyTo(S[8..]);
+            S[8 + L] = 0x80;
+            // S already padded with zeros
+
+            int keySize = mKeySizeInBits / 8;
+            int tempSize = keySize + blockSize;
+            Span<byte> temp = tempSize <= 128
+                ? stackalloc byte[tempSize]
+                : new byte[tempSize];
+
+            Span<byte> bccOut = blockSize <= 64
+                ? stackalloc byte[blockSize]
+                : new byte[blockSize];
+
+            Span<byte> IV = blockSize <= 64
+                ? stackalloc byte[blockSize]
+                : new byte[blockSize];
+
+            var K1 = ExpandToKeyParameter(K_BITS.AsSpan(0, keySize));
+            mEngine.Init(true, K1);
+
+            for (int i = 0; i * blockSize < tempSize; ++i)
+            {
+                Pack.UInt32_To_BE((uint)i, IV);
+                BCC(bccOut, IV, S);
 
-	    /*
-	    * 1. chaining_value = 0^outlen    
-	    *    . Comment: Set the first chaining value to outlen zeros.
-	    * 2. n = len (data)/outlen.
-	    * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits 
-	    *    each, forming block(1) to block(n). 
-	    * 4. For i = 1 to n do
-	    * 4.1 input_block = chaining_value ^ block(i) .
-	    * 4.2 chaining_value = Block_Encrypt (Key, input_block).
-	    * 5. output_block = chaining_value.
-	    * 6. Return output_block. 
-	     */
-	    private void BCC(byte[] bccOut, byte[] k, byte[] iV, byte[] data)
+                int bytesToCopy = System.Math.Min(blockSize, tempSize - i * blockSize);
+                bccOut[..bytesToCopy].CopyTo(temp[(i * blockSize)..]);
+            }
+
+            var K2 = ExpandToKeyParameter(temp[..keySize]);
+            mEngine.Init(true, K2);
+            var X = temp[keySize..];
+
+            byte[] result = new byte[N];
+            for (int i = 0; i * blockSize < result.Length; ++i)
+            {
+                mEngine.ProcessBlock(X, X);
+
+                int bytesToCopy = System.Math.Min(blockSize, result.Length - i * blockSize);
+                X[..bytesToCopy].CopyTo(result.AsSpan(i * blockSize));
+            }
+            return result;
+        }
+#endif
+
+        /*
+        * 1. chaining_value = 0^outlen    
+        *    . Comment: Set the first chaining value to outlen zeros.
+        * 2. n = len (data)/outlen.
+        * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits 
+        *    each, forming block(1) to block(n). 
+        * 4. For i = 1 to n do
+        * 4.1 input_block = chaining_value ^ block(i) .
+        * 4.2 chaining_value = Block_Encrypt (Key, input_block).
+        * 5. output_block = chaining_value.
+        * 6. Return output_block. 
+        */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void BCC(Span<byte> bccOut, ReadOnlySpan<byte> iV, ReadOnlySpan<byte> data)
+        {
+            int blockSize = mEngine.GetBlockSize();
+
+            Span<byte> chainingValue = blockSize <= 64
+                ? stackalloc byte[blockSize]
+                : new byte[blockSize];
+            Span<byte> inputBlock = blockSize <= 64
+                ? stackalloc byte[blockSize]
+                : new byte[blockSize];
+
+            mEngine.ProcessBlock(iV, chainingValue);
+
+            int n = data.Length / blockSize;
+            for (int i = 0; i < n; i++)
+            {
+                Xor(chainingValue, data[(i * blockSize)..], inputBlock);
+                mEngine.ProcessBlock(inputBlock, chainingValue);
+            }
+
+            bccOut.CopyFrom(chainingValue);
+        }
+#else
+        private void BCC(byte[] bccOut, byte[] iV, byte[] data)
 	    {
 	        int outlen = mEngine.GetBlockSize();
 	        byte[] chainingValue = new byte[outlen]; // initial values = 0
@@ -291,33 +464,24 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 
 	        byte[] inputBlock = new byte[outlen];
 
-	        mEngine.Init(true, new KeyParameter(ExpandKey(k)));
-
             mEngine.ProcessBlock(iV, 0, chainingValue, 0);
 
             for (int i = 0; i < n; i++)
 	        {
-	            XOR(inputBlock, chainingValue, data, i*outlen);
+	            Xor(inputBlock, chainingValue, data, i*outlen);
 	            mEngine.ProcessBlock(inputBlock, 0, chainingValue, 0);
 	        }
 
             Array.Copy(chainingValue, 0, bccOut, 0, bccOut.Length);
 	    }
+#endif
 
-	    private void copyIntToByteArray(byte[] buf, int value, int offSet)
-	    {
-	        buf[offSet + 0] = ((byte)(value >> 24));
-	        buf[offSet + 1] = ((byte)(value >> 16));
-	        buf[offSet + 2] = ((byte)(value >> 8));
-	        buf[offSet + 3] = ((byte)(value));
-	    }
-
-	    /**
+        /**
 	     * Return the block size (in bits) of the DRBG.
 	     *
 	     * @return the number of bits produced on each internal round of the DRBG.
 	     */
-	    public int BlockSize
+        public int BlockSize
 	    {
 			get { return mV.Length * 8; }
 	    }
@@ -334,7 +498,12 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	    public int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput,
 			bool predictionResistant)
 	    {
-	        if (mIsTdea)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return additionalInput == null
+                ? Generate(output.AsSpan(outputOff, outputLen), predictionResistant)
+                : GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant);
+#else
+			if (mIsTdea)
 	        {
 	            if (mReseedCounter > TDEA_RESEED_MAX)
 	                return -1;
@@ -359,7 +528,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 
 	        if (additionalInput != null)
 	        {
-	            additionalInput = Block_Cipher_df(additionalInput, mSeedLength);
+	            additionalInput = BlockCipherDF(additionalInput, mSeedLength / 8);
 	            CTR_DRBG_Update(additionalInput, mKey, mV);
 	        }
 	        else
@@ -369,7 +538,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 
             byte[] tmp = new byte[mV.Length];
 
-            mEngine.Init(true, new KeyParameter(ExpandKey(mKey)));
+            mEngine.Init(true, ExpandToKeyParameter(mKey));
 
             for (int i = 0, limit = outputLen / tmp.Length; i <= limit; i++)
 	        {
@@ -390,18 +559,126 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
             mReseedCounter++;
 
             return outputLen * 8;
-	    }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Generate(Span<byte> output, bool predictionResistant)
+        {
+            int outputLen = output.Length;
+            if (mIsTdea)
+            {
+                if (mReseedCounter > TDEA_RESEED_MAX)
+                    return -1;
 
-	    /**
+                if (outputLen > TDEA_MAX_BITS_REQUEST / 8)
+                    throw new ArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST, "output");
+            }
+            else
+            {
+                if (mReseedCounter > AES_RESEED_MAX)
+                    return -1;
+
+                if (outputLen > AES_MAX_BITS_REQUEST / 8)
+                    throw new ArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST, "output");
+            }
+
+            if (predictionResistant)
+            {
+                CTR_DRBG_Reseed_algorithm(ReadOnlySpan<byte>.Empty);
+            }
+
+            byte[] seed = new byte[mSeedLength / 8];
+
+            return ImplGenerate(seed, output);
+        }
+
+        public int GenerateWithInput(Span<byte> output, ReadOnlySpan<byte> additionalInput, bool predictionResistant)
+		{
+			int outputLen = output.Length;
+            if (mIsTdea)
+            {
+                if (mReseedCounter > TDEA_RESEED_MAX)
+                    return -1;
+
+                if (outputLen > TDEA_MAX_BITS_REQUEST / 8)
+                    throw new ArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST, "output");
+            }
+            else
+            {
+                if (mReseedCounter > AES_RESEED_MAX)
+                    return -1;
+
+                if (outputLen > AES_MAX_BITS_REQUEST / 8)
+                    throw new ArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST, "output");
+            }
+
+            int seedLength = mSeedLength / 8;
+            byte[] seed;
+            if (predictionResistant)
+            {
+                CTR_DRBG_Reseed_algorithm(additionalInput);
+                seed = new byte[seedLength];
+            }
+            else
+			{
+                seed = BlockCipherDF(additionalInput, seedLength);
+                CTR_DRBG_Update(seed, mKey, mV);
+            }
+
+			return ImplGenerate(seed, output);
+        }
+
+		private int ImplGenerate(ReadOnlySpan<byte> seed, Span<byte> output)
+		{
+            byte[] tmp = new byte[mV.Length];
+
+            mEngine.Init(true, ExpandToKeyParameter(mKey));
+
+            int outputLen = output.Length;
+            for (int i = 0, limit = outputLen / tmp.Length; i <= limit; i++)
+            {
+                int bytesToCopy = System.Math.Min(tmp.Length, outputLen - i * tmp.Length);
+
+                if (bytesToCopy != 0)
+                {
+                    AddOneTo(mV);
+
+                    mEngine.ProcessBlock(mV, 0, tmp, 0);
+
+                    tmp[..bytesToCopy].CopyTo(output[(i * tmp.Length)..]);
+                }
+            }
+
+            CTR_DRBG_Update(seed, mKey, mV);
+
+            mReseedCounter++;
+
+            return outputLen * 8;
+        }
+#endif
+
+        /**
 	      * Reseed the DRBG.
 	      *
 	      * @param additionalInput additional input to be added to the DRBG in this step.
 	      */
-	    public void Reseed(byte[] additionalInput)
+        public void Reseed(byte[] additionalInput)
 	    {
-	        CTR_DRBG_Reseed_algorithm(additionalInput);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			Reseed(Spans.FromNullableReadOnly(additionalInput));
+#else
+			CTR_DRBG_Reseed_algorithm(additionalInput);
+#endif
 	    }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Reseed(ReadOnlySpan<byte> additionalInput)
+		{
+            CTR_DRBG_Reseed_algorithm(additionalInput);
+        }
+#endif
+
         private bool IsTdea(IBlockCipher cipher)
 	    {
 	        return cipher.AlgorithmName.Equals("DESede") || cipher.AlgorithmName.Equals("TDEA");
@@ -421,26 +698,39 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
             return -1;
 	    }
 
-        private byte[] ExpandKey(byte[] key)
+        private KeyParameter ExpandToKeyParameter(byte[] key)
 	    {
-	        if (mIsTdea)
-	        {
-	            // expand key to 192 bits.
-	            byte[] tmp = new byte[24];
+			if (!mIsTdea)
+				return new KeyParameter(key);
 
-                PadKey(key, 0, tmp, 0);
-                PadKey(key, 7, tmp, 8);
-                PadKey(key, 14, tmp, 16);
+	        // expand key to 192 bits.
+	        byte[] tmp = new byte[24];
 
-	            return tmp;
-	        }
-	        else
-	        {
-	            return key;
-	        }
-	    }
+            PadKey(key, 0, tmp, 0);
+            PadKey(key, 7, tmp, 8);
+            PadKey(key, 14, tmp, 16);
 
-	    /**
+            return new KeyParameter(tmp);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private KeyParameter ExpandToKeyParameter(ReadOnlySpan<byte> key)
+        {
+			if (!mIsTdea)
+				return new KeyParameter(key);
+
+            // expand key to 192 bits.
+            Span<byte> tmp = stackalloc byte[24];
+
+            PadKey(key, tmp);
+			PadKey(key[7..], tmp[8..]);
+			PadKey(key[14..], tmp[16..]);
+
+            return new KeyParameter(tmp);
+        }
+#endif
+
+        /**
 	     * Pad out a key for TDEA, setting odd parity for each byte.
 	     *
 	     * @param keyMaster
@@ -461,5 +751,21 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 
             DesParameters.SetOddParity(tmp, tmpOff, 8);
 	    }
-	}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void PadKey(ReadOnlySpan<byte> keyMaster, Span<byte> tmp)
+        {
+            tmp[0] = (byte)(keyMaster[0] & 0xFE);
+            tmp[1] = (byte)((keyMaster[0] << 7) | ((keyMaster[1] & 0xfc) >> 1));
+            tmp[2] = (byte)((keyMaster[1] << 6) | ((keyMaster[2] & 0xf8) >> 2));
+            tmp[3] = (byte)((keyMaster[2] << 5) | ((keyMaster[3] & 0xf0) >> 3));
+            tmp[4] = (byte)((keyMaster[3] << 4) | ((keyMaster[4] & 0xe0) >> 4));
+            tmp[5] = (byte)((keyMaster[4] << 3) | ((keyMaster[5] & 0xc0) >> 5));
+            tmp[6] = (byte)((keyMaster[5] << 2) | ((keyMaster[6] & 0x80) >> 6));
+            tmp[7] = (byte)(keyMaster[6] << 1);
+
+			DesParameters.SetOddParity(tmp[..8]);
+        }
+#endif
+    }
 }
diff --git a/crypto/src/crypto/prng/drbg/DrbgUtilities.cs b/crypto/src/crypto/prng/drbg/DrbgUtilities.cs
index 58baaf5d9..24037b3f1 100644
--- a/crypto/src/crypto/prng/drbg/DrbgUtilities.cs
+++ b/crypto/src/crypto/prng/drbg/DrbgUtilities.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Prng.Drbg
 {
@@ -35,65 +35,77 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
             return MaxSecurityStrengths[name.Substring(0, name.IndexOf("/"))];
 	    }
 
-	    /**
+        /**
 	     * Used by both Dual EC and Hash.
 	     */
-	    internal static byte[] HashDF(IDigest digest, byte[] seedMaterial, int seedLength)
-	    {
-	         // 1. temp = the Null string.
-	        // 2. .
-	        // 3. counter = an 8-bit binary value representing the integer "1".
-	        // 4. For i = 1 to len do
-	        // Comment : In step 4.1, no_of_bits_to_return
-	        // is used as a 32-bit string.
-	        // 4.1 temp = temp || Hash (counter || no_of_bits_to_return ||
-	        // input_string).
-	        // 4.2 counter = counter + 1.
-	        // 5. requested_bits = Leftmost (no_of_bits_to_return) of temp.
-	        // 6. Return SUCCESS and requested_bits.
-	        byte[] temp = new byte[(seedLength + 7) / 8];
-
-	        int len = temp.Length / digest.GetDigestSize();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void HashDF(IDigest digest, ReadOnlySpan<byte> seedMaterial, int seedLength, Span<byte> output)
+#else
+		internal static void HashDF(IDigest digest, byte[] seedMaterial, int seedLength, byte[] output)
+#endif
+        {
+			// 1. temp = the Null string.
+			// 2. .
+			// 3. counter = an 8-bit binary value representing the integer "1".
+			// 4. For i = 1 to len do
+			// Comment : In step 4.1, no_of_bits_to_return
+			// is used as a 32-bit string.
+			// 4.1 temp = temp || Hash (counter || no_of_bits_to_return ||
+			// input_string).
+			// 4.2 counter = counter + 1.
+			// 5. requested_bits = Leftmost (no_of_bits_to_return) of temp.
+			// 6. Return SUCCESS and requested_bits.
+			int outputLength = (seedLength + 7) / 8;
+
+            int digestSize = digest.GetDigestSize();
+            int len = outputLength / digestSize;
 	        int counter = 1;
 
-	        byte[] dig = new byte[digest.GetDigestSize()];
-
-	        for (int i = 0; i <= len; i++)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			Span<byte> dig = digestSize <= 128
+				? stackalloc byte[digestSize]
+				: new byte[digestSize];
+			Span<byte> header = stackalloc byte[5];
+            Pack.UInt32_To_BE((uint)seedLength, header[1..]);
+#else
+			byte[] dig = new byte[digestSize];
+			byte[] header = new byte[5];
+            Pack.UInt32_To_BE((uint)seedLength, header, 1);
+#endif
+
+            for (int i = 0; i <= len; i++, counter++)
 	        {
-	            digest.Update((byte)counter);
-
-	            digest.Update((byte)(seedLength >> 24));
-	            digest.Update((byte)(seedLength >> 16));
-	            digest.Update((byte)(seedLength >> 8));
-	            digest.Update((byte)seedLength);
-
-	            digest.BlockUpdate(seedMaterial, 0, seedMaterial.Length);
-
-	            digest.DoFinal(dig, 0);
-
-	            int bytesToCopy = ((temp.Length - i * dig.Length) > dig.Length)
-	                    ? dig.Length
-	                    : (temp.Length - i * dig.Length);
-	            Array.Copy(dig, 0, temp, i * dig.Length, bytesToCopy);
-
-	            counter++;
-	        }
-
-	        // do a left shift to get rid of excess bits.
-	        if (seedLength % 8 != 0)
+                header[0] = (byte)counter;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                digest.BlockUpdate(header);
+                digest.BlockUpdate(seedMaterial);
+                digest.DoFinal(dig);
+
+                int bytesToCopy = System.Math.Min(digestSize, outputLength - i * digestSize);
+				dig[..bytesToCopy].CopyTo(output[(i * digestSize)..]);
+#else
+				digest.BlockUpdate(header, 0, header.Length);
+				digest.BlockUpdate(seedMaterial, 0, seedMaterial.Length);
+                digest.DoFinal(dig, 0);
+
+				int bytesToCopy = System.Math.Min(digestSize, outputLength - i * digestSize);
+	            Array.Copy(dig, 0, output, i * digestSize, bytesToCopy);
+#endif
+            }
+
+            // do a left shift to get rid of excess bits.
+            if (seedLength % 8 != 0)
 	        {
 	            int shift = 8 - (seedLength % 8);
 	            uint carry = 0;
 
-                for (int i = 0; i != temp.Length; i++)
+                for (int i = 0; i != outputLength; i++)
 	            {
-	                uint b = temp[i];
-	                temp[i] = (byte)((b >> shift) | (carry << (8 - shift)));
+	                uint b = output[i];
+                    output[i] = (byte)((b >> shift) | (carry << (8 - shift)));
 	                carry = b;
 	            }
 	        }
-
-            return temp;
 	    }
 	}
 }
diff --git a/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs b/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs
index 0ec0e8b71..47e0191dd 100644
--- a/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs
+++ b/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs
@@ -8,7 +8,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	/**
 	 * A SP800-90A HMAC DRBG.
 	 */
-	public class HMacSP800Drbg
+	public sealed class HMacSP800Drbg
         :   ISP80090Drbg
 	{
 	    private readonly static long RESEED_MAX = 1L << (48 - 1);
@@ -33,7 +33,8 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	     * @param personalizationString personalization string to distinguish this DRBG (may be null).
 	     * @param nonce nonce to further distinguish this DRBG (may be null).
 	     */
-	    public HMacSP800Drbg(IMac hMac, int securityStrength, IEntropySource entropySource, byte[] personalizationString, byte[] nonce)
+	    public HMacSP800Drbg(IMac hMac, int securityStrength, IEntropySource entropySource,
+            byte[] personalizationString, byte[] nonce)
 	    {
 	        if (securityStrength > DrbgUtilities.GetMaxSecurityStrength(hMac))
 	            throw new ArgumentException("Requested security strength is not supported by the derivation function");
@@ -56,12 +57,41 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
             mReseedCounter = 1;
 	    }
 
-        private void hmac_DRBG_Update(byte[] seedMaterial)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void hmac_DRBG_Update()
+        {
+            hmac_DRBG_Update_Func(ReadOnlySpan<byte>.Empty, 0x00);
+        }
+
+        private void hmac_DRBG_Update(ReadOnlySpan<byte> seedMaterial)
+        {
+            hmac_DRBG_Update_Func(seedMaterial, 0x00);
+            hmac_DRBG_Update_Func(seedMaterial, 0x01);
+        }
+
+        private void hmac_DRBG_Update_Func(ReadOnlySpan<byte> seedMaterial, byte vValue)
+        {
+            mHMac.Init(new KeyParameter(mK));
+
+            mHMac.BlockUpdate(mV);
+            mHMac.Update(vValue);
+            if (!seedMaterial.IsEmpty)
+            {
+                mHMac.BlockUpdate(seedMaterial);
+            }
+            mHMac.DoFinal(mK);
+
+            mHMac.Init(new KeyParameter(mK));
+            mHMac.BlockUpdate(mV);
+            mHMac.DoFinal(mV);
+        }
+#else
+		private void hmac_DRBG_Update(byte[] seedMaterial)
 	    {
-	        hmac_DRBG_Update_Func(seedMaterial, (byte)0x00);
+	        hmac_DRBG_Update_Func(seedMaterial, 0x00);
 	        if (seedMaterial != null)
 	        {
-	            hmac_DRBG_Update_Func(seedMaterial, (byte)0x01);
+	            hmac_DRBG_Update_Func(seedMaterial, 0x01);
 	        }
 	    }
 
@@ -84,13 +114,14 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 
             mHMac.DoFinal(mV, 0);
 	    }
+#endif
 
-	    /**
+        /**
 	     * Return the block size (in bits) of the DRBG.
 	     *
 	     * @return the number of bits produced on each round of the DRBG.
 	     */
-	    public int BlockSize
+        public int BlockSize
 	    {
 			get { return mV.Length * 8; }
 	    }
@@ -107,15 +138,18 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	    public int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput,
 			bool predictionResistant)
 	    {
-	        int numberOfBits = outputLen * 8;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return additionalInput == null
+				? Generate(output.AsSpan(outputOff, outputLen), predictionResistant)
+				: GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant);
+#else
+			int numberOfBits = outputLen * 8;
 
             if (numberOfBits > MAX_BITS_REQUEST)
 	            throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
 
             if (mReseedCounter > RESEED_MAX)
-	        {
 	            return -1;
-	        }
 
             if (predictionResistant)
 	        {
@@ -159,23 +193,134 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        Array.Copy(rv, 0, output, outputOff, outputLen);
 
             return numberOfBits;
+#endif
 	    }
 
-	    /**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Generate(Span<byte> output, bool predictionResistant)
+        {
+            int numberOfBits = output.Length * 8;
+
+            if (numberOfBits > MAX_BITS_REQUEST)
+                throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
+
+            if (mReseedCounter > RESEED_MAX)
+                return -1;
+
+            if (predictionResistant)
+            {
+                Reseed(ReadOnlySpan<byte>.Empty);
+            }
+
+            // 3.
+            ImplGenerate(output);
+
+            hmac_DRBG_Update();
+
+            mReseedCounter++;
+
+            return numberOfBits;
+        }
+
+        public int GenerateWithInput(Span<byte> output, ReadOnlySpan<byte> additionalInput, bool predictionResistant)
+        {
+            int numberOfBits = output.Length * 8;
+
+            if (numberOfBits > MAX_BITS_REQUEST)
+                throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
+
+            if (mReseedCounter > RESEED_MAX)
+                return -1;
+
+            if (predictionResistant)
+            {
+                Reseed(additionalInput);
+            }
+            else
+            {
+                // 2.
+                hmac_DRBG_Update(additionalInput);
+            }
+
+            // 3.
+            ImplGenerate(output);
+
+            if (predictionResistant)
+            {
+                hmac_DRBG_Update();
+            }
+            else
+            {
+                hmac_DRBG_Update(additionalInput);
+            }
+
+            mReseedCounter++;
+
+            return numberOfBits;
+        }
+
+        private void ImplGenerate(Span<byte> output)
+        {
+            int outputLen = output.Length;
+            int m = outputLen / mV.Length;
+
+            mHMac.Init(new KeyParameter(mK));
+
+            for (int i = 0; i < m; i++)
+            {
+                mHMac.BlockUpdate(mV);
+                mHMac.DoFinal(mV);
+
+                mV.CopyTo(output[(i * mV.Length)..]);
+            }
+
+            int remaining = outputLen - m * mV.Length;
+            if (remaining > 0)
+            {
+                mHMac.BlockUpdate(mV);
+                mHMac.DoFinal(mV);
+
+                mV[..remaining].CopyTo(output[(m * mV.Length)..]);
+            }
+        }
+#endif
+
+        /**
 	      * Reseed the DRBG.
 	      *
 	      * @param additionalInput additional input to be added to the DRBG in this step.
 	      */
-	    public void Reseed(byte[] additionalInput)
+        public void Reseed(byte[] additionalInput)
 	    {
-	        byte[] entropy = GetEntropy();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			Reseed(Spans.FromNullableReadOnly(additionalInput));
+#else
+			byte[] entropy = GetEntropy();
 	        byte[] seedMaterial = Arrays.Concatenate(entropy, additionalInput);
 
 	        hmac_DRBG_Update(seedMaterial);
 
 	        mReseedCounter = 1;
+#endif
 	    }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Reseed(ReadOnlySpan<byte> additionalInput)
+		{
+            int entropyLength = GetEntropyLength();
+            int seedMaterialLength = entropyLength + additionalInput.Length;
+            Span<byte> seedMaterial = seedMaterialLength <= 256
+                ? stackalloc byte[seedMaterialLength]
+                : new byte[seedMaterialLength];
+            GetEntropy(seedMaterial);
+            additionalInput.CopyTo(seedMaterial[entropyLength..]);
+
+            hmac_DRBG_Update(seedMaterial);
+
+            mReseedCounter = 1;
+        }
+#endif
+
         private byte[] GetEntropy()
 	    {
 	        byte[] entropy = mEntropySource.GetEntropy();
@@ -183,5 +328,20 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	            throw new InvalidOperationException("Insufficient entropy provided by entropy source");
 	        return entropy;
 	    }
-	}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int GetEntropy(Span<byte> output)
+        {
+            int length = mEntropySource.GetEntropy(output);
+            if (length < (mSecurityStrength + 7) / 8)
+                throw new InvalidOperationException("Insufficient entropy provided by entropy source");
+            return length;
+        }
+
+        private int GetEntropyLength()
+        {
+            return (mEntropySource.EntropySize + 7) / 8;
+        }
+#endif
+    }
 }
diff --git a/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs b/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs
index accc65ec3..3f9cffbcd 100644
--- a/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs
+++ b/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Prng.Drbg
@@ -8,7 +9,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	/**
 	 * A SP800-90A Hash DRBG.
 	 */
-	public class HashSP800Drbg
+	public sealed class HashSP800Drbg
         :   ISP80090Drbg
 	{
 	    private readonly static byte[] ONE = { 0x01 };
@@ -72,12 +73,13 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 
 	        byte[] entropy = GetEntropy();
 	        byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalizationString);
-	        byte[] seed = DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength);
+            mV = new byte[(mSeedLength + 7) / 8];
+            DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV);
 
-            mV = seed;
 	        byte[] subV = new byte[mV.Length + 1];
 	        Array.Copy(mV, 0, subV, 1, mV.Length);
-	        mC = DrbgUtilities.HashDF(mDigest, subV, mSeedLength);
+            mC = new byte[(mSeedLength + 7) / 8];
+            DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC);
 
             mReseedCounter = 1;
 	    }
@@ -89,7 +91,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	     */
 	    public int BlockSize
 	    {
-			get { return mDigest.GetDigestSize () * 8; }
+			get { return mDigest.GetDigestSize() * 8; }
 	    }
 
 	    /**
@@ -104,20 +106,25 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	    public int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput,
 			bool predictionResistant)
 	    {
-	        // 1. If reseed_counter > reseed_interval, then return an indication that a
-	        // reseed is required.
-	        // 2. If (additional_input != Null), then do
-	        // 2.1 w = Hash (0x02 || V || additional_input).
-	        // 2.2 V = (V + w) mod 2^seedlen
-	        // .
-	        // 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
-	        // 4. H = Hash (0x03 || V).
-	        // 5. V = (V + H + C + reseed_counter) mod 2^seedlen
-	        // .
-	        // 6. reseed_counter = reseed_counter + 1.
-	        // 7. Return SUCCESS, returned_bits, and the new values of V, C, and
-	        // reseed_counter for the new_working_state.
-	        int numberOfBits = outputLen * 8;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return additionalInput == null
+                ? Generate(output.AsSpan(outputOff, outputLen), predictionResistant)
+                : GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant);
+#else
+			// 1. If reseed_counter > reseed_interval, then return an indication that a
+			// reseed is required.
+			// 2. If (additional_input != Null), then do
+			// 2.1 w = Hash (0x02 || V || additional_input).
+			// 2.2 V = (V + w) mod 2^seedlen
+			// .
+			// 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
+			// 4. H = Hash (0x03 || V).
+			// 5. V = (V + H + C + reseed_counter) mod 2^seedlen
+			// .
+			// 6. reseed_counter = reseed_counter + 1.
+			// 7. Return SUCCESS, returned_bits, and the new values of V, C, and
+			// reseed_counter for the new_working_state.
+			int numberOfBits = outputLen * 8;
 
 	        if (numberOfBits > MAX_BITS_REQUEST)
 	            throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
@@ -137,7 +144,6 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	            byte[] newInput = new byte[1 + mV.Length + additionalInput.Length];
 	            newInput[0] = 0x02;
 	            Array.Copy(mV, 0, newInput, 1, mV.Length);
-	            // TODO: inOff / inLength
 	            Array.Copy(additionalInput, 0, newInput, 1 + mV.Length, additionalInput.Length);
 	            byte[] w = Hash(newInput);
 
@@ -145,7 +151,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        }
 
             // 3.
-	        byte[] rv = hashgen(mV, numberOfBits);
+	        byte[] rv = Hashgen(mV, outputLen);
 
             // 4.
 	        byte[] subH = new byte[mV.Length + 1];
@@ -157,11 +163,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
             // 5.
 	        AddTo(mV, H);
 	        AddTo(mV, mC);
+
 	        byte[] c = new byte[4];
-	        c[0] = (byte)(mReseedCounter >> 24);
-	        c[1] = (byte)(mReseedCounter >> 16);
-	        c[2] = (byte)(mReseedCounter >> 8);
-	        c[3] = (byte)mReseedCounter;
+			Pack.UInt32_To_BE((uint)mReseedCounter, c);
 
 	        AddTo(mV, c);
 
@@ -170,9 +174,118 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        Array.Copy(rv, 0, output, outputOff, outputLen);
 
 	        return numberOfBits;
-	    }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Generate(Span<byte> output, bool predictionResistant)
+        {
+            // 1. If reseed_counter > reseed_interval, then return an indication that a
+            // reseed is required.
+            // 2. If (additional_input != Null), then do
+            // 2.1 w = Hash (0x02 || V || additional_input).
+            // 2.2 V = (V + w) mod 2^seedlen
+            // .
+            // 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
+            // 4. H = Hash (0x03 || V).
+            // 5. V = (V + H + C + reseed_counter) mod 2^seedlen
+            // .
+            // 6. reseed_counter = reseed_counter + 1.
+            // 7. Return SUCCESS, returned_bits, and the new values of V, C, and
+            // reseed_counter for the new_working_state.
+            int numberOfBits = output.Length * 8;
+
+            if (numberOfBits > MAX_BITS_REQUEST)
+                throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
+
+            if (mReseedCounter > RESEED_MAX)
+                return -1;
+
+            if (predictionResistant)
+            {
+                Reseed(ReadOnlySpan<byte>.Empty);
+            }
+
+			return ImplGenerate(output);
+        }
+
+        public int GenerateWithInput(Span<byte> output, ReadOnlySpan<byte> additionalInput, bool predictionResistant)
+		{
+			// 1. If reseed_counter > reseed_interval, then return an indication that a
+			// reseed is required.
+			// 2. If (additional_input != Null), then do
+			// 2.1 w = Hash (0x02 || V || additional_input).
+			// 2.2 V = (V + w) mod 2^seedlen
+			// .
+			// 3. (returned_bits) = Hashgen (requested_number_of_bits, V).
+			// 4. H = Hash (0x03 || V).
+			// 5. V = (V + H + C + reseed_counter) mod 2^seedlen
+			// .
+			// 6. reseed_counter = reseed_counter + 1.
+			// 7. Return SUCCESS, returned_bits, and the new values of V, C, and
+			// reseed_counter for the new_working_state.
+            int numberOfBits = output.Length * 8;
+
+            if (numberOfBits > MAX_BITS_REQUEST)
+                throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output");
+
+            if (mReseedCounter > RESEED_MAX)
+                return -1;
+
+            if (predictionResistant)
+            {
+                Reseed(additionalInput);
+            }
+			else
+			{
+                // 2.
+                mDigest.Update(0x02);
+                mDigest.BlockUpdate(mV);
+                mDigest.BlockUpdate(additionalInput);
+
+                int digestSize = mDigest.GetDigestSize();
+                Span<byte> w = digestSize <= 128
+					? stackalloc byte[digestSize]
+                    : new byte[digestSize];
+                mDigest.DoFinal(w);
+
+                AddTo(mV, w);
+            }
+
+			return ImplGenerate(output);
+        }
+
+		private int ImplGenerate(Span<byte> output)
+		{
+            // 3.
+            Hashgen(mV, output);
+
+            // 4.
+            mDigest.Update(0x03);
+            mDigest.BlockUpdate(mV);
 
-	    private byte[] GetEntropy()
+            int digestSize = mDigest.GetDigestSize();
+            Span<byte> H = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+            mDigest.DoFinal(H);
+
+            // 5.
+            AddTo(mV, H);
+            AddTo(mV, mC);
+
+			Span<byte> c = stackalloc byte[4];
+			Pack.UInt32_To_BE((uint)mReseedCounter, c);
+
+            AddTo(mV, c);
+
+            mReseedCounter++;
+
+            return output.Length * 8;
+        }
+#endif
+
+        private byte[] GetEntropy()
 	    {
 	        byte[] entropy = mEntropySource.GetEntropy();
 	        if (entropy.Length < (mSecurityStrength + 7) / 8)
@@ -180,18 +293,37 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	        return entropy;
 	    }
 
-	    // this will always add the shorter length byte array mathematically to the
-	    // longer length byte array.
-	    // be careful....
-	    private void AddTo(byte[] longer, byte[] shorter)
-	    {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int GetEntropy(Span<byte> output)
+        {
+            int length = mEntropySource.GetEntropy(output);
+            if (length < (mSecurityStrength + 7) / 8)
+                throw new InvalidOperationException("Insufficient entropy provided by entropy source");
+            return length;
+        }
+
+        private int GetEntropyLength()
+        {
+            return (mEntropySource.EntropySize + 7) / 8;
+        }
+#endif
+
+        // this will always add the shorter length byte array mathematically to the
+        // longer length byte array.
+        // be careful....
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void AddTo(Span<byte> longer, ReadOnlySpan<byte> shorter)
+#else
+		private void AddTo(byte[] longer, byte[] shorter)
+#endif
+        {
             int off = longer.Length - shorter.Length;
 
             uint carry = 0;
             int i = shorter.Length;
             while (--i >= 0)
             {
-                carry += (uint)longer[off + i] + (uint)shorter[i];
+                carry += (uint)longer[off + i] + shorter[i];
                 longer[off + i] = (byte)carry;
                 carry >>= 8;
             }
@@ -212,78 +344,155 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	      */
 	    public void Reseed(byte[] additionalInput)
 	    {
-	        // 1. seed_material = 0x01 || V || entropy_input || additional_input.
-	        //
-	        // 2. seed = Hash_df (seed_material, seedlen).
-	        //
-	        // 3. V = seed.
-	        //
-	        // 4. C = Hash_df ((0x00 || V), seedlen).
-	        //
-	        // 5. reseed_counter = 1.
-	        //
-	        // 6. Return V, C, and reseed_counter for the new_working_state.
-	        //
-	        // Comment: Precede with a byte of all zeros.
-	        byte[] entropy = GetEntropy();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			Reseed(Spans.FromNullableReadOnly(additionalInput));
+#else
+			// 1. seed_material = 0x01 || V || entropy_input || additional_input.
+			//
+			// 2. seed = Hash_df (seed_material, seedlen).
+			//
+			// 3. V = seed.
+			//
+			// 4. C = Hash_df ((0x00 || V), seedlen).
+			//
+			// 5. reseed_counter = 1.
+			//
+			// 6. Return V, C, and reseed_counter for the new_working_state.
+			//
+			// Comment: Precede with a byte of all zeros.
+			byte[] entropy = GetEntropy();
 	        byte[] seedMaterial = Arrays.ConcatenateAll(ONE, mV, entropy, additionalInput);
-	        byte[] seed = DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength);
+	        DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV);
 
-            mV = seed;
 	        byte[] subV = new byte[mV.Length + 1];
 	        subV[0] = 0x00;
 	        Array.Copy(mV, 0, subV, 1, mV.Length);
-	        mC = DrbgUtilities.HashDF(mDigest, subV, mSeedLength);
+	        DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC);
 
             mReseedCounter = 1;
+#endif
 	    }
 
-        private byte[] Hash(byte[] input)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Reseed(ReadOnlySpan<byte> additionalInput)
         {
-            byte[] hash = new byte[mDigest.GetDigestSize()];
-            DoHash(input, hash);
-            return hash;
+			// 1. seed_material = 0x01 || V || entropy_input || additional_input.
+			//
+			// 2. seed = Hash_df (seed_material, seedlen).
+			//
+			// 3. V = seed.
+			//
+			// 4. C = Hash_df ((0x00 || V), seedlen).
+			//
+			// 5. reseed_counter = 1.
+			//
+			// 6. Return V, C, and reseed_counter for the new_working_state.
+			//
+			// Comment: Precede with a byte of all zeros.
+			int entropyLength = GetEntropyLength();
+
+			int seedMaterialLength = 1 + mV.Length + entropyLength + additionalInput.Length;
+			Span<byte> seedMaterial = seedMaterialLength <= 256
+				? stackalloc byte[seedMaterialLength]
+				: new byte[seedMaterialLength];
+
+			seedMaterial[0] = 0x01;
+			mV.CopyTo(seedMaterial[1..]);
+			GetEntropy(seedMaterial[(1 + mV.Length)..]);
+			additionalInput.CopyTo(seedMaterial[(1 + mV.Length + entropyLength)..]);
+
+            DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV);
+
+			int subVLength = 1 + mV.Length;
+			Span<byte> subV = subVLength <= 128
+				? stackalloc byte[subVLength]
+				: new byte[subVLength];
+            subV[0] = 0x00;
+			mV.CopyTo(subV[1..]);
+
+            DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC);
+
+            mReseedCounter = 1;
         }
+#endif
 
-        private void DoHash(byte[] input, byte[] output)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void DoHash(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            mDigest.BlockUpdate(input);
+            mDigest.DoFinal(output);
+        }
+#else
+		private void DoHash(byte[] input, byte[] output)
         {
             mDigest.BlockUpdate(input, 0, input.Length);
             mDigest.DoFinal(output, 0);
         }
 
+        private byte[] Hash(byte[] input)
+        {
+            byte[] hash = new byte[mDigest.GetDigestSize()];
+            DoHash(input, hash);
+            return hash;
+        }
+#endif
+
         // 1. m = [requested_number_of_bits / outlen]
-	    // 2. data = V.
-	    // 3. W = the Null string.
-	    // 4. For i = 1 to m
-	    // 4.1 wi = Hash (data).
-	    // 4.2 W = W || wi.
-	    // 4.3 data = (data + 1) mod 2^seedlen
-	    // .
-	    // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W.
-	    private byte[] hashgen(byte[] input, int lengthInBits)
+        // 2. data = V.
+        // 3. W = the Null string.
+        // 4. For i = 1 to m
+        // 4.1 wi = Hash (data).
+        // 4.2 W = W || wi.
+        // 4.3 data = (data + 1) mod 2^seedlen
+        // .
+        // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W.
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Hashgen(ReadOnlySpan<byte> input, Span<byte> output)
 	    {
 	        int digestSize = mDigest.GetDigestSize();
-	        int m = (lengthInBits / 8) / digestSize;
+	        int m = output.Length / digestSize;
+
+			int dataSize = input.Length;
+            Span<byte> data = dataSize <= 256
+				? stackalloc byte[input.Length]
+                : new byte[input.Length];
+			input.CopyTo(data);
+
+            Span<byte> dig = digestSize <= 128
+				? stackalloc byte[digestSize]
+                : new byte[digestSize];
+
+	        for (int i = 0; i <= m; i++)
+	        {
+	            DoHash(data, dig);
 
-            byte[] data = new byte[input.Length];
-	        Array.Copy(input, 0, data, 0, input.Length);
+				int bytesToCopy = System.Math.Min(digestSize, output.Length - i * digestSize);
+				dig[..bytesToCopy].CopyTo(output[(i * digestSize)..]);
+                AddTo(data, ONE);
+	        }
+	    }
+#else
+        private byte[] Hashgen(byte[] input, int length)
+	    {
+	        int digestSize = mDigest.GetDigestSize();
+	        int m = length / digestSize;
 
-	        byte[] W = new byte[lengthInBits / 8];
+            byte[] data = (byte[])input.Clone();
+	        byte[] W = new byte[length];
 
-            byte[] dig = new byte[mDigest.GetDigestSize()];
+            byte[] dig = new byte[digestSize];
 	        for (int i = 0; i <= m; i++)
 	        {
 	            DoHash(data, dig);
 
-	            int bytesToCopy = ((W.Length - i * dig.Length) > dig.Length)
-	                    ? dig.Length
-	                    : (W.Length - i * dig.Length);
-	            Array.Copy(dig, 0, W, i * dig.Length, bytesToCopy);
+				int bytesToCopy = System.Math.Min(digestSize, length - i * digestSize);
+	            Array.Copy(dig, 0, W, i * digestSize, bytesToCopy);
 
                 AddTo(data, ONE);
 	        }
 
 	        return W;
-	    }    
-	}
+	    }
+#endif
+    }
 }
diff --git a/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs b/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs
index 78cbcd92f..ab7252a8d 100644
--- a/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs
+++ b/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs
@@ -14,7 +14,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	     */
 		int BlockSize { get; }
 
-	    /**
+        /**
 	     * Populate a passed in array with random data.
 	     *
 	     * @param output output array for generated bits.
@@ -23,13 +23,23 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg
 	     *
 	     * @return number of bits generated, -1 if a reseed required.
 	     */
-	    int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput, bool predictionResistant);
+        int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput, bool predictionResistant);
 
-	    /**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int Generate(Span<byte> output, bool predictionResistant);
+
+        int GenerateWithInput(Span<byte> output, ReadOnlySpan<byte> additionalInput, bool predictionResistant);
+#endif
+
+        /**
 	     * Reseed the DRBG.
 	     *
 	     * @param additionalInput additional input to be added to the DRBG in this step.
 	     */
-	    void Reseed(byte[] additionalInput);
-	}
+        void Reseed(byte[] additionalInput);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void Reseed(ReadOnlySpan<byte> additionalInput);
+#endif
+    }
 }
diff --git a/crypto/src/crypto/signers/DsaDigestSigner.cs b/crypto/src/crypto/signers/DsaDigestSigner.cs
index 15444a0f7..e8c2487ba 100644
--- a/crypto/src/crypto/signers/DsaDigestSigner.cs
+++ b/crypto/src/crypto/signers/DsaDigestSigner.cs
@@ -14,19 +14,12 @@ namespace Org.BouncyCastle.Crypto.Signers
         private readonly IDsaEncoding encoding;
         private bool forSigning;
 
-		public DsaDigestSigner(
-			IDsa	dsa,
-			IDigest	digest)
+		public DsaDigestSigner(IDsa dsa, IDigest digest)
+			: this(dsa, digest, StandardDsaEncoding.Instance)
 		{
-            this.dsa = dsa;
-            this.digest = digest;
-            this.encoding = StandardDsaEncoding.Instance;
 		}
 
-        public DsaDigestSigner(
-            IDsaExt dsa,
-            IDigest digest,
-            IDsaEncoding encoding)
+        public DsaDigestSigner(IDsa dsa, IDigest digest, IDsaEncoding encoding)
         {
             this.dsa = dsa;
             this.digest = digest;
@@ -38,17 +31,14 @@ namespace Org.BouncyCastle.Crypto.Signers
 			get { return digest.AlgorithmName + "with" + dsa.AlgorithmName; }
 		}
 
-        public virtual void Init(
-			bool forSigning,
-			ICipherParameters parameters)
+        public virtual void Init(bool forSigning, ICipherParameters parameters)
 		{
 			this.forSigning = forSigning;
 
 			AsymmetricKeyParameter k;
-
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom withRandom)
 			{
-				k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters;
+				k = (AsymmetricKeyParameter)withRandom.Parameters;
 			}
 			else
 			{
@@ -66,31 +56,24 @@ namespace Org.BouncyCastle.Crypto.Signers
 			dsa.Init(forSigning, parameters);
 		}
 
-		/**
-		 * update the internal digest with the byte b
-		 */
-        public virtual void Update(
-			byte input)
+        public virtual void Update(byte input)
 		{
 			digest.Update(input);
 		}
 
-		/**
-		 * update the internal digest with the byte array in
-		 */
-        public virtual void BlockUpdate(
-			byte[]	input,
-			int			inOff,
-			int			length)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
+		{
+			digest.BlockUpdate(input, inOff, inLen);
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual void BlockUpdate(ReadOnlySpan<byte> input)
 		{
-			digest.BlockUpdate(input, inOff, length);
+			digest.BlockUpdate(input);
 		}
+#endif
 
-		/**
-		 * Generate a signature for the message we've been loaded with using
-		 * the key we were initialised with.
-     */
-        public virtual byte[] GenerateSignature()
+		public virtual byte[] GenerateSignature()
 		{
 			if (!forSigning)
 				throw new InvalidOperationException("DSADigestSigner not initialised for signature generation.");
@@ -110,9 +93,7 @@ namespace Org.BouncyCastle.Crypto.Signers
             }
 		}
 
-		/// <returns>true if the internal state represents the signature described in the passed in array.</returns>
-        public virtual bool VerifySignature(
-			byte[] signature)
+        public virtual bool VerifySignature(byte[] signature)
 		{
 			if (forSigning)
 				throw new InvalidOperationException("DSADigestSigner not initialised for verification");
@@ -132,7 +113,6 @@ namespace Org.BouncyCastle.Crypto.Signers
             }
 		}
 
-		/// <summary>Reset the internal state</summary>
         public virtual void Reset()
 		{
 			digest.Reset();
@@ -140,7 +120,7 @@ namespace Org.BouncyCastle.Crypto.Signers
 
         protected virtual BigInteger GetOrder()
         {
-            return dsa is IDsaExt ? ((IDsaExt)dsa).Order : null;
+            return dsa.Order;
         }
 	}
 }
diff --git a/crypto/src/crypto/signers/DsaSigner.cs b/crypto/src/crypto/signers/DsaSigner.cs
index 1f5d9b937..318eeeb48 100644
--- a/crypto/src/crypto/signers/DsaSigner.cs
+++ b/crypto/src/crypto/signers/DsaSigner.cs
@@ -12,7 +12,7 @@ namespace Org.BouncyCastle.Crypto.Signers
      * Cryptography", pages 452 - 453.
      */
     public class DsaSigner
-        : IDsaExt
+        : IDsa
     {
         protected readonly IDsaKCalculator kCalculator;
 
@@ -155,7 +155,7 @@ namespace Org.BouncyCastle.Crypto.Signers
 
         protected virtual SecureRandom InitSecureRandom(bool needed, SecureRandom provided)
         {
-            return !needed ? null : (provided != null) ? provided : new SecureRandom();
+            return !needed ? null : CryptoServicesRegistrar.GetSecureRandom(provided);
         }
     }
 }
diff --git a/crypto/src/crypto/signers/ECDsaSigner.cs b/crypto/src/crypto/signers/ECDsaSigner.cs
index 0a265d96e..d78e92516 100644
--- a/crypto/src/crypto/signers/ECDsaSigner.cs
+++ b/crypto/src/crypto/signers/ECDsaSigner.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
@@ -14,7 +13,7 @@ namespace Org.BouncyCastle.Crypto.Signers
      * EC-DSA as described in X9.62
      */
     public class ECDsaSigner
-        : IDsaExt
+        : IDsa
     {
         private static readonly BigInteger Eight = BigInteger.ValueOf(8);
 
@@ -240,7 +239,7 @@ namespace Org.BouncyCastle.Crypto.Signers
 
         protected virtual SecureRandom InitSecureRandom(bool needed, SecureRandom provided)
         {
-            return !needed ? null : (provided != null) ? provided : new SecureRandom();
+            return !needed ? null : CryptoServicesRegistrar.GetSecureRandom(provided);
         }
     }
 }
diff --git a/crypto/src/crypto/signers/ECGOST3410Signer.cs b/crypto/src/crypto/signers/ECGOST3410Signer.cs
index 7df43f0a0..fd5fa4818 100644
--- a/crypto/src/crypto/signers/ECGOST3410Signer.cs
+++ b/crypto/src/crypto/signers/ECGOST3410Signer.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
@@ -14,7 +13,7 @@ namespace Org.BouncyCastle.Crypto.Signers
      * GOST R 34.10-2001 Signature Algorithm
      */
     public class ECGost3410Signer
-        : IDsaExt
+        : IDsa
     {
         private ECKeyParameters key;
         private SecureRandom random;
@@ -25,44 +24,37 @@ namespace Org.BouncyCastle.Crypto.Signers
             get { return key.AlgorithmName; }
         }
 
-        public virtual void Init(
-            bool				forSigning,
-            ICipherParameters	parameters)
+        public virtual void Init(bool forSigning, ICipherParameters parameters)
         {
             this.forSigning = forSigning;
 
             if (forSigning)
             {
-                if (parameters is ParametersWithRandom)
+                if (parameters is ParametersWithRandom rParam)
                 {
-                    ParametersWithRandom rParam = (ParametersWithRandom)parameters;
-
                     this.random = rParam.Random;
                     parameters = rParam.Parameters;
                 }
                 else
                 {
-                    this.random = new SecureRandom();
+                    this.random = CryptoServicesRegistrar.GetSecureRandom();
                 }
 
-                if (!(parameters is ECPrivateKeyParameters))
+                if (!(parameters is ECPrivateKeyParameters ecPrivateKeyParameters))
                     throw new InvalidKeyException("EC private key required for signing");
 
-                this.key = (ECPrivateKeyParameters) parameters;
+                this.key = ecPrivateKeyParameters;
             }
             else
             {
-                if (!(parameters is ECPublicKeyParameters))
+                if (!(parameters is ECPublicKeyParameters ecPublicKeyParameters))
                     throw new InvalidKeyException("EC public key required for verification");
 
-                this.key = (ECPublicKeyParameters)parameters;
+                this.key = ecPublicKeyParameters;
             }
         }
 
-        public virtual BigInteger Order
-        {
-            get { return key.Parameters.N; }
-        }
+        public virtual BigInteger Order => key.Parameters.N;
 
         /**
          * generate a signature for the given message using the key we were
@@ -71,13 +63,10 @@ namespace Org.BouncyCastle.Crypto.Signers
          *
          * @param message the message that will be verified later.
          */
-        public virtual BigInteger[] GenerateSignature(
-            byte[] message)
+        public virtual BigInteger[] GenerateSignature(byte[] message)
         {
             if (!forSigning)
-            {
                 throw new InvalidOperationException("not initialized for signing");
-            }
 
             byte[] mRev = Arrays.Reverse(message); // conversion is little-endian
             BigInteger e = new BigInteger(1, mRev);
@@ -86,7 +75,7 @@ namespace Org.BouncyCastle.Crypto.Signers
             BigInteger n = ec.N;
             BigInteger d = ((ECPrivateKeyParameters)key).D;
 
-            BigInteger r, s = null;
+            BigInteger r, s;
 
             ECMultiplier basePointMultiplier = CreateBasePointMultiplier();
 
@@ -97,7 +86,7 @@ namespace Org.BouncyCastle.Crypto.Signers
                 {
                     do
                     {
-                        k = new BigInteger(n.BitLength, random);
+                        k = BigIntegers.CreateRandomBigInteger(n.BitLength, random);
                     }
                     while (k.SignValue == 0);
 
@@ -107,7 +96,7 @@ namespace Org.BouncyCastle.Crypto.Signers
                 }
                 while (r.SignValue == 0);
 
-                s = (k.Multiply(e)).Add(d.Multiply(r)).Mod(n);
+                s = k.Multiply(e).Add(d.Multiply(r)).Mod(n);
             }
             while (s.SignValue == 0);
 
@@ -119,15 +108,10 @@ namespace Org.BouncyCastle.Crypto.Signers
          * the passed in message (for standard GOST3410 the message should be
          * a GOST3411 hash of the real message to be verified).
          */
-        public virtual bool VerifySignature(
-            byte[]		message,
-            BigInteger	r,
-            BigInteger	s)
+        public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s)
         {
             if (forSigning)
-            {
                 throw new InvalidOperationException("not initialized for verification");
-            }
 
             byte[] mRev = Arrays.Reverse(message); // conversion is little-endian
             BigInteger e = new BigInteger(1, mRev);
@@ -135,20 +119,16 @@ namespace Org.BouncyCastle.Crypto.Signers
 
             // r in the range [1,n-1]
             if (r.CompareTo(BigInteger.One) < 0 || r.CompareTo(n) >= 0)
-            {
                 return false;
-            }
 
             // s in the range [1,n-1]
             if (s.CompareTo(BigInteger.One) < 0 || s.CompareTo(n) >= 0)
-            {
                 return false;
-            }
 
             BigInteger v = BigIntegers.ModOddInverseVar(n, e);
 
             BigInteger z1 = s.Multiply(v).Mod(n);
-            BigInteger z2 = (n.Subtract(r)).Multiply(v).Mod(n);
+            BigInteger z2 = n.Subtract(r).Multiply(v).Mod(n);
 
             ECPoint G = key.Parameters.G; // P
             ECPoint Q = ((ECPublicKeyParameters)key).Q;
diff --git a/crypto/src/crypto/signers/ECNRSigner.cs b/crypto/src/crypto/signers/ECNRSigner.cs
index bc193e797..d7790386a 100644
--- a/crypto/src/crypto/signers/ECNRSigner.cs
+++ b/crypto/src/crypto/signers/ECNRSigner.cs
@@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Crypto.Signers
      * EC-NR as described in IEEE 1363-2000
      */
     public class ECNRSigner
-        : IDsaExt
+        : IDsa
     {
         private bool			forSigning;
         private ECKeyParameters	key;
@@ -24,24 +24,20 @@ namespace Org.BouncyCastle.Crypto.Signers
             get { return "ECNR"; }
         }
 
-        public virtual void Init(
-            bool				forSigning,
-            ICipherParameters	parameters)
+        public virtual void Init(bool forSigning, ICipherParameters parameters)
         {
             this.forSigning = forSigning;
 
             if (forSigning)
             {
-                if (parameters is ParametersWithRandom)
+                if (parameters is ParametersWithRandom rParam)
                 {
-                    ParametersWithRandom rParam = (ParametersWithRandom) parameters;
-
                     this.random = rParam.Random;
                     parameters = rParam.Parameters;
                 }
                 else
                 {
-                    this.random = new SecureRandom();
+                    this.random = CryptoServicesRegistrar.GetSecureRandom();
                 }
 
                 if (!(parameters is ECPrivateKeyParameters))
diff --git a/crypto/src/crypto/signers/Ed25519Signer.cs b/crypto/src/crypto/signers/Ed25519Signer.cs
index 4646ce1a5..59dc1bec5 100644
--- a/crypto/src/crypto/signers/Ed25519Signer.cs
+++ b/crypto/src/crypto/signers/Ed25519Signer.cs
@@ -52,6 +52,13 @@ namespace Org.BouncyCastle.Crypto.Signers
             buffer.Write(buf, off, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            buffer.Write(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning || null == privateKey)
diff --git a/crypto/src/crypto/signers/Ed25519ctxSigner.cs b/crypto/src/crypto/signers/Ed25519ctxSigner.cs
index 293afedf5..4ccca8f22 100644
--- a/crypto/src/crypto/signers/Ed25519ctxSigner.cs
+++ b/crypto/src/crypto/signers/Ed25519ctxSigner.cs
@@ -55,6 +55,13 @@ namespace Org.BouncyCastle.Crypto.Signers
             buffer.Write(buf, off, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            buffer.Write(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning || null == privateKey)
diff --git a/crypto/src/crypto/signers/Ed25519phSigner.cs b/crypto/src/crypto/signers/Ed25519phSigner.cs
index 8f4afab19..800447143 100644
--- a/crypto/src/crypto/signers/Ed25519phSigner.cs
+++ b/crypto/src/crypto/signers/Ed25519phSigner.cs
@@ -55,6 +55,13 @@ namespace Org.BouncyCastle.Crypto.Signers
             prehash.BlockUpdate(buf, off, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            prehash.BlockUpdate(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning || null == privateKey)
diff --git a/crypto/src/crypto/signers/Ed448Signer.cs b/crypto/src/crypto/signers/Ed448Signer.cs
index 723ee7e33..3a7def690 100644
--- a/crypto/src/crypto/signers/Ed448Signer.cs
+++ b/crypto/src/crypto/signers/Ed448Signer.cs
@@ -55,6 +55,13 @@ namespace Org.BouncyCastle.Crypto.Signers
             buffer.Write(buf, off, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            buffer.Write(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning || null == privateKey)
diff --git a/crypto/src/crypto/signers/Ed448phSigner.cs b/crypto/src/crypto/signers/Ed448phSigner.cs
index 197c2f706..7ff9cfbbe 100644
--- a/crypto/src/crypto/signers/Ed448phSigner.cs
+++ b/crypto/src/crypto/signers/Ed448phSigner.cs
@@ -55,13 +55,20 @@ namespace Org.BouncyCastle.Crypto.Signers
             prehash.BlockUpdate(buf, off, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            prehash.BlockUpdate(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning || null == privateKey)
                 throw new InvalidOperationException("Ed448phSigner not initialised for signature generation.");
 
             byte[] msg = new byte[Ed448.PrehashSize];
-            if (Ed448.PrehashSize != prehash.DoFinal(msg, 0, Ed448.PrehashSize))
+            if (Ed448.PrehashSize != prehash.OutputFinal(msg, 0, Ed448.PrehashSize))
                 throw new InvalidOperationException("Prehash digest failed");
 
             byte[] signature = new byte[Ed448PrivateKeyParameters.SignatureSize];
diff --git a/crypto/src/crypto/signers/GOST3410DigestSigner.cs b/crypto/src/crypto/signers/GOST3410DigestSigner.cs
index 81a5ff1cc..9564e43d3 100644
--- a/crypto/src/crypto/signers/GOST3410DigestSigner.cs
+++ b/crypto/src/crypto/signers/GOST3410DigestSigner.cs
@@ -15,11 +15,7 @@ namespace Org.BouncyCastle.Crypto.Signers
         private int halfSize;
         private bool forSigning;
 
-
-
-        public Gost3410DigestSigner(
-            IDsa signer,
-            IDigest digest)
+        public Gost3410DigestSigner(IDsa signer, IDigest digest)
         {
             this.dsaSigner = signer;
             this.digest = digest;
@@ -34,9 +30,7 @@ namespace Org.BouncyCastle.Crypto.Signers
             get { return digest.AlgorithmName + "with" + dsaSigner.AlgorithmName; }
         }
 
-        public virtual void Init(
-            bool forSigning,
-            ICipherParameters parameters)
+        public virtual void Init(bool forSigning, ICipherParameters parameters)
         {
             this.forSigning = forSigning;
 
@@ -66,30 +60,23 @@ namespace Org.BouncyCastle.Crypto.Signers
             dsaSigner.Init(forSigning, parameters);
         }
 
-        /**
-		 * update the internal digest with the byte b
-		 */
-        public virtual void Update(
-            byte input)
+        public virtual void Update(byte input)
         {
             digest.Update(input);
         }
 
-        /**
-		 * update the internal digest with the byte array in
-		 */
-        public virtual void BlockUpdate(
-            byte[] input,
-            int inOff,
-            int length)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            digest.BlockUpdate(input, inOff, inLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            digest.BlockUpdate(input, inOff, length);
+            digest.BlockUpdate(input);
         }
+#endif
 
-        /**
-		 * Generate a signature for the message we've been loaded with using
-		 * the key we were initialised with.
-		 */
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning)
@@ -116,9 +103,7 @@ namespace Org.BouncyCastle.Crypto.Signers
             }
         }
 
-        /// <returns>true if the internal state represents the signature described in the passed in array.</returns>
-        public virtual bool VerifySignature(
-            byte[] signature)
+        public virtual bool VerifySignature(byte[] signature)
         {
             if (forSigning)
                 throw new InvalidOperationException("DSADigestSigner not initialised for verification");
@@ -140,7 +125,6 @@ namespace Org.BouncyCastle.Crypto.Signers
             return dsaSigner.VerifySignature(hash, R, S);
         }
 
-        /// <summary>Reset the internal state</summary>
         public virtual void Reset()
         {
             digest.Reset();
diff --git a/crypto/src/crypto/signers/GOST3410Signer.cs b/crypto/src/crypto/signers/GOST3410Signer.cs
index bcc1125b1..03aab0b04 100644
--- a/crypto/src/crypto/signers/GOST3410Signer.cs
+++ b/crypto/src/crypto/signers/GOST3410Signer.cs
@@ -11,7 +11,7 @@ namespace Org.BouncyCastle.Crypto.Signers
 	 * Gost R 34.10-94 Signature Algorithm
 	 */
 	public class Gost3410Signer
-		: IDsaExt
+		: IDsa
 	{
 		private Gost3410KeyParameters key;
 		private SecureRandom random;
@@ -21,22 +21,18 @@ namespace Org.BouncyCastle.Crypto.Signers
 			get { return "GOST3410"; }
 		}
 
-        public virtual void Init(
-			bool				forSigning,
-			ICipherParameters	parameters)
+        public virtual void Init(bool forSigning, ICipherParameters parameters)
 		{
 			if (forSigning)
 			{
-				if (parameters is ParametersWithRandom)
+				if (parameters is ParametersWithRandom rParam)
 				{
-					ParametersWithRandom rParam = (ParametersWithRandom)parameters;
-
 					this.random = rParam.Random;
 					parameters = rParam.Parameters;
 				}
 				else
 				{
-					this.random = new SecureRandom();
+					this.random = CryptoServicesRegistrar.GetSecureRandom();
 				}
 
 				if (!(parameters is Gost3410PrivateKeyParameters))
diff --git a/crypto/src/crypto/signers/GenericSigner.cs b/crypto/src/crypto/signers/GenericSigner.cs
index a5512176f..e0ff685ae 100644
--- a/crypto/src/crypto/signers/GenericSigner.cs
+++ b/crypto/src/crypto/signers/GenericSigner.cs
@@ -59,26 +59,23 @@ namespace Org.BouncyCastle.Crypto.Signers
             engine.Init(forSigning, parameters);
         }
 
-        /**
-        * update the internal digest with the byte b
-        */
         public virtual void Update(byte input)
         {
             digest.Update(input);
         }
 
-        /**
-        * update the internal digest with the byte array in
-        */
-        public virtual void BlockUpdate(byte[] input, int inOff, int length)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
         {
-            digest.BlockUpdate(input, inOff, length);
+            digest.BlockUpdate(input, inOff, inLen);
         }
 
-        /**
-        * Generate a signature for the message we've been loaded with using the key
-        * we were initialised with.
-        */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            digest.BlockUpdate(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning)
@@ -90,10 +87,6 @@ namespace Org.BouncyCastle.Crypto.Signers
             return engine.ProcessBlock(hash, 0, hash.Length);
         }
 
-        /**
-        * return true if the internal state represents the signature described in
-        * the passed in array.
-        */
         public virtual bool VerifySignature(byte[] signature)
         {
             if (forSigning)
diff --git a/crypto/src/crypto/signers/HMacDsaKCalculator.cs b/crypto/src/crypto/signers/HMacDsaKCalculator.cs
index 2641f58b6..67dadede8 100644
--- a/crypto/src/crypto/signers/HMacDsaKCalculator.cs
+++ b/crypto/src/crypto/signers/HMacDsaKCalculator.cs
@@ -46,55 +46,58 @@ namespace Org.BouncyCastle.Crypto.Signers
         {
             this.n = n;
 
-            Arrays.Fill(V, (byte)0x01);
-            Arrays.Fill(K, (byte)0);
-
-            int size = BigIntegers.GetUnsignedByteLength(n);
-            byte[] x = new byte[size];
-            byte[] dVal = BigIntegers.AsUnsignedByteArray(d);
-
-            Array.Copy(dVal, 0, x, x.Length - dVal.Length, dVal.Length);
-
-            byte[] m = new byte[size];
+            Arrays.Fill(V, 0x01);
+            Arrays.Fill(K, 0);
 
             BigInteger mInt = BitsToInt(message);
-
             if (mInt.CompareTo(n) >= 0)
             {
                 mInt = mInt.Subtract(n);
             }
 
-            byte[] mVal = BigIntegers.AsUnsignedByteArray(mInt);
+            int size = BigIntegers.GetUnsignedByteLength(n);
 
-            Array.Copy(mVal, 0, m, m.Length - mVal.Length, mVal.Length);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int xmSize = size * 2;
+            Span<byte> xm = xmSize <= 512
+                ? stackalloc byte[xmSize]
+                : new byte[xmSize];
+            BigIntegers.AsUnsignedByteArray(d, xm[..size]);
+            BigIntegers.AsUnsignedByteArray(mInt, xm[size..]);
+#else
+            byte[] x = BigIntegers.AsUnsignedByteArray(size, d);
+            byte[] m = BigIntegers.AsUnsignedByteArray(size, mInt);
+#endif
 
             hMac.Init(new KeyParameter(K));
 
             hMac.BlockUpdate(V, 0, V.Length);
-            hMac.Update((byte)0x00);
+            hMac.Update(0x00);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            hMac.BlockUpdate(xm);
+#else
             hMac.BlockUpdate(x, 0, x.Length);
             hMac.BlockUpdate(m, 0, m.Length);
+#endif
             InitAdditionalInput0(hMac);
-
             hMac.DoFinal(K, 0);
 
             hMac.Init(new KeyParameter(K));
-
             hMac.BlockUpdate(V, 0, V.Length);
-
             hMac.DoFinal(V, 0);
 
             hMac.BlockUpdate(V, 0, V.Length);
-            hMac.Update((byte)0x01);
+            hMac.Update(0x01);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            hMac.BlockUpdate(xm);
+#else
             hMac.BlockUpdate(x, 0, x.Length);
             hMac.BlockUpdate(m, 0, m.Length);
-
+#endif
             hMac.DoFinal(K, 0);
 
             hMac.Init(new KeyParameter(K));
-
             hMac.BlockUpdate(V, 0, V.Length);
-
             hMac.DoFinal(V, 0);
         }
 
@@ -109,7 +112,6 @@ namespace Org.BouncyCastle.Crypto.Signers
                 while (tOff < t.Length)
                 {
                     hMac.BlockUpdate(V, 0, V.Length);
-
                     hMac.DoFinal(V, 0);
 
                     int len = System.Math.Min(t.Length - tOff, V.Length);
@@ -120,19 +122,14 @@ namespace Org.BouncyCastle.Crypto.Signers
                 BigInteger k = BitsToInt(t);
 
                 if (k.SignValue > 0 && k.CompareTo(n) < 0)
-                {
                     return k;
-                }
 
                 hMac.BlockUpdate(V, 0, V.Length);
-                hMac.Update((byte)0x00);
-
+                hMac.Update(0x00);
                 hMac.DoFinal(K, 0);
 
                 hMac.Init(new KeyParameter(K));
-
                 hMac.BlockUpdate(V, 0, V.Length);
-
                 hMac.DoFinal(V, 0);
             }
         }
diff --git a/crypto/src/crypto/signers/Iso9796d2PssSigner.cs b/crypto/src/crypto/signers/Iso9796d2PssSigner.cs
index ad2718280..72afabf4c 100644
--- a/crypto/src/crypto/signers/Iso9796d2PssSigner.cs
+++ b/crypto/src/crypto/signers/Iso9796d2PssSigner.cs
@@ -109,42 +109,36 @@ namespace Org.BouncyCastle.Crypto.Signers
         /// <exception cref="ArgumentException">if wrong parameter type or a fixed
         /// salt is passed in which is the wrong length.
         /// </exception>
-        public virtual void Init(
-            bool				forSigning,
-            ICipherParameters	parameters)
+        public virtual void Init(bool forSigning, ICipherParameters parameters)
         {
             RsaKeyParameters kParam;
-            if (parameters is ParametersWithRandom)
+            if (parameters is ParametersWithRandom withRandom)
             {
-                ParametersWithRandom p = (ParametersWithRandom) parameters;
-
-                kParam = (RsaKeyParameters) p.Parameters;
+                kParam = (RsaKeyParameters)withRandom.Parameters;
 
                 if (forSigning)
                 {
-                    random = p.Random;
+                    random = withRandom.Random;
                 }
             }
-            else if (parameters is ParametersWithSalt)
+            else if (parameters is ParametersWithSalt withSalt)
             {
                 if (!forSigning)
-                    throw new ArgumentException("ParametersWithSalt only valid for signing", "parameters");
-
-                ParametersWithSalt p = (ParametersWithSalt) parameters;
+                    throw new ArgumentException("ParametersWithSalt only valid for signing", nameof(parameters));
 
-                kParam = (RsaKeyParameters) p.Parameters;
-                standardSalt = p.GetSalt();
+                kParam = (RsaKeyParameters)withSalt.Parameters;
+                standardSalt = withSalt.GetSalt();
 
                 if (standardSalt.Length != saltLength)
                     throw new ArgumentException("Fixed salt is of wrong length");
             }
             else
             {
-                kParam = (RsaKeyParameters) parameters;
+                kParam = (RsaKeyParameters)parameters;
 
                 if (forSigning)
                 {
-                    random = new SecureRandom();
+                    random = CryptoServicesRegistrar.GetSecureRandom();
                 }
             }
 
@@ -290,27 +284,46 @@ namespace Org.BouncyCastle.Crypto.Signers
             }
         }
 
-        /// <summary> update the internal digest with the byte array in</summary>
-        public virtual void BlockUpdate(
-            byte[]	input,
-            int		inOff,
-            int		length)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(inOff, inLen));
+#else
             if (preSig == null)
             {
-                while (length > 0 && messageLength < mBuf.Length)
+                while (inLen > 0 && messageLength < mBuf.Length)
                 {
                     this.Update(input[inOff]);
                     inOff++;
-                    length--;
+                    inLen--;
+                }
+            }
+
+            if (inLen > 0)
+            {
+                digest.BlockUpdate(input, inOff, inLen);
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (preSig == null)
+            {
+                while (!input.IsEmpty && messageLength < mBuf.Length)
+                {
+                    this.Update(input[0]);
+                    input = input[1..];
                 }
             }
 
-            if (length > 0)
+            if (!input.IsEmpty)
             {
-                digest.BlockUpdate(input, inOff, length);
+                digest.BlockUpdate(input);
             }
         }
+#endif
 
         /// <summary> reset the internal state</summary>
         public virtual void Reset()
diff --git a/crypto/src/crypto/signers/Iso9796d2Signer.cs b/crypto/src/crypto/signers/Iso9796d2Signer.cs
index f28c4ac71..ea1dc3f18 100644
--- a/crypto/src/crypto/signers/Iso9796d2Signer.cs
+++ b/crypto/src/crypto/signers/Iso9796d2Signer.cs
@@ -218,9 +218,7 @@ namespace Org.BouncyCastle.Crypto.Signers
             recoveredMessage.CopyTo(mBuf, 0);
         }
 
-        /// <summary> update the internal digest with the byte b</summary>
-        public virtual void Update(
-            byte input)
+        public virtual void Update(byte input)
         {
             digest.Update(input);
 
@@ -232,26 +230,42 @@ namespace Org.BouncyCastle.Crypto.Signers
             messageLength++;
         }
 
-        /// <summary> update the internal digest with the byte array in</summary>
-        public virtual void BlockUpdate(
-            byte[]	input,
-            int		inOff,
-            int		length)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
         {
-            while (length > 0 && messageLength < mBuf.Length)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(input.AsSpan(inOff, inLen));
+#else
+            while (inLen > 0 && messageLength < mBuf.Length)
             {
-                //for (int i = 0; i < length && (i + messageLength) < mBuf.Length; i++)
-                //{
-                //    mBuf[messageLength + i] = input[inOff + i];
-                //}
                 this.Update(input[inOff]);
                 inOff++;
-                length--;
+                inLen--;
             }
 
-            digest.BlockUpdate(input, inOff, length);
-            messageLength += length;
+            if (inLen > 0)
+            {
+                digest.BlockUpdate(input, inOff, inLen);
+                messageLength += inLen;
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            while (!input.IsEmpty && messageLength < mBuf.Length)
+            {
+                this.Update(input[0]);
+                input = input[1..];
+            }
+
+            if (!input.IsEmpty)
+            {
+                digest.BlockUpdate(input);
+                messageLength += input.Length;
+            }
         }
+#endif
 
         /// <summary> reset the internal state</summary>
         public virtual void Reset()
diff --git a/crypto/src/crypto/signers/PssSigner.cs b/crypto/src/crypto/signers/PssSigner.cs
index 2a941df47..df73a7472 100644
--- a/crypto/src/crypto/signers/PssSigner.cs
+++ b/crypto/src/crypto/signers/PssSigner.cs
@@ -152,22 +152,18 @@ namespace Org.BouncyCastle.Crypto.Signers
 			get { return mgfDigest.AlgorithmName + "withRSAandMGF1"; }
 		}
 
-		public virtual void Init(
-			bool				forSigning,
-			ICipherParameters	parameters)
+		public virtual void Init(bool forSigning, ICipherParameters parameters)
 		{
-			if (parameters is ParametersWithRandom)
+			if (parameters is ParametersWithRandom withRandom)
 			{
-				ParametersWithRandom p = (ParametersWithRandom) parameters;
-
-				parameters = p.Parameters;
-				random = p.Random;
+				parameters = withRandom.Parameters;
+				random = withRandom.Random;
 			}
 			else
 			{
 				if (forSigning)
 				{
-					random = new SecureRandom();
+					random = CryptoServicesRegistrar.GetSecureRandom();
 				}
 			}
 
@@ -176,11 +172,11 @@ namespace Org.BouncyCastle.Crypto.Signers
 			RsaKeyParameters kParam;
 			if (parameters is RsaBlindingParameters)
 			{
-				kParam = ((RsaBlindingParameters) parameters).PublicKey;
+				kParam = ((RsaBlindingParameters)parameters).PublicKey;
 			}
 			else
 			{
-				kParam = (RsaKeyParameters) parameters;
+				kParam = (RsaKeyParameters)parameters;
 			}
 
 			emBits = kParam.Modulus.BitLength - 1;
@@ -198,31 +194,28 @@ namespace Org.BouncyCastle.Crypto.Signers
 			Array.Clear(block, 0, block.Length);
 		}
 
-		/// <summary> update the internal digest with the byte b</summary>
-		public virtual void Update(
-			byte input)
+		public virtual void Update(byte input)
 		{
 			contentDigest1.Update(input);
 		}
 
-		/// <summary> update the internal digest with the byte array in</summary>
-		public virtual void BlockUpdate(
-			byte[]	input,
-			int		inOff,
-			int		length)
+		public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
+		{
+			contentDigest1.BlockUpdate(input, inOff, inLen);
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public virtual void BlockUpdate(ReadOnlySpan<byte> input)
 		{
-			contentDigest1.BlockUpdate(input, inOff, length);
+			contentDigest1.BlockUpdate(input);
 		}
+#endif
 
-		/// <summary> reset the internal state</summary>
 		public virtual void Reset()
 		{
 			contentDigest1.Reset();
 		}
 
-		/// <summary> Generate a signature for the message we've been loaded with using
-		/// the key we were initialised with.
-		/// </summary>
 		public virtual byte[] GenerateSignature()
 		{
 			if (contentDigest1.GetDigestSize() != hLen)
@@ -268,11 +261,7 @@ namespace Org.BouncyCastle.Crypto.Signers
 			return b;
 		}
 
-		/// <summary> return true if the internal state represents the signature described
-		/// in the passed in array.
-		/// </summary>
-		public virtual bool VerifySignature(
-			byte[] signature)
+		public virtual bool VerifySignature(byte[] signature)
 		{
 			if (contentDigest1.GetDigestSize() != hLen)
 				throw new InvalidOperationException();
@@ -365,7 +354,7 @@ namespace Org.BouncyCastle.Crypto.Signers
 			{
 				byte[] mask = new byte[length];
 				mgfDigest.BlockUpdate(Z, zOff, zLen);
-				((IXof)mgfDigest).DoFinal(mask, 0, mask.Length);
+				((IXof)mgfDigest).OutputFinal(mask, 0, mask.Length);
 
 				return mask;
 			}
diff --git a/crypto/src/crypto/signers/RsaDigestSigner.cs b/crypto/src/crypto/signers/RsaDigestSigner.cs
index 25bd4af4e..75b3a24b9 100644
--- a/crypto/src/crypto/signers/RsaDigestSigner.cs
+++ b/crypto/src/crypto/signers/RsaDigestSigner.cs
@@ -122,30 +122,23 @@ namespace Org.BouncyCastle.Crypto.Signers
             rsaEngine.Init(forSigning, parameters);
         }
 
-        /**
-         * update the internal digest with the byte b
-         */
-        public virtual void Update(
-            byte input)
+        public virtual void Update(byte input)
         {
             digest.Update(input);
         }
 
-        /**
-         * update the internal digest with the byte array in
-         */
-        public virtual void BlockUpdate(
-            byte[]	input,
-            int		inOff,
-            int		length)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
         {
-            digest.BlockUpdate(input, inOff, length);
+            digest.BlockUpdate(input, inOff, inLen);
         }
 
-        /**
-         * Generate a signature for the message we've been loaded with using
-         * the key we were initialised with.
-         */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            digest.BlockUpdate(input);
+        }
+#endif
+
         public virtual byte[] GenerateSignature()
         {
             if (!forSigning)
@@ -158,12 +151,7 @@ namespace Org.BouncyCastle.Crypto.Signers
             return rsaEngine.ProcessBlock(data, 0, data.Length);
         }
 
-        /**
-         * return true if the internal state represents the signature described
-         * in the passed in array.
-         */
-        public virtual bool VerifySignature(
-            byte[] signature)
+        public virtual bool VerifySignature(byte[] signature)
         {
             if (forSigning)
                 throw new InvalidOperationException("RsaDigestSigner not initialised for verification");
diff --git a/crypto/src/crypto/signers/SM2Signer.cs b/crypto/src/crypto/signers/SM2Signer.cs
index c344a220a..07b41bd30 100644
--- a/crypto/src/crypto/signers/SM2Signer.cs
+++ b/crypto/src/crypto/signers/SM2Signer.cs
@@ -72,10 +72,8 @@ namespace Org.BouncyCastle.Crypto.Signers
 
             if (forSigning)
             {
-                if (baseParam is ParametersWithRandom)
+                if (baseParam is ParametersWithRandom rParam)
                 {
-                    ParametersWithRandom rParam = (ParametersWithRandom)baseParam;
-
                     ecKey = (ECKeyParameters)rParam.Parameters;
                     ecParams = ecKey.Parameters;
                     kCalculator.Init(ecParams.N, rParam.Random);
@@ -84,7 +82,7 @@ namespace Org.BouncyCastle.Crypto.Signers
                 {
                     ecKey = (ECKeyParameters)baseParam;
                     ecParams = ecKey.Parameters;
-                    kCalculator.Init(ecParams.N, new SecureRandom());
+                    kCalculator.Init(ecParams.N, CryptoServicesRegistrar.GetSecureRandom());
                 }
                 pubPoint = CreateBasePointMultiplier().Multiply(ecParams.G, ((ECPrivateKeyParameters)ecKey).D).Normalize();
             }
@@ -106,10 +104,17 @@ namespace Org.BouncyCastle.Crypto.Signers
             digest.Update(b);
         }
 
-        public virtual void BlockUpdate(byte[] buf, int off, int len)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            digest.BlockUpdate(input, inOff, inLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            digest.BlockUpdate(buf, off, len);
+            digest.BlockUpdate(input);
         }
+#endif
 
         public virtual bool VerifySignature(byte[] signature)
         {
diff --git a/crypto/src/crypto/signers/X931Signer.cs b/crypto/src/crypto/signers/X931Signer.cs
index 0907403a8..40255c40c 100644
--- a/crypto/src/crypto/signers/X931Signer.cs
+++ b/crypto/src/crypto/signers/X931Signer.cs
@@ -3,6 +3,7 @@
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Zlib;
 
 namespace Org.BouncyCastle.Crypto.Signers
 {
@@ -82,46 +83,34 @@ namespace Org.BouncyCastle.Crypto.Signers
             Reset();
         }
 
-        /// <summary> clear possible sensitive data</summary>
-        private void ClearBlock(byte[] block)
+        public virtual void Update(byte b)
         {
-            Array.Clear(block, 0, block.Length);
+            digest.Update(b);
         }
 
-        /**
-         * update the internal digest with the byte b
-         */
-        public virtual void Update(byte b)
+        public virtual void BlockUpdate(byte[] input, int inOff, int inLen)
         {
-            digest.Update(b);
+            digest.BlockUpdate(input, inOff, inLen);
         }
 
-        /**
-         * update the internal digest with the byte array in
-         */
-        public virtual void BlockUpdate(byte[] input, int off, int len)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            digest.BlockUpdate(input, off, len);
+            digest.BlockUpdate(input);
         }
+#endif
 
-        /**
-         * reset the internal state
-         */
         public virtual void Reset()
         {
             digest.Reset();
         }
 
-        /**
-         * generate a signature for the loaded message using the key we were
-         * initialised with.
-         */
         public virtual byte[] GenerateSignature()
         {
             CreateSignatureBlock();
 
             BigInteger t = new BigInteger(1, cipher.ProcessBlock(block, 0, block.Length));
-            ClearBlock(block);
+            Arrays.Fill(block, 0x00);
 
             t = t.Min(kParam.Modulus.Subtract(t));
 
@@ -156,10 +145,6 @@ namespace Org.BouncyCastle.Crypto.Signers
             block[delta - 1] = (byte)0xba;
         }
 
-        /**
-         * return true if the signature represents a ISO9796-2 signature
-         * for the passed in message.
-         */
         public virtual bool VerifySignature(byte[] signature)
         {
             try
@@ -193,12 +178,20 @@ namespace Org.BouncyCastle.Crypto.Signers
 
             CreateSignatureBlock();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int fBlockSize = block.Length;
+            Span<byte> fBlock = fBlockSize <= 512
+                ? stackalloc byte[fBlockSize]
+                : new byte[fBlockSize];
+            BigIntegers.AsUnsignedByteArray(f, fBlock);
+#else
             byte[] fBlock = BigIntegers.AsUnsignedByteArray(block.Length, f);
+#endif
 
             bool rv = Arrays.ConstantTimeAreEqual(block, fBlock);
 
-            ClearBlock(block);
-            ClearBlock(fBlock);
+            Arrays.Fill(block, 0x00);
+            Arrays.Fill<byte>(fBlock, 0x00);
 
             return rv;
         }
diff --git a/crypto/src/crypto/util/CipherFactory.cs b/crypto/src/crypto/util/CipherFactory.cs
index 2e8c44bb4..56f57c545 100644
--- a/crypto/src/crypto/util/CipherFactory.cs
+++ b/crypto/src/crypto/util/CipherFactory.cs
@@ -110,7 +110,7 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         private static BufferedBlockCipher CreateCipher(DerObjectIdentifier algorithm)
         {
-            IBlockCipher cipher;
+            IBlockCipherMode cipher;
 
             if (NistObjectIdentifiers.IdAes128Cbc.Equals(algorithm)
                 || NistObjectIdentifiers.IdAes192Cbc.Equals(algorithm)
diff --git a/crypto/src/crypto/util/CipherKeyGeneratorFactory.cs b/crypto/src/crypto/util/CipherKeyGeneratorFactory.cs
index efaad138c..2e48513ad 100644
--- a/crypto/src/crypto/util/CipherKeyGeneratorFactory.cs
+++ b/crypto/src/crypto/util/CipherKeyGeneratorFactory.cs
@@ -11,12 +11,8 @@ using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Crypto.Utilities
 {
-    public class CipherKeyGeneratorFactory
+    public static class CipherKeyGeneratorFactory
     {
-        private CipherKeyGeneratorFactory()
-        {
-        }
-
         /**
          * Create a key generator for the passed in Object Identifier.
          *
diff --git a/crypto/src/crypto/util/Pack.cs b/crypto/src/crypto/util/Pack.cs
index fd6ee1d9f..a12253e59 100644
--- a/crypto/src/crypto/util/Pack.cs
+++ b/crypto/src/crypto/util/Pack.cs
@@ -1,23 +1,32 @@
 using System;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers.Binary;
+#endif
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
 
 namespace Org.BouncyCastle.Crypto.Utilities
 {
-    internal sealed class Pack
+    internal static class Pack
     {
-        private Pack()
-        {
-        }
-
         internal static void UInt16_To_BE(ushort n, byte[] bs)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt16BigEndian(bs, n);
+#else
             bs[0] = (byte)(n >> 8);
-            bs[1] = (byte)(n);
+            bs[1] = (byte)n;
+#endif
         }
 
         internal static void UInt16_To_BE(ushort n, byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt16BigEndian(bs.AsSpan(off), n);
+#else
             bs[off] = (byte)(n >> 8);
-            bs[off + 1] = (byte)(n);
+            bs[off + 1] = (byte)n;
+#endif
         }
 
         internal static void UInt16_To_BE(ushort[] ns, byte[] bs, int off)
@@ -59,9 +68,13 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static ushort BE_To_UInt16(byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt16BigEndian(bs.AsSpan(off));
+#else
             uint n = (uint)bs[off] << 8
-                | (uint)bs[off + 1];
+                | bs[off + 1];
             return (ushort)n;
+#endif
         }
 
         internal static void BE_To_UInt16(byte[] bs, int bsOff, ushort[] ns, int nsOff)
@@ -89,18 +102,26 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static void UInt32_To_BE(uint n, byte[] bs)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt32BigEndian(bs, n);
+#else
             bs[0] = (byte)(n >> 24);
             bs[1] = (byte)(n >> 16);
             bs[2] = (byte)(n >> 8);
-            bs[3] = (byte)(n);
+            bs[3] = (byte)n;
+#endif
         }
 
         internal static void UInt32_To_BE(uint n, byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt32BigEndian(bs.AsSpan(off), n);
+#else
             bs[off] = (byte)(n >> 24);
             bs[off + 1] = (byte)(n >> 16);
             bs[off + 2] = (byte)(n >> 8);
-            bs[off + 3] = (byte)(n);
+            bs[off + 3] = (byte)n;
+#endif
         }
 
         internal static void UInt32_To_BE(uint[] ns, byte[] bs, int off)
@@ -137,18 +158,39 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static uint BE_To_UInt32(byte[] bs)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt32BigEndian(bs);
+#else
             return (uint)bs[0] << 24
                 | (uint)bs[1] << 16
                 | (uint)bs[2] << 8
-                | (uint)bs[3];
+                | bs[3];
+#endif
         }
 
         internal static uint BE_To_UInt32(byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt32BigEndian(bs.AsSpan(off));
+#else
             return (uint)bs[off] << 24
                 | (uint)bs[off + 1] << 16
                 | (uint)bs[off + 2] << 8
-                | (uint)bs[off + 3];
+                | bs[off + 3];
+#endif
+        }
+
+        internal static uint BE_To_UInt32_Partial(byte[] bs, int off, int len)
+        {
+            Debug.Assert(1 <= len && len <= 4);
+
+            uint result = bs[off];
+            for (int i = 1; i < len; ++i)
+            {
+                result <<= 8;
+                result |= bs[off + i];
+            }
+            return result;
         }
 
         internal static void BE_To_UInt32(byte[] bs, int off, uint[] ns)
@@ -178,14 +220,22 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static void UInt64_To_BE(ulong n, byte[] bs)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt64BigEndian(bs, n);
+#else
             UInt32_To_BE((uint)(n >> 32), bs);
-            UInt32_To_BE((uint)(n), bs, 4);
+            UInt32_To_BE((uint)n, bs, 4);
+#endif
         }
 
         internal static void UInt64_To_BE(ulong n, byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt64BigEndian(bs.AsSpan(off), n);
+#else
             UInt32_To_BE((uint)(n >> 32), bs, off);
-            UInt32_To_BE((uint)(n), bs, off + 4);
+            UInt32_To_BE((uint)n, bs, off + 4);
+#endif
         }
 
         internal static byte[] UInt64_To_BE(ulong[] ns)
@@ -213,11 +263,39 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        internal static ulong BE_To_UInt64(byte[] bs)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt64BigEndian(bs);
+#else
+            uint hi = BE_To_UInt32(bs);
+            uint lo = BE_To_UInt32(bs, 4);
+            return ((ulong)hi << 32) | (ulong)lo;
+#endif
+        }
+
         internal static ulong BE_To_UInt64(byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt64BigEndian(bs.AsSpan(off));
+#else
             uint hi = BE_To_UInt32(bs, off);
             uint lo = BE_To_UInt32(bs, off + 4);
             return ((ulong)hi << 32) | (ulong)lo;
+#endif
+        }
+
+        internal static ulong BE_To_UInt64_Partial(byte[] bs, int off, int len)
+        {
+            Debug.Assert(1 <= len && len <= 8);
+
+            ulong result = bs[off];
+            for (int i = 1; i < len; ++i)
+            {
+                result <<= 8;
+                result |= bs[off + i];
+            }
+            return result;
         }
 
         internal static void BE_To_UInt64(byte[] bs, int off, ulong[] ns)
@@ -240,14 +318,22 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static void UInt16_To_LE(ushort n, byte[] bs)
         {
-            bs[0] = (byte)(n);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt16LittleEndian(bs, n);
+#else
+            bs[0] = (byte)n;
             bs[1] = (byte)(n >> 8);
+#endif
         }
 
         internal static void UInt16_To_LE(ushort n, byte[] bs, int off)
         {
-            bs[off] = (byte)(n);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt16LittleEndian(bs.AsSpan(off), n);
+#else
+            bs[off] = (byte)n;
             bs[off + 1] = (byte)(n >> 8);
+#endif
         }
 
         internal static byte[] UInt16_To_LE(ushort n)
@@ -259,16 +345,24 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static ushort LE_To_UInt16(byte[] bs)
         {
-            uint n = (uint)bs[0]
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt16LittleEndian(bs);
+#else
+            uint n = bs[0]
                 | (uint)bs[1] << 8;
             return (ushort)n;
+#endif
         }
 
         internal static ushort LE_To_UInt16(byte[] bs, int off)
         {
-            uint n = (uint)bs[off]
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt16LittleEndian(bs.AsSpan(off));
+#else
+            uint n = bs[off]
                 | (uint)bs[off + 1] << 8;
             return (ushort)n;
+#endif
         }
 
         internal static byte[] UInt32_To_LE(uint n)
@@ -280,18 +374,26 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static void UInt32_To_LE(uint n, byte[] bs)
         {
-            bs[0] = (byte)(n);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt32LittleEndian(bs, n);
+#else
+            bs[0] = (byte)n;
             bs[1] = (byte)(n >> 8);
             bs[2] = (byte)(n >> 16);
             bs[3] = (byte)(n >> 24);
+#endif
         }
 
         internal static void UInt32_To_LE(uint n, byte[] bs, int off)
         {
-            bs[off] = (byte)(n);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt32LittleEndian(bs.AsSpan(off), n);
+#else
+            bs[off] = (byte)n;
             bs[off + 1] = (byte)(n >> 8);
             bs[off + 2] = (byte)(n >> 16);
             bs[off + 3] = (byte)(n >> 24);
+#endif
         }
 
         internal static byte[] UInt32_To_LE(uint[] ns)
@@ -319,20 +421,35 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        internal static uint LE_To_UInt24(byte[] bs, int off)
+        {
+            return bs[off]
+                | (uint)bs[off + 1] << 8
+                | (uint)bs[off + 2] << 16;
+        }
+
         internal static uint LE_To_UInt32(byte[] bs)
         {
-            return (uint)bs[0]
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt32LittleEndian(bs);
+#else
+            return bs[0]
                 | (uint)bs[1] << 8
                 | (uint)bs[2] << 16
                 | (uint)bs[3] << 24;
+#endif
         }
 
         internal static uint LE_To_UInt32(byte[] bs, int off)
         {
-            return (uint)bs[off]
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt32LittleEndian(bs.AsSpan(off));
+#else
+            return bs[off]
                 | (uint)bs[off + 1] << 8
                 | (uint)bs[off + 2] << 16
                 | (uint)bs[off + 3] << 24;
+#endif
         }
 
         internal static void LE_To_UInt32(byte[] bs, int off, uint[] ns)
@@ -373,14 +490,22 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static void UInt64_To_LE(ulong n, byte[] bs)
         {
-            UInt32_To_LE((uint)(n), bs);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt64LittleEndian(bs, n);
+#else
+            UInt32_To_LE((uint)n, bs);
             UInt32_To_LE((uint)(n >> 32), bs, 4);
+#endif
         }
 
         internal static void UInt64_To_LE(ulong n, byte[] bs, int off)
         {
-            UInt32_To_LE((uint)(n), bs, off);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BinaryPrimitives.WriteUInt64LittleEndian(bs.AsSpan(off), n);
+#else
+            UInt32_To_LE((uint)n, bs, off);
             UInt32_To_LE((uint)(n >> 32), bs, off + 4);
+#endif
         }
 
         internal static byte[] UInt64_To_LE(ulong[] ns)
@@ -410,16 +535,24 @@ namespace Org.BouncyCastle.Crypto.Utilities
 
         internal static ulong LE_To_UInt64(byte[] bs)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt64LittleEndian(bs);
+#else
             uint lo = LE_To_UInt32(bs);
             uint hi = LE_To_UInt32(bs, 4);
             return ((ulong)hi << 32) | (ulong)lo;
+#endif
         }
 
         internal static ulong LE_To_UInt64(byte[] bs, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReadUInt64LittleEndian(bs.AsSpan(off));
+#else
             uint lo = LE_To_UInt32(bs, off);
             uint hi = LE_To_UInt32(bs, off + 4);
             return ((ulong)hi << 32) | (ulong)lo;
+#endif
         }
 
         internal static void LE_To_UInt64(byte[] bs, int off, ulong[] ns)
@@ -441,14 +574,13 @@ namespace Org.BouncyCastle.Crypto.Utilities
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static uint BE_To_UInt32(ReadOnlySpan<byte> bs)
         {
-            return (uint)bs[0] << 24
-                |  (uint)bs[1] << 16
-                |  (uint)bs[2] <<  8
-                |        bs[3];
+            return BinaryPrimitives.ReadUInt32BigEndian(bs);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void BE_To_UInt32(ReadOnlySpan<byte> bs, Span<uint> ns)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -458,13 +590,28 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static uint BE_To_UInt32_Partial(ReadOnlySpan<byte> bs)
+        {
+            int len = bs.Length;
+            Debug.Assert(1 <= len && len <= 4);
+
+            uint result = bs[0];
+            for (int i = 1; i < len; ++i)
+            {
+                result <<= 8;
+                result |= bs[i];
+            }
+            return result;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static ulong BE_To_UInt64(ReadOnlySpan<byte> bs)
         {
-            uint hi = BE_To_UInt32(bs);
-            uint lo = BE_To_UInt32(bs[4..]);
-            return ((ulong)hi << 32) | lo;
+            return BinaryPrimitives.ReadUInt64BigEndian(bs);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void BE_To_UInt64(ReadOnlySpan<byte> bs, Span<ulong> ns)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -474,14 +621,34 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static ulong BE_To_UInt64_Partial(ReadOnlySpan<byte> bs)
+        {
+            int len = bs.Length;
+            Debug.Assert(1 <= len && len <= 8);
+
+            ulong result = bs[0];
+            for (int i = 1; i < len; ++i)
+            {
+                result <<= 8;
+                result |= bs[i];
+            }
+            return result;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static ushort LE_To_UInt16(ReadOnlySpan<byte> bs)
+        {
+            return BinaryPrimitives.ReadUInt16LittleEndian(bs);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static uint LE_To_UInt32(ReadOnlySpan<byte> bs)
         {
-            return      bs[0]
-                | (uint)bs[1] <<  8
-                | (uint)bs[2] << 16
-                | (uint)bs[3] << 24;
+            return BinaryPrimitives.ReadUInt32LittleEndian(bs);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void LE_To_UInt32(ReadOnlySpan<byte> bs, Span<uint> ns)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -491,13 +658,13 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static ulong LE_To_UInt64(ReadOnlySpan<byte> bs)
         {
-            uint lo = LE_To_UInt32(bs);
-            uint hi = LE_To_UInt32(bs[4..]);
-            return (ulong)hi << 32 | lo;
+            return BinaryPrimitives.ReadUInt64LittleEndian(bs);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void LE_To_UInt64(ReadOnlySpan<byte> bs, Span<ulong> ns)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -507,14 +674,25 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void UInt16_To_BE(ushort n, Span<byte> bs)
+        {
+            BinaryPrimitives.WriteUInt16BigEndian(bs, n);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void UInt16_To_LE(ushort n, Span<byte> bs)
+        {
+            BinaryPrimitives.WriteUInt16LittleEndian(bs, n);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt32_To_BE(uint n, Span<byte> bs)
         {
-            bs[0] = (byte)(n >> 24);
-            bs[1] = (byte)(n >> 16);
-            bs[2] = (byte)(n >>  8);
-            bs[3] = (byte) n;
+            BinaryPrimitives.WriteUInt32BigEndian(bs, n);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt32_To_BE(ReadOnlySpan<uint> ns, Span<byte> bs)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -524,14 +702,13 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt32_To_LE(uint n, Span<byte> bs)
         {
-            bs[0] = (byte) n;
-            bs[1] = (byte)(n >>  8);
-            bs[2] = (byte)(n >> 16);
-            bs[3] = (byte)(n >> 24);
+            BinaryPrimitives.WriteUInt32LittleEndian(bs, n);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt32_To_LE(ReadOnlySpan<uint> ns, Span<byte> bs)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -541,12 +718,13 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt64_To_BE(ulong n, Span<byte> bs)
         {
-            UInt32_To_BE((uint)(n >> 32), bs);
-            UInt32_To_BE((uint)n, bs[4..]);
+            BinaryPrimitives.WriteUInt64BigEndian(bs, n);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt64_To_BE(ReadOnlySpan<ulong> ns, Span<byte> bs)
         {
             for (int i = 0; i < ns.Length; ++i)
@@ -556,12 +734,13 @@ namespace Org.BouncyCastle.Crypto.Utilities
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt64_To_LE(ulong n, Span<byte> bs)
         {
-            UInt32_To_LE((uint)n, bs);
-            UInt32_To_LE((uint)(n >> 32), bs[4..]);
+            BinaryPrimitives.WriteUInt64LittleEndian(bs, n);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static void UInt64_To_LE(ReadOnlySpan<ulong> ns, Span<byte> bs)
         {
             for (int i = 0; i < ns.Length; ++i)
diff --git a/crypto/src/math/BigInteger.cs b/crypto/src/math/BigInteger.cs
index d6c43cdc0..caf78843e 100644
--- a/crypto/src/math/BigInteger.cs
+++ b/crypto/src/math/BigInteger.cs
@@ -7,14 +7,14 @@ using System.Runtime.Intrinsics.X86;
 #endif
 using System.Runtime.Serialization;
 using System.Text;
-
+using Org.BouncyCastle.Crypto.Prng;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Math
 {
     [Serializable]
-    public class BigInteger
+    public sealed class BigInteger
     {
         // The first few odd primes
         /*
@@ -163,8 +163,6 @@ namespace Org.BouncyCastle.Math
         private const int chunk2 = 1, chunk8 = 1, chunk10 = 19, chunk16 = 16;
         private static readonly BigInteger radix2, radix2E, radix8, radix8E, radix10, radix10E, radix16, radix16E;
 
-        private static readonly SecureRandom RandomSource = new SecureRandom();
-
         /*
          * These are the threshold bit-lengths (of an exponent) where we increase the window size.
          * They are calculated according to the expected savings in multiplications.
@@ -237,15 +235,14 @@ namespace Org.BouncyCastle.Math
             this.mQuote = 0;
         }
 
-        private static int GetByteLength(
-            int nBits)
+        private static int GetByteLength(int nBits)
         {
             return (nBits + BitsPerByte - 1) / BitsPerByte;
         }
 
         public static BigInteger Arbitrary(int sizeInBits)
         {
-            return new BigInteger(sizeInBits, RandomSource);
+            return new BigInteger(sizeInBits, SecureRandom.ArbitraryRandom);
         }
 
         private BigInteger(
@@ -510,7 +507,14 @@ namespace Org.BouncyCastle.Math
                 else
                 {
                     int numBytes = end - iBval;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    Span<byte> inverse = numBytes <= 512
+                        ? stackalloc byte[numBytes]
+                        : new byte[numBytes];
+#else
                     byte[] inverse = new byte[numBytes];
+#endif
 
                     int index = 0;
                     while (index < numBytes)
@@ -527,7 +531,7 @@ namespace Org.BouncyCastle.Math
 
                     inverse[index]++;
 
-                    this.magnitude = MakeMagnitude(inverse, 0, inverse.Length);
+                    this.magnitude = MakeMagnitude(inverse);
                 }
             }
             else
@@ -538,24 +542,26 @@ namespace Org.BouncyCastle.Math
             }
         }
 
-        private static int[] MakeMagnitude(
-            byte[]	bytes,
-            int		offset,
-            int		length)
+        private static int[] MakeMagnitude(byte[] bytes)
+        {
+            return MakeMagnitude(bytes, 0, bytes.Length);
+        }
+
+        private static int[] MakeMagnitude(byte[] bytes, int offset, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return MakeMagnitude(bytes.AsSpan(offset, length));
+#else
             int end = offset + length;
 
             // strip leading zeros
             int firstSignificant;
-            for (firstSignificant = offset; firstSignificant < end
-                && bytes[firstSignificant] == 0; firstSignificant++)
+            for (firstSignificant = offset; firstSignificant < end && bytes[firstSignificant] == 0; firstSignificant++)
             {
             }
 
             if (firstSignificant >= end)
-            {
                 return ZeroMagnitude;
-            }
 
             int nInts = (end - firstSignificant + 3) / BytesPerInt;
             int bCount = (end - firstSignificant) % BytesPerInt;
@@ -565,10 +571,59 @@ namespace Org.BouncyCastle.Math
             }
 
             if (nInts < 1)
+                return ZeroMagnitude;
+
+            int[] mag = new int[nInts];
+
+            int v = 0;
+            int magnitudeIndex = 0;
+            for (int i = firstSignificant; i < end; ++i)
+            {
+                v <<= 8;
+                v |= bytes[i] & 0xff;
+                bCount--;
+                if (bCount <= 0)
+                {
+                    mag[magnitudeIndex] = v;
+                    magnitudeIndex++;
+                    bCount = BytesPerInt;
+                    v = 0;
+                }
+            }
+
+            if (magnitudeIndex < mag.Length)
             {
+                mag[magnitudeIndex] = v;
+            }
+
+            return mag;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int[] MakeMagnitude(ReadOnlySpan<byte> bytes)
+        {
+            int end = bytes.Length;
+
+            // strip leading zeros
+            int firstSignificant;
+            for (firstSignificant = 0; firstSignificant < end && bytes[firstSignificant] == 0; firstSignificant++)
+            {
+            }
+
+            if (firstSignificant >= end)
                 return ZeroMagnitude;
+
+            int nInts = (end - firstSignificant + 3) / BytesPerInt;
+            int bCount = (end - firstSignificant) % BytesPerInt;
+            if (bCount == 0)
+            {
+                bCount = BytesPerInt;
             }
 
+            if (nInts < 1)
+                return ZeroMagnitude;
+
             int[] mag = new int[nInts];
 
             int v = 0;
@@ -594,19 +649,14 @@ namespace Org.BouncyCastle.Math
 
             return mag;
         }
+#endif
 
-        public BigInteger(
-            int		sign,
-            byte[]	bytes)
+        public BigInteger(int sign, byte[] bytes)
             : this(sign, bytes, 0, bytes.Length)
         {
         }
 
-        public BigInteger(
-            int		sign,
-            byte[]	bytes,
-            int		offset,
-            int		length)
+        public BigInteger(int sign, byte[] bytes, int offset, int length)
         {
             if (sign < -1 || sign > 1)
                 throw new FormatException("Invalid sign value");
@@ -624,6 +674,26 @@ namespace Org.BouncyCastle.Math
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public BigInteger(int sign, ReadOnlySpan<byte> bytes)
+        {
+            if (sign < -1 || sign > 1)
+                throw new FormatException("Invalid sign value");
+
+            if (sign == 0)
+            {
+                this.sign = 0;
+                this.magnitude = ZeroMagnitude;
+            }
+            else
+            {
+                // copy bytes
+                this.magnitude = MakeMagnitude(bytes);
+                this.sign = this.magnitude.Length < 1 ? 0 : sign;
+            }
+        }
+#endif
+
         public BigInteger(
             int		sizeInBits,
             Random	random)
@@ -642,14 +712,21 @@ namespace Org.BouncyCastle.Math
             }
 
             int nBytes = GetByteLength(sizeInBits);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> b = nBytes <= 512
+                ? stackalloc byte[nBytes]
+                : new byte[nBytes];
+#else
             byte[] b = new byte[nBytes];
+#endif
             random.NextBytes(b);
 
             // strip off any excess bits in the MSB
             int xBits = BitsPerByte * nBytes - sizeInBits;
             b[0] &= (byte)(255U >> xBits);
 
-            this.magnitude = MakeMagnitude(b, 0, b.Length);
+            this.magnitude = MakeMagnitude(b);
             this.sign = this.magnitude.Length < 1 ? 0 : 1;
         }
 
@@ -673,7 +750,14 @@ namespace Org.BouncyCastle.Math
             }
              
             int nBytes = GetByteLength(bitLength);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> b = nBytes <= 512
+                ? stackalloc byte[nBytes]
+                : new byte[nBytes];
+#else
             byte[] b = new byte[nBytes];
+#endif
 
             int xBits = BitsPerByte * nBytes - bitLength;
             byte mask = (byte)(255U >> xBits);
@@ -692,7 +776,7 @@ namespace Org.BouncyCastle.Math
                 // ensure the trailing bit is 1 (i.e. must be odd)
                 b[nBytes - 1] |= 1;
 
-                this.magnitude = MakeMagnitude(b, 0, b.Length);
+                this.magnitude = MakeMagnitude(b);
                 this.nBits = -1;
                 this.mQuote = 0;
 
@@ -1374,7 +1458,7 @@ namespace Org.BouncyCastle.Math
             if (n.Equals(One))
                 return false;
 
-            return n.CheckProbablePrime(certainty, RandomSource, randomlySelected);
+            return n.CheckProbablePrime(certainty, SecureRandom.ArbitraryRandom, randomlySelected);
         }
 
         private bool CheckProbablePrime(int certainty, Random random, bool randomlySelected)
@@ -2547,7 +2631,7 @@ namespace Org.BouncyCastle.Math
 
             BigInteger n = Inc().SetBit(0);
 
-            while (!n.CheckProbablePrime(100, RandomSource, false))
+            while (!n.CheckProbablePrime(100, SecureRandom.ArbitraryRandom, false))
             {
                 n = n.Add(Two);
             }
@@ -3108,18 +3192,41 @@ namespace Org.BouncyCastle.Math
             return Subtract(0, res, 0, lilMag);
         }
 
+        public int GetLengthofByteArray()
+        {
+            return GetByteLength(BitLength + 1);
+        }
+
+        public int GetLengthofByteArrayUnsigned()
+        {
+            return GetByteLength(sign < 0 ? BitLength + 1 : BitLength);
+        }
+
         public byte[] ToByteArray()
         {
             return ToByteArray(false);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ToByteArray(Span<byte> output)
+        {
+            ToByteArray(false, output);
+        }
+#endif
+
         public byte[] ToByteArrayUnsigned()
         {
             return ToByteArray(true);
         }
 
-        private byte[] ToByteArray(
-            bool unsigned)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ToByteArrayUnsigned(Span<byte> output)
+        {
+            ToByteArray(true, output);
+        }
+#endif
+
+        private byte[] ToByteArray(bool unsigned)
         {
             if (sign == 0)
                 return unsigned ? ZeroEncoding : new byte[1];
@@ -3198,6 +3305,90 @@ namespace Org.BouncyCastle.Math
             return bytes;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ToByteArray(bool unsigned, Span<byte> output)
+        {
+            if (sign == 0)
+            {
+                if (!unsigned)
+                {
+                    output[0] = 0;
+                }
+                return;
+            }
+
+            int nBits = (unsigned && sign > 0) ? BitLength : BitLength + 1;
+
+            int nBytes = GetByteLength(nBits);
+            if (nBytes > output.Length)
+                throw new ArgumentException("insufficient space", nameof(output));
+
+            int magIndex = magnitude.Length;
+            int bytesIndex = nBytes;
+
+            if (sign > 0)
+            {
+                while (magIndex > 1)
+                {
+                    uint mag = (uint) magnitude[--magIndex];
+                    output[--bytesIndex] = (byte) mag;
+                    output[--bytesIndex] = (byte)(mag >> 8);
+                    output[--bytesIndex] = (byte)(mag >> 16);
+                    output[--bytesIndex] = (byte)(mag >> 24);
+                }
+
+                uint lastMag = (uint)magnitude[0];
+                while (lastMag > byte.MaxValue)
+                {
+                    output[--bytesIndex] = (byte)lastMag;
+                    lastMag >>= 8;
+                }
+
+                output[--bytesIndex] = (byte)lastMag;
+            }
+            else // sign < 0
+            {
+                bool carry = true;
+
+                while (magIndex > 1)
+                {
+                    uint mag = ~((uint)magnitude[--magIndex]);
+
+                    if (carry)
+                    {
+                        carry = (++mag == uint.MinValue);
+                    }
+
+                    output[--bytesIndex] = (byte) mag;
+                    output[--bytesIndex] = (byte)(mag >> 8);
+                    output[--bytesIndex] = (byte)(mag >> 16);
+                    output[--bytesIndex] = (byte)(mag >> 24);
+                }
+
+                uint lastMag = (uint)magnitude[0];
+
+                if (carry)
+                {
+                    // Never wraps because magnitude[0] != 0
+                    --lastMag;
+                }
+
+                while (lastMag > byte.MaxValue)
+                {
+                    output[--bytesIndex] = (byte)~lastMag;
+                    lastMag >>= 8;
+                }
+
+                output[--bytesIndex] = (byte)~lastMag;
+
+                if (bytesIndex > 0)
+                {
+                    output[--bytesIndex] = byte.MaxValue;
+                }
+            }
+        }
+#endif
+
         public override string ToString()
         {
             return ToString(10);
diff --git a/crypto/src/math/ec/ECAlgorithms.cs b/crypto/src/math/ec/ECAlgorithms.cs
index 64e68fccc..3059ca3b3 100644
--- a/crypto/src/math/ec/ECAlgorithms.cs
+++ b/crypto/src/math/ec/ECAlgorithms.cs
@@ -213,9 +213,18 @@ namespace Org.BouncyCastle.Math.EC
         {
             ECCurve cp = p.Curve;
             if (!c.Equals(cp))
-                throw new ArgumentException("Point must be on the same curve", "p");
-
+                throw new ArgumentException("Point must be on the same curve", nameof(p));
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int encodedLength = p.GetEncodedLength(false);
+            Span<byte> encoding = encodedLength <= 512
+                ? stackalloc byte[encodedLength]
+                : new byte[encodedLength];
+            p.EncodeTo(false, encoding);
+            return c.DecodePoint(encoding);
+#else
             return c.DecodePoint(p.GetEncoded(false));
+#endif
         }
 
         internal static ECPoint ImplCheckResult(ECPoint p)
diff --git a/crypto/src/math/ec/ECCurve.cs b/crypto/src/math/ec/ECCurve.cs
index 38e05991e..b37d62721 100644
--- a/crypto/src/math/ec/ECCurve.cs
+++ b/crypto/src/math/ec/ECCurve.cs
@@ -436,67 +436,143 @@ namespace Org.BouncyCastle.Math.EC
          */
         public virtual ECPoint DecodePoint(byte[] encoded)
         {
-            ECPoint p = null;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DecodePoint(encoded.AsSpan());
+#else
+            ECPoint p;
             int expectedLength = (FieldSize + 7) / 8;
 
             byte type = encoded[0];
             switch (type)
             {
-                case 0x00: // infinity
-                {
-                    if (encoded.Length != 1)
-                        throw new ArgumentException("Incorrect length for infinity encoding", "encoded");
+            case 0x00: // infinity
+            {
+                if (encoded.Length != 1)
+                    throw new ArgumentException("Incorrect length for infinity encoding", "encoded");
 
-                    p = Infinity;
-                    break;
-                }
+                p = Infinity;
+                break;
+            }
 
-                case 0x02: // compressed
-                case 0x03: // compressed
-                {
-                    if (encoded.Length != (expectedLength + 1))
-                        throw new ArgumentException("Incorrect length for compressed encoding", "encoded");
+            case 0x02: // compressed
+            case 0x03: // compressed
+            {
+                if (encoded.Length != (expectedLength + 1))
+                    throw new ArgumentException("Incorrect length for compressed encoding", "encoded");
 
-                    int yTilde = type & 1;
-                    BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
+                int yTilde = type & 1;
+                BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
 
-                    p = DecompressPoint(yTilde, X);
-                    if (!p.ImplIsValid(true, true))
-                        throw new ArgumentException("Invalid point");
+                p = DecompressPoint(yTilde, X);
+                if (!p.ImplIsValid(true, true))
+                    throw new ArgumentException("Invalid point");
 
-                    break;
-                }
+                break;
+            }
 
-                case 0x04: // uncompressed
-                {
-                    if (encoded.Length != (2 * expectedLength + 1))
-                        throw new ArgumentException("Incorrect length for uncompressed encoding", "encoded");
+            case 0x04: // uncompressed
+            {
+                if (encoded.Length != (2 * expectedLength + 1))
+                    throw new ArgumentException("Incorrect length for uncompressed encoding", "encoded");
 
-                    BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
-                    BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength);
+                BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
+                BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength);
 
-                    p = ValidatePoint(X, Y);
-                    break;
-                }
+                p = ValidatePoint(X, Y);
+                break;
+            }
 
-                case 0x06: // hybrid
-                case 0x07: // hybrid
-                {
-                    if (encoded.Length != (2 * expectedLength + 1))
-                        throw new ArgumentException("Incorrect length for hybrid encoding", "encoded");
+            case 0x06: // hybrid
+            case 0x07: // hybrid
+            {
+                if (encoded.Length != (2 * expectedLength + 1))
+                    throw new ArgumentException("Incorrect length for hybrid encoding", "encoded");
 
-                    BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
-                    BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength);
+                BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
+                BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength);
 
-                    if (Y.TestBit(0) != (type == 0x07))
-                        throw new ArgumentException("Inconsistent Y coordinate in hybrid encoding", "encoded");
+                if (Y.TestBit(0) != (type == 0x07))
+                    throw new ArgumentException("Inconsistent Y coordinate in hybrid encoding", "encoded");
 
-                    p = ValidatePoint(X, Y);
-                    break;
-                }
+                p = ValidatePoint(X, Y);
+                break;
+            }
 
-                default:
-                    throw new FormatException("Invalid point encoding " + type);
+            default:
+                throw new FormatException("Invalid point encoding " + type);
+            }
+
+            if (type != 0x00 && p.IsInfinity)
+                throw new ArgumentException("Invalid infinity encoding", "encoded");
+
+            return p;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual ECPoint DecodePoint(ReadOnlySpan<byte> encoded)
+        {
+            ECPoint p;
+            int expectedLength = (FieldSize + 7) / 8;
+
+            byte type = encoded[0];
+            switch (type)
+            {
+            case 0x00: // infinity
+            {
+                if (encoded.Length != 1)
+                    throw new ArgumentException("Incorrect length for infinity encoding", "encoded");
+
+                p = Infinity;
+                break;
+            }
+
+            case 0x02: // compressed
+            case 0x03: // compressed
+            {
+                if (encoded.Length != (expectedLength + 1))
+                    throw new ArgumentException("Incorrect length for compressed encoding", "encoded");
+
+                int yTilde = type & 1;
+                BigInteger X = new BigInteger(1, encoded[1..]);
+
+                p = DecompressPoint(yTilde, X);
+                if (!p.ImplIsValid(true, true))
+                    throw new ArgumentException("Invalid point");
+
+                break;
+            }
+
+            case 0x04: // uncompressed
+            {
+                if (encoded.Length != (2 * expectedLength + 1))
+                    throw new ArgumentException("Incorrect length for uncompressed encoding", "encoded");
+
+                BigInteger X = new BigInteger(1, encoded[1..(1 + expectedLength)]);
+                BigInteger Y = new BigInteger(1, encoded[(1 + expectedLength)..]);
+
+                p = ValidatePoint(X, Y);
+                break;
+            }
+
+            case 0x06: // hybrid
+            case 0x07: // hybrid
+            {
+                if (encoded.Length != (2 * expectedLength + 1))
+                    throw new ArgumentException("Incorrect length for hybrid encoding", "encoded");
+
+                BigInteger X = new BigInteger(1, encoded[1..(1 + expectedLength)]);
+                BigInteger Y = new BigInteger(1, encoded[(1 + expectedLength)..]);
+
+                if (Y.TestBit(0) != (type == 0x07))
+                    throw new ArgumentException("Inconsistent Y coordinate in hybrid encoding", "encoded");
+
+                p = ValidatePoint(X, Y);
+                break;
+            }
+
+            default:
+                throw new FormatException("Invalid point encoding " + type);
             }
 
             if (type != 0x00 && p.IsInfinity)
@@ -504,6 +580,7 @@ namespace Org.BouncyCastle.Math.EC
 
             return p;
         }
+#endif
 
         private class DefaultLookupTable
             : AbstractECLookupTable
@@ -660,7 +737,6 @@ namespace Org.BouncyCastle.Math.EC
         private const int FP_DEFAULT_COORDS = COORD_JACOBIAN_MODIFIED;
 
         private static readonly HashSet<BigInteger> KnownQs = new HashSet<BigInteger>();
-        private static readonly SecureRandom random = new SecureRandom();
 
         protected readonly BigInteger m_q, m_r;
         protected readonly FpPoint m_infinity;
@@ -694,7 +770,8 @@ namespace Org.BouncyCastle.Math.EC
                         throw new ArgumentException("Fp q value out of range");
 
                     if (Primes.HasAnySmallFactors(q) ||
-                        !Primes.IsMRProbablePrime(q, random, GetNumberOfIterations(qBitLength, certainty)))
+                        !Primes.IsMRProbablePrime(q, SecureRandom.ArbitraryRandom,
+                            GetNumberOfIterations(qBitLength, certainty)))
                     {
                         throw new ArgumentException("Fp q value not prime");
                     }
diff --git a/crypto/src/math/ec/ECFieldElement.cs b/crypto/src/math/ec/ECFieldElement.cs
index a96556482..3afc843cd 100644
--- a/crypto/src/math/ec/ECFieldElement.cs
+++ b/crypto/src/math/ec/ECFieldElement.cs
@@ -96,8 +96,25 @@ namespace Org.BouncyCastle.Math.EC
 
         public virtual byte[] GetEncoded()
         {
-            return BigIntegers.AsUnsignedByteArray((FieldSize + 7) / 8, ToBigInteger());
+            return BigIntegers.AsUnsignedByteArray(GetEncodedLength(), ToBigInteger());
         }
+
+        public virtual int GetEncodedLength()
+        {
+            return (FieldSize + 7) / 8;
+        }
+
+        public virtual void EncodeTo(byte[] buf, int off)
+        {
+            BigIntegers.AsUnsignedByteArray(ToBigInteger(), buf, off, GetEncodedLength());
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void EncodeTo(Span<byte> buf)
+        {
+            BigIntegers.AsUnsignedByteArray(ToBigInteger(), buf[..GetEncodedLength()]);
+        }
+#endif
     }
 
     public abstract class AbstractFpFieldElement
@@ -768,9 +785,9 @@ namespace Org.BouncyCastle.Math.EC
             LongArray ab = ax.Multiply(bx, m, ks);
             LongArray xy = xx.Multiply(yx, m, ks);
 
-            if (ab == ax || ab == bx)
+            if (LongArray.AreAliased(ref ab, ref ax) || LongArray.AreAliased(ref ab, ref bx))
             {
-                ab = (LongArray)ab.Copy();
+                ab = ab.Copy();
             }
 
             ab.AddShiftedByWords(xy, 0);
@@ -810,9 +827,9 @@ namespace Org.BouncyCastle.Math.EC
             LongArray aa = ax.Square(m, ks);
             LongArray xy = xx.Multiply(yx, m, ks);
 
-            if (aa == ax)
+            if (LongArray.AreAliased(ref aa, ref ax))
             {
-                aa = (LongArray)aa.Copy();
+                aa = aa.Copy();
             }
 
             aa.AddShiftedByWords(xy, 0);
diff --git a/crypto/src/math/ec/ECPoint.cs b/crypto/src/math/ec/ECPoint.cs
index dcda5abfc..ee7cf9a92 100644
--- a/crypto/src/math/ec/ECPoint.cs
+++ b/crypto/src/math/ec/ECPoint.cs
@@ -12,8 +12,6 @@ namespace Org.BouncyCastle.Math.EC
      */
     public abstract class ECPoint
     {
-        private static readonly SecureRandom Random = new SecureRandom();
-
         protected static ECFieldElement[] EMPTY_ZS = new ECFieldElement[0];
 
         protected static ECFieldElement[] GetInitialZCoords(ECCurve curve)
@@ -246,10 +244,7 @@ namespace Org.BouncyCastle.Math.EC
                      * 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.
                      */
-                    // TODO Add CryptoServicesRegistrar class and use here
-                    //SecureRandom r = CryptoServicesRegistrar.GetSecureRandom();
-                    SecureRandom r = Random;
-                    ECFieldElement b = m_curve.RandomFieldElementMult(r);
+                    ECFieldElement b = m_curve.RandomFieldElementMult(SecureRandom.ArbitraryRandom);
                     ECFieldElement zInv = z.Multiply(b).Invert().Multiply(b);
                     return Normalize(zInv);
                 }
@@ -437,6 +432,14 @@ namespace Org.BouncyCastle.Math.EC
 
         public abstract byte[] GetEncoded(bool compressed);
 
+        public abstract int GetEncodedLength(bool compressed);
+
+        public abstract void EncodeTo(bool compressed, byte[] buf, int off);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void EncodeTo(bool compressed, Span<byte> buf);
+#endif
+
         protected internal abstract bool CompressionYTilde { get; }
 
         public abstract ECPoint Add(ECPoint b);
@@ -560,6 +563,69 @@ namespace Org.BouncyCastle.Math.EC
             }
         }
 
+        public override int GetEncodedLength(bool compressed)
+        {
+            if (IsInfinity)
+                return 1;
+
+            if (compressed)
+                return 1 + XCoord.GetEncodedLength();
+
+            return 1 + XCoord.GetEncodedLength() + YCoord.GetEncodedLength();
+        }
+
+        public override void EncodeTo(bool compressed, byte[] buf, int off)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            EncodeTo(compressed, buf.AsSpan(off));
+#else
+            if (IsInfinity)
+            {
+                buf[off] = 0x00;
+                return;
+            }
+
+            ECPoint normed = Normalize();
+            ECFieldElement X = normed.XCoord, Y = normed.YCoord;
+
+            if (compressed)
+            {
+                buf[off] = (byte)(normed.CompressionYTilde ? 0x03 : 0x02);
+                X.EncodeTo(buf, off + 1);
+                return;
+            }
+
+            buf[off] = 0x04;
+            X.EncodeTo(buf, off + 1);
+            Y.EncodeTo(buf, off + 1 + X.GetEncodedLength());
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void EncodeTo(bool compressed, Span<byte> buf)
+        {
+            if (IsInfinity)
+            {
+                buf[0] = 0x00;
+                return;
+            }
+
+            ECPoint normed = Normalize();
+            ECFieldElement X = normed.XCoord, Y = normed.YCoord;
+
+            if (compressed)
+            {
+                buf[0] = (byte)(normed.CompressionYTilde ? 0x03 : 0x02);
+                X.EncodeTo(buf[1..]);
+                return;
+            }
+
+            buf[0] = 0x04;
+            X.EncodeTo(buf[1..]);
+            Y.EncodeTo(buf[(1 + X.GetEncodedLength())..]);
+        }
+#endif
+
         /**
          * Multiplies this <code>ECPoint</code> by the given number.
          * @param k The multiplicator.
diff --git a/crypto/src/math/ec/LongArray.cs b/crypto/src/math/ec/LongArray.cs
index 0cf32fd15..aa36de215 100644
--- a/crypto/src/math/ec/LongArray.cs
+++ b/crypto/src/math/ec/LongArray.cs
@@ -1,27 +1,32 @@
 using System;
 using System.Text;
-
+using Org.BouncyCastle.Math.Raw;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Math.EC
 {
-    internal sealed class LongArray
+    internal struct LongArray
     {
+        internal static bool AreAliased(ref LongArray a, ref LongArray b)
+        {
+            return a.m_data == b.m_data;
+        }
+
         // TODO make m fixed for the LongArray, and hence compute T once and for all
 
         private ulong[] m_data;
 
-        public LongArray(int intLen)
+        internal LongArray(int intLen)
         {
             m_data = new ulong[intLen];
         }
 
-        public LongArray(ulong[] data)
+        internal LongArray(ulong[] data)
         {
             m_data = data;
         }
 
-        public LongArray(ulong[] data, int off, int len)
+        internal LongArray(ulong[] data, int off, int len)
         {
             if (off == 0 && len == data.Length)
             {
@@ -34,16 +39,14 @@ namespace Org.BouncyCastle.Math.EC
             }
         }
 
-        public LongArray(BigInteger bigInt)
+        internal LongArray(BigInteger bigInt)
         {
             if (bigInt == null || bigInt.SignValue < 0)
-            {
-                throw new ArgumentException("invalid F2m field value", "bigInt");
-            }
+                throw new ArgumentException("invalid F2m field value", nameof(bigInt));
 
             if (bigInt.SignValue == 0)
             {
-                m_data = new ulong[]{ 0UL };
+                m_data = new ulong[1]{ 0UL };
                 return;
             }
 
@@ -93,51 +96,44 @@ namespace Org.BouncyCastle.Math.EC
             Array.Copy(m_data, 0, z, zOff, m_data.Length);
         }
 
-        public bool IsOne()
+        internal bool IsOne()
         {
             ulong[] a = m_data;
             int aLen = a.Length;
             if (aLen < 1 || a[0] != 1UL)
-            {
                 return false;
-            }
+
             for (int i = 1; i < aLen; ++i)
             {
                 if (a[i] != 0UL)
-                {
                     return false;
-                }
             }
             return true;
         }
 
-        public bool IsZero()
+        internal bool IsZero()
         {
             ulong[] a = m_data;
             for (int i = 0; i < a.Length; ++i)
             {
                 if (a[i] != 0UL)
-                {
                     return false;
-                }
             }
             return true;
         }
 
-        public int GetUsedLength()
+        internal int GetUsedLength()
         {
             return GetUsedLengthFrom(m_data.Length);
         }
 
-        public int GetUsedLengthFrom(int from)
+        internal int GetUsedLengthFrom(int from)
         {
             ulong[] a = m_data;
             from = System.Math.Min(from, a.Length);
 
             if (from < 1)
-            {
                 return 0;
-            }
 
             // Check if first element will act as sentinel
             if (a[0] != 0UL)
@@ -160,16 +156,15 @@ namespace Org.BouncyCastle.Math.EC
             return 0;
         }
 
-        public int Degree()
+        internal int Degree()
         {
             int i = m_data.Length;
             ulong w;
             do
             {
                 if (i == 0)
-                {
                     return 0;
-                }
+
                 w = m_data[--i];
             }
             while (w == 0UL);
@@ -184,9 +179,8 @@ namespace Org.BouncyCastle.Math.EC
             do
             {
                 if (i == 0)
-                {
                     return 0;
-                }
+
                 w = m_data[--i];
             }
             while (w == 0);
@@ -206,13 +200,11 @@ namespace Org.BouncyCastle.Math.EC
             return newInts;
         }
 
-        public BigInteger ToBigInteger()
+        internal BigInteger ToBigInteger()
         {
             int usedLen = GetUsedLength();
             if (usedLen == 0)
-            {
                 return BigInteger.Zero;
-            }
 
             ulong highestInt = m_data[usedLen - 1];
             byte[] temp = new byte[8];
@@ -273,12 +265,10 @@ namespace Org.BouncyCastle.Math.EC
             return prev;
         }
 
-        public LongArray AddOne()
+        internal LongArray AddOne()
         {
             if (m_data.Length == 0)
-            {
-                return new LongArray(new ulong[]{ 1UL });
-            }
+                return new LongArray(new ulong[1]{ 1UL });
 
             int resultLen = System.Math.Max(1, GetUsedLength());
             ulong[] data = ResizedData(resultLen);
@@ -333,13 +323,11 @@ namespace Org.BouncyCastle.Math.EC
             return prev;
         }
 
-        public void AddShiftedByWords(LongArray other, int words)
+        internal void AddShiftedByWords(LongArray other, int words)
         {
             int otherUsedLen = other.GetUsedLength();
             if (otherUsedLen == 0)
-            {
                 return;
-            }
 
             int minLen = otherUsedLen + words;
             if (minLen > m_data.Length)
@@ -352,18 +340,12 @@ namespace Org.BouncyCastle.Math.EC
 
         private static void Add(ulong[] x, int xOff, ulong[] y, int yOff, int count)
         {
-            for (int i = 0; i < count; ++i)
-            {
-                x[xOff + i] ^= y[yOff + i];
-            }
+            Nat.XorTo64(count, y, yOff, x, xOff);
         }
 
         private static void Add(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff, int count)
         {
-            for (int i = 0; i < count; ++i)
-            {
-                z[zOff + i] = x[xOff + i] ^ y[yOff + i];
-            }
+            Nat.Xor64(count, x, xOff, y, yOff, z, zOff);
         }
 
         private static void AddBoth(ulong[] x, int xOff, ulong[] y1, int y1Off, ulong[] y2, int y2Off, int count)
@@ -393,7 +375,7 @@ namespace Org.BouncyCastle.Math.EC
             }
         }
 
-        public bool TestBitZero()
+        internal bool TestBitZero()
         {
             return m_data.Length > 0 && (m_data[0] & 1UL) != 0;
         }
@@ -439,21 +421,18 @@ namespace Org.BouncyCastle.Math.EC
             }
         }
 
-        public LongArray ModMultiplyLD(LongArray other, int m, int[] ks)
+        internal LongArray ModMultiplyLD(LongArray other, int m, int[] ks)
         {
             /*
              * Find out the degree of each argument and handle the zero cases
              */
             int aDeg = Degree();
             if (aDeg == 0)
-            {
                 return this;
-            }
+
             int bDeg = other.Degree();
             if (bDeg == 0)
-            {
                 return other;
-            }
 
             /*
              * Swap if necessary so that A is the smaller argument
@@ -476,9 +455,7 @@ namespace Org.BouncyCastle.Math.EC
             {
                 ulong a0 = A.m_data[0];
                 if (a0 == 1UL)
-                {
                     return B;
-                }
 
                 /*
                  * Fast path for small A, with performance dependent only on the number of set bits
@@ -571,21 +548,18 @@ namespace Org.BouncyCastle.Math.EC
             return ReduceResult(c, 0, cLen, m, ks);
         }
 
-        public LongArray ModMultiply(LongArray other, int m, int[] ks)
+        internal LongArray ModMultiply(LongArray other, int m, int[] ks)
         {
             /*
              * Find out the degree of each argument and handle the zero cases
              */
             int aDeg = Degree();
             if (aDeg == 0)
-            {
                 return this;
-            }
+
             int bDeg = other.Degree();
             if (bDeg == 0)
-            {
                 return other;
-            }
 
             /*
              * Swap if necessary so that A is the smaller argument
@@ -608,9 +582,7 @@ namespace Org.BouncyCastle.Math.EC
             {
                 ulong a0 = A.m_data[0];
                 if (a0 == 1UL)
-                {
                     return B;
-                }
 
                 /*
                  * Fast path for small A, with performance dependent only on the number of set bits
@@ -681,9 +653,8 @@ namespace Org.BouncyCastle.Math.EC
                     uint v = (uint)aVal & MASK; aVal >>= 4;
                     AddBoth(c, cOff, T0, ti[u], T1, ti[v], bMax);
                     if (aVal == 0UL)
-                    {
                         break;
-                    }
+
                     cOff += cLen;
                 }
             }
@@ -702,28 +673,25 @@ namespace Org.BouncyCastle.Math.EC
             return ReduceResult(c, 0, cLen, m, ks);
         }
 
-        //public LongArray ModReduce(int m, int[] ks)
+        //internal LongArray ModReduce(int m, int[] ks)
         //{
         //    ulong[] buf = Arrays.Clone(m_data);
         //    int rLen = ReduceInPlace(buf, 0, buf.Length, m, ks);
         //    return new LongArray(buf, 0, rLen);
         //}
 
-        public LongArray Multiply(LongArray other, int m, int[] ks)
+        internal LongArray Multiply(LongArray other, int m, int[] ks)
         {
             /*
              * Find out the degree of each argument and handle the zero cases
              */
             int aDeg = Degree();
             if (aDeg == 0)
-            {
                 return this;
-            }
+
             int bDeg = other.Degree();
             if (bDeg == 0)
-            {
                 return other;
-            }
 
             /*
              * Swap if necessary so that A is the smaller argument
@@ -746,9 +714,7 @@ namespace Org.BouncyCastle.Math.EC
             {
                 ulong a0 = A.m_data[0];
                 if (a0 == 1UL)
-                {
                     return B;
-                }
 
                 /*
                  * Fast path for small A, with performance dependent only on the number of set bits
@@ -820,9 +786,8 @@ namespace Org.BouncyCastle.Math.EC
                     uint v = (uint)aVal & MASK; aVal >>= 4;
                     AddBoth(c, cOff, T0, ti[u], T1, ti[v], bMax);
                     if (aVal == 0UL)
-                    {
                         break;
-                    }
+
                     cOff += cLen;
                 }
             }
@@ -842,7 +807,7 @@ namespace Org.BouncyCastle.Math.EC
             return new LongArray(c, 0, cLen);
         }
 
-        public void Reduce(int m, int[] ks)
+        internal void Reduce(int m, int[] ks)
         {
             ulong[] buf = m_data;
             int rLen = ReduceInPlace(buf, 0, buf.Length, m, ks);
@@ -863,9 +828,7 @@ namespace Org.BouncyCastle.Math.EC
         {
             int mLen = (m + 63) >> 6;
             if (len < mLen)
-            {
                 return len;
-            }
 
             int numBits = System.Math.Min(len << 6, (m << 1) - 1); // TODO use actual degree?
             int excessBits = (len << 6) - numBits;
@@ -994,19 +957,19 @@ namespace Org.BouncyCastle.Math.EC
             }
         }
 
-        public LongArray ModSquare(int m, int[] ks)
+        internal LongArray ModSquare(int m, int[] ks)
         {
             int len = GetUsedLength();
             if (len == 0)
                 return this;
 
             ulong[] r = new ulong[len << 1];
-            Raw.Interleave.Expand64To128(m_data, 0, len, r, 0);
+            Interleave.Expand64To128(m_data, 0, len, r, 0);
 
             return new LongArray(r, 0, ReduceInPlace(r, 0, r.Length, m, ks));
         }
 
-        public LongArray ModSquareN(int n, int m, int[] ks)
+        internal LongArray ModSquareN(int n, int m, int[] ks)
         {
             int len = GetUsedLength();
             if (len == 0)
@@ -1018,21 +981,21 @@ namespace Org.BouncyCastle.Math.EC
 
             while (--n >= 0)
             {
-                Raw.Interleave.Expand64To128(r, 0, len);
+                Interleave.Expand64To128(r, 0, len, r, 0);
                 len = ReduceInPlace(r, 0, r.Length, m, ks);
             }
-    
+
             return new LongArray(r, 0, len);
         }
 
-        public LongArray Square(int m, int[] ks)
+        internal LongArray Square(int m, int[] ks)
         {
             int len = GetUsedLength();
             if (len == 0)
                 return this;
 
             ulong[] r = new ulong[len << 1];
-            Raw.Interleave.Expand64To128(m_data, 0, len, r, 0);
+            Interleave.Expand64To128(m_data, 0, len, r, 0);
 
             return new LongArray(r, 0, r.Length);
         }
@@ -1147,7 +1110,7 @@ namespace Org.BouncyCastle.Math.EC
     //        return t4.ModMultiply(t1, m, ks);
     //    }
 
-        public LongArray ModInverse(int m, int[] ks)
+        internal LongArray ModInverse(int m, int[] ks)
         {
             /*
              * Fermat's Little Theorem
@@ -1188,16 +1151,13 @@ namespace Org.BouncyCastle.Math.EC
              */
             int uzDegree = Degree();
             if (uzDegree == 0)
-            {
                 throw new InvalidOperationException();
-            }
+
             if (uzDegree == 1)
-            {
                 return this;
-            }
 
             // u(z) := a(z)
-            LongArray uz = (LongArray)Copy();
+            LongArray uz = Copy();
 
             int t = (m + 63) >> 6;
 
@@ -1237,9 +1197,7 @@ namespace Org.BouncyCastle.Math.EC
 
                 int duv2 = uv[b].DegreeFrom(duv1);
                 if (duv2 == 0)
-                {
                     return gg[1 - b];
-                }
 
                 {
                     int dgg2 = ggDeg[1 - b];
@@ -1263,26 +1221,25 @@ namespace Org.BouncyCastle.Math.EC
 
         public override bool Equals(object obj)
         {
-            return Equals(obj as LongArray);
+            if (obj is LongArray longArray)
+                return Equals(ref longArray);
+
+            return false;
         }
 
-        public bool Equals(LongArray other)
+        internal bool Equals(ref LongArray other)
         {
-            if (this == other)
+            if (AreAliased(ref this, ref other))
                 return true;
-            if (null == other)
-                return false;
+
             int usedLen = GetUsedLength();
             if (other.GetUsedLength() != usedLen)
-            {
                 return false;
-            }
+
             for (int i = 0; i < usedLen; i++)
             {
                 if (m_data[i] != other.m_data[i])
-                {
                     return false;
-                }
             }
             return true;
         }
@@ -1311,9 +1268,7 @@ namespace Org.BouncyCastle.Math.EC
         {
             int i = GetUsedLength();
             if (i == 0)
-            {
                 return "0";
-            }
 
             StringBuilder sb = new StringBuilder(i * 64);
             sb.Append(Convert.ToString((long)m_data[--i], 2));
diff --git a/crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs b/crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs
index 25cb24932..e3de6c594 100644
--- a/crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs
+++ b/crypto/src/math/ec/custom/gm/SM2P256V1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.GM
 
         public override ECFieldElement Invert()
         {
-            //return new SM2P256V1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat256.Create();
             SM2P256V1Field.Inv(x, z);
             return new SM2P256V1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs
index e9235c2f3..1db449442 100644
--- a/crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP128R1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-    //        return new SecP128R1FieldElement(toBigInteger().modInverse(Q));
             uint[] z = Nat128.Create();
             SecP128R1Field.Inv(x, z);
             return new SecP128R1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs
index 4876fafa9..a4307cbaf 100644
--- a/crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP160R1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-    //        return new SecP160R1FieldElement(ToBigInteger().modInverse(Q));
             uint[] z = Nat160.Create();
             SecP160R1Field.Inv(x, z);
             return new SecP160R1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs
index 795fe3b2e..9237c0778 100644
--- a/crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP160R2FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-    //        return new SecP160R2FieldElement(ToBigInteger().modInverse(Q));
             uint[] z = Nat160.Create();
             SecP160R2Field.Inv(x, z);
             return new SecP160R2FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs
index c933ffc8d..a37bc1539 100644
--- a/crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP192K1FieldElement.cs
@@ -116,7 +116,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP192K1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat192.Create();
             SecP192K1Field.Inv(x, z);
             return new SecP192K1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs
index e61c2251b..a8c7ae83c 100644
--- a/crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP192R1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP192R1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat192.Create();
             SecP192R1Field.Inv(x, z);
             return new SecP192R1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs
index eb740419f..24de7112a 100644
--- a/crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP224K1FieldElement.cs
@@ -120,7 +120,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP224K1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat224.Create();
             SecP224K1Field.Inv(x, z);
             return new SecP224K1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs
index bb60edaf6..e53f44164 100644
--- a/crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP224R1FieldElement.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Math.Raw;
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 
@@ -115,7 +116,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP224R1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat224.Create();
             SecP224R1Field.Inv(x, z);
             return new SecP224R1FieldElement(z);
@@ -134,7 +134,7 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
             uint[] nc = Nat224.Create();
             SecP224R1Field.Negate(c, nc);
 
-            uint[] r = Mod.Random(SecP224R1Field.P);
+            uint[] r = Mod.Random(SecureRandom.ArbitraryRandom, SecP224R1Field.P);
             uint[] t = Nat224.Create();
 
             if (!IsSquare(c))
diff --git a/crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs
index 2bb83d5e9..055df0d06 100644
--- a/crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP256K1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP256K1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat256.Create();
             SecP256K1Field.Inv(x, z);
             return new SecP256K1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs
index 928461ec6..e09cd8c8d 100644
--- a/crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP256R1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP256R1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat256.Create();
             SecP256R1Field.Inv(x, z);
             return new SecP256R1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs
index d190c4ae9..33f251b76 100644
--- a/crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP384R1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP384R1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat.Create(12);
             SecP384R1Field.Inv(x, z);
             return new SecP384R1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs b/crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs
index 409352586..1169d41a9 100644
--- a/crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs
+++ b/crypto/src/math/ec/custom/sec/SecP521R1FieldElement.cs
@@ -115,7 +115,6 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public override ECFieldElement Invert()
         {
-            //return new SecP521R1FieldElement(ToBigInteger().ModInverse(Q));
             uint[] z = Nat.Create(17);
             SecP521R1Field.Inv(x, z);
             return new SecP521R1FieldElement(z);
diff --git a/crypto/src/math/ec/custom/sec/SecT113Field.cs b/crypto/src/math/ec/custom/sec/SecT113Field.cs
index c41d9f7d7..65249562a 100644
--- a/crypto/src/math/ec/custom/sec/SecT113Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT113Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -166,6 +170,25 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11);
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1);
+                return;
+            }
+#endif
+
             /*
              * "Three-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
              */
diff --git a/crypto/src/math/ec/custom/sec/SecT131Field.cs b/crypto/src/math/ec/custom/sec/SecT131Field.cs
index 4ff5999a4..6088b264c 100644
--- a/crypto/src/math/ec/custom/sec/SecT131Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT131Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -194,6 +198,33 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var X2_ = Vector128.CreateScalar(x[2]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+                var Y2_ = Vector128.CreateScalar(y[2]);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y2_, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11),
+                                   Pclmulqdq.CarrylessMultiply(X2_, Y01, 0x00)));
+                var Z34 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y2_, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X2_, Y01, 0x10));
+                var Z4_ =          Pclmulqdq.CarrylessMultiply(X2_, Y2_, 0x00);
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1) ^ Z34.GetElement(0);
+                zz[4] = Z4_.GetElement(0) ^ Z34.GetElement(1);
+                return;
+            }
+#endif
+
             /*
              * "Five-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
              */
diff --git a/crypto/src/math/ec/custom/sec/SecT163Field.cs b/crypto/src/math/ec/custom/sec/SecT163Field.cs
index 44105039d..0c616600a 100644
--- a/crypto/src/math/ec/custom/sec/SecT163Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT163Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -205,6 +209,34 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var X2_ = Vector128.CreateScalar(x[2]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+                var Y2_ = Vector128.CreateScalar(y[2]);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y2_, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11),
+                                   Pclmulqdq.CarrylessMultiply(X2_, Y01, 0x00)));
+                var Z34 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y2_, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X2_, Y01, 0x10));
+                var Z45 =          Pclmulqdq.CarrylessMultiply(X2_, Y2_, 0x00);
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1) ^ Z34.GetElement(0);
+                zz[4] = Z45.GetElement(0) ^ Z34.GetElement(1);
+                zz[5] = Z45.GetElement(1);
+                return;
+            }
+#endif
+
             /*
              * "Five-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
              */
diff --git a/crypto/src/math/ec/custom/sec/SecT193Field.cs b/crypto/src/math/ec/custom/sec/SecT193Field.cs
index 59da8b000..4aa3ad5c2 100644
--- a/crypto/src/math/ec/custom/sec/SecT193Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT193Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -226,6 +230,38 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var X2_ = Vector128.CreateScalar(x[2]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+                var Y2_ = Vector128.CreateScalar(y[2]);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y2_, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11),
+                                   Pclmulqdq.CarrylessMultiply(X2_, Y01, 0x00)));
+                var Z34 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y2_, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X2_, Y01, 0x10));
+                var Z45 =          Pclmulqdq.CarrylessMultiply(X2_, Y2_, 0x00);
+
+                ulong X3M = 0UL - x[3];
+                ulong Y3M = 0UL - y[3];
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1) ^ Z34.GetElement(0) ^ (X3M & y[0]) ^ (x[0] & Y3M);
+                zz[4] = Z45.GetElement(0) ^ Z34.GetElement(1) ^ (X3M & y[1]) ^ (x[1] & Y3M);
+                zz[5] = Z45.GetElement(1)                     ^ (X3M & y[2]) ^ (x[2] & Y3M);
+                zz[6] =                                          X3M & y[3];
+                return;
+            }
+#endif
+
             /*
              * "Two-level seven-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
              */
diff --git a/crypto/src/math/ec/custom/sec/SecT233Field.cs b/crypto/src/math/ec/custom/sec/SecT233Field.cs
index c16a3d612..e4e291154 100644
--- a/crypto/src/math/ec/custom/sec/SecT233Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT233Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -237,6 +241,54 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var X23 = Vector128.Create(x[2], x[3]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+                var Y23 = Vector128.Create(y[2], y[3]);
+                var X03 = Sse2.Xor(X01, X23);
+                var Y03 = Sse2.Xor(Y01, Y23);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11);
+
+                var Z45 =          Pclmulqdq.CarrylessMultiply(X23, Y23, 0x00);
+                var Z56 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y23, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X23, Y23, 0x10));
+                var Z67 =          Pclmulqdq.CarrylessMultiply(X23, Y23, 0x11);
+
+                var K01 =          Pclmulqdq.CarrylessMultiply(X03, Y03, 0x00);
+                var K12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X03, Y03, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X03, Y03, 0x10));
+                var K23 =          Pclmulqdq.CarrylessMultiply(X03, Y03, 0x11);
+
+                K01 = Sse2.Xor(K01, Z01);
+                K12 = Sse2.Xor(K12, Z12);
+                K23 = Sse2.Xor(K23, Z23);
+
+                K01 = Sse2.Xor(K01, Z45);
+                K12 = Sse2.Xor(K12, Z56);
+                K23 = Sse2.Xor(K23, Z67);
+
+                Z23 = Sse2.Xor(Z23, K01);
+                Z45 = Sse2.Xor(Z45, K23);
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1) ^ K12.GetElement(0);
+                zz[4] = Z45.GetElement(0) ^ K12.GetElement(1);
+                zz[5] = Z45.GetElement(1) ^ Z56.GetElement(0);
+                zz[6] = Z67.GetElement(0) ^ Z56.GetElement(1);
+                zz[7] = Z67.GetElement(1);
+                return;
+            }
+#endif
+
             /*
              * "Two-level seven-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
              */
diff --git a/crypto/src/math/ec/custom/sec/SecT239Field.cs b/crypto/src/math/ec/custom/sec/SecT239Field.cs
index de87b18a2..a3851de16 100644
--- a/crypto/src/math/ec/custom/sec/SecT239Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT239Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -246,6 +250,54 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var X23 = Vector128.Create(x[2], x[3]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+                var Y23 = Vector128.Create(y[2], y[3]);
+                var X03 = Sse2.Xor(X01, X23);
+                var Y03 = Sse2.Xor(Y01, Y23);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11);
+
+                var Z45 =          Pclmulqdq.CarrylessMultiply(X23, Y23, 0x00);
+                var Z56 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y23, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X23, Y23, 0x10));
+                var Z67 =          Pclmulqdq.CarrylessMultiply(X23, Y23, 0x11);
+
+                var K01 =          Pclmulqdq.CarrylessMultiply(X03, Y03, 0x00);
+                var K12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X03, Y03, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X03, Y03, 0x10));
+                var K23 =          Pclmulqdq.CarrylessMultiply(X03, Y03, 0x11);
+
+                K01 = Sse2.Xor(K01, Z01);
+                K12 = Sse2.Xor(K12, Z12);
+                K23 = Sse2.Xor(K23, Z23);
+
+                K01 = Sse2.Xor(K01, Z45);
+                K12 = Sse2.Xor(K12, Z56);
+                K23 = Sse2.Xor(K23, Z67);
+
+                Z23 = Sse2.Xor(Z23, K01);
+                Z45 = Sse2.Xor(Z45, K23);
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1) ^ K12.GetElement(0);
+                zz[4] = Z45.GetElement(0) ^ K12.GetElement(1);
+                zz[5] = Z45.GetElement(1) ^ Z56.GetElement(0);
+                zz[6] = Z67.GetElement(0) ^ Z56.GetElement(1);
+                zz[7] = Z67.GetElement(1);
+                return;
+            }
+#endif
+
             /*
              * "Two-level seven-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
              */
diff --git a/crypto/src/math/ec/custom/sec/SecT283Field.cs b/crypto/src/math/ec/custom/sec/SecT283Field.cs
index ee5ad89c5..334986452 100644
--- a/crypto/src/math/ec/custom/sec/SecT283Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT283Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -245,6 +249,56 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiply(ulong[] x, ulong[] y, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X01 = Vector128.Create(x[0], x[1]);
+                var X23 = Vector128.Create(x[2], x[3]);
+                var X4_ = Vector128.CreateScalar(x[4]);
+                var Y01 = Vector128.Create(y[0], y[1]);
+                var Y23 = Vector128.Create(y[2], y[3]);
+                var Y4_ = Vector128.CreateScalar(y[4]);
+
+                var Z01 =          Pclmulqdq.CarrylessMultiply(X01, Y01, 0x00);
+                var Z12 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X01, Y01, 0x10));
+                var Z23 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y23, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y01, 0x11),
+                                   Pclmulqdq.CarrylessMultiply(X23, Y01, 0x00)));
+                var Z34 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y23, 0x01),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y23, 0x10),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y01, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X23, Y01, 0x10))));
+                var Z45 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y4_, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y23, 0x11),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y23, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y01, 0x11),
+                                   Pclmulqdq.CarrylessMultiply(X4_, Y01, 0x00)))));
+                var Z56 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X01, Y4_, 0x01),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y23, 0x01),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y23, 0x10),
+                                   Pclmulqdq.CarrylessMultiply(X4_, Y01, 0x10))));
+                var Z67 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y4_, 0x00),
+                          Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y23, 0x11),
+                                   Pclmulqdq.CarrylessMultiply(X4_, Y23, 0x00)));
+                var Z78 = Sse2.Xor(Pclmulqdq.CarrylessMultiply(X23, Y4_, 0x01),
+                                   Pclmulqdq.CarrylessMultiply(X4_, Y23, 0x10));
+                var Z89 =          Pclmulqdq.CarrylessMultiply(X4_, Y4_, 0x00);
+
+                zz[0] = Z01.GetElement(0);
+                zz[1] = Z01.GetElement(1) ^ Z12.GetElement(0);
+                zz[2] = Z23.GetElement(0) ^ Z12.GetElement(1);
+                zz[3] = Z23.GetElement(1) ^ Z34.GetElement(0);
+                zz[4] = Z45.GetElement(0) ^ Z34.GetElement(1);
+                zz[5] = Z45.GetElement(1) ^ Z56.GetElement(0);
+                zz[6] = Z67.GetElement(0) ^ Z56.GetElement(1);
+                zz[7] = Z67.GetElement(1) ^ Z78.GetElement(0);
+                zz[8] = Z89.GetElement(0) ^ Z78.GetElement(1);
+                zz[9] = Z89.GetElement(1);
+                return;
+            }
+#endif
+
             /*
              * Formula (17) from "Some New Results on Binary Polynomial Multiplication",
              * Murat Cenk and M. Anwar Hasan.
diff --git a/crypto/src/math/ec/custom/sec/SecT409Field.cs b/crypto/src/math/ec/custom/sec/SecT409Field.cs
index 0fb7377f6..414a094a8 100644
--- a/crypto/src/math/ec/custom/sec/SecT409Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT409Field.cs
@@ -1,5 +1,9 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Math.Raw;
 
@@ -344,6 +348,20 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
             Debug.Assert(x >> 59 == 0);
             Debug.Assert(y >> 59 == 0);
 
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X = Vector128.CreateScalar(x);
+                var Y = Vector128.CreateScalar(y);
+                var Z = Pclmulqdq.CarrylessMultiply(X, Y, 0x00);
+                ulong z0 = Z.GetElement(0);
+                ulong z1 = Z.GetElement(1);
+                z[zOff    ] ^= z0 & M59;
+                z[zOff + 1] ^= (z0 >> 59) ^ (z1 << 5);
+                return;
+            }
+#endif
+
             //u[0] = 0;
             u[1] = y;
             u[2] = u[1] << 1;
diff --git a/crypto/src/math/ec/custom/sec/SecT571Field.cs b/crypto/src/math/ec/custom/sec/SecT571Field.cs
index 91a3fde9d..49eaae2d4 100644
--- a/crypto/src/math/ec/custom/sec/SecT571Field.cs
+++ b/crypto/src/math/ec/custom/sec/SecT571Field.cs
@@ -19,18 +19,12 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public static void Add(ulong[] x, ulong[] y, ulong[] z)
         {
-            for (int i = 0; i < 9; ++i)
-            {
-                z[i] = x[i] ^ y[i]; 
-            }
+            Nat.Xor64(9, x, y, z);
         }
 
         private static void Add(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
         {
-            for (int i = 0; i < 9; ++i)
-            {
-                z[zOff + i] = x[xOff + i] ^ y[yOff + i];
-            }
+            Nat.Xor64(9, x, xOff, y, yOff, z, zOff);
         }
 
         public static void AddBothTo(ulong[] x, ulong[] y, ulong[] z)
@@ -51,10 +45,7 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public static void AddExt(ulong[] xx, ulong[] yy, ulong[] zz)
         {
-            for (int i = 0; i < 18; ++i)
-            {
-                zz[i] = xx[i] ^ yy[i]; 
-            }
+            Nat.Xor64(18, xx, yy, zz);
         }
 
         public static void AddOne(ulong[] x, ulong[] z)
@@ -68,10 +59,7 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         private static void AddTo(ulong[] x, ulong[] z)
         {
-            for (int i = 0; i < 9; ++i)
-            {
-                z[i] ^= x[i];
-            }
+            Nat.XorTo64(9, x, z);
         }
 
         public static ulong[] FromBigInteger(BigInteger x)
@@ -175,6 +163,11 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         public static ulong[] PrecompMultiplicand(ulong[] x)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            ulong[] z = Nat576.Create64();
+            Nat576.Copy64(x, z);
+            return z;
+#else
             /*
              * Precompute table of all 4-bit products of x (first section)
              */
@@ -197,6 +190,7 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
             Nat.ShiftUpBits64(len, t, 0, 4, 0UL, t, len);
 
             return t;
+#endif
         }
 
         public static void Reduce(ulong[] xx, ulong[] z)
@@ -367,6 +361,9 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
 
         protected static void ImplMultiplyPrecomp(ulong[] x, ulong[] precomp, ulong[] zz)
         {
+#if NETCOREAPP3_0_OR_GREATER
+            ImplMultiply(x, precomp, zz);
+#else
             uint MASK = 0xF;
 
             /*
@@ -399,6 +396,7 @@ namespace Org.BouncyCastle.Math.EC.Custom.Sec
                     Nat.ShiftUpBits64(18, zz, 0, 8, 0UL);
                 }
             }
+#endif
         }
 
         protected static void ImplMulwAcc(ulong[] u, ulong x, ulong y, ulong[] z, int zOff)
diff --git a/crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs b/crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs
index 37e5b5c29..6449e1d8b 100644
--- a/crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs
+++ b/crypto/src/math/ec/multiplier/FixedPointCombMultiplier.cs
@@ -28,18 +28,25 @@ namespace Org.BouncyCastle.Math.EC.Multiplier
             int width = info.Width;
 
             int d = (size + width - 1) / width;
+            int fullComb = d * width;
 
             ECPoint R = c.Infinity;
 
-            int fullComb = d * width;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int KLen = Nat.GetLengthForBits(fullComb);
+            Span<uint> K = KLen <= 32
+                ? stackalloc uint[KLen]
+                : new uint[KLen];
+            Nat.FromBigInteger(fullComb, k, K);
+#else
             uint[] K = Nat.FromBigInteger(fullComb, k);
+#endif
 
-            int top = fullComb - 1;
-            for (int i = 0; i < d; ++i)
+            for (int i = 1; i <= d; ++i)
             {
                 uint secretIndex = 0;
 
-                for (int j = top - i; j >= 0; j -= d)
+                for (int j = fullComb - i; j >= 0; j -= d)
                 {
                     uint secretBit = K[j >> 5] >> (j & 0x1F);
                     secretIndex ^= secretBit >> 1;
diff --git a/crypto/src/math/ec/rfc7748/X25519.cs b/crypto/src/math/ec/rfc7748/X25519.cs
index 217ef8785..ffddd4376 100644
--- a/crypto/src/math/ec/rfc7748/X25519.cs
+++ b/crypto/src/math/ec/rfc7748/X25519.cs
@@ -26,6 +26,36 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             return !Arrays.AreAllZeroes(r, rOff, PointSize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool CalculateAgreement(ReadOnlySpan<byte> k, ReadOnlySpan<byte> u, Span<byte> r)
+        {
+            ScalarMult(k, u, r);
+            return !Arrays.AreAllZeroes(r[..PointSize]);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+
+        private static void DecodeScalar(ReadOnlySpan<byte> k, Span<uint> n)
+        {
+            for (int i = 0; i < 8; ++i)
+            {
+                n[i] = Decode32(k[(i * 4)..]);
+            }
+
+            n[0] &= 0xFFFFFFF8U;
+            n[7] &= 0x7FFFFFFFU;
+            n[7] |= 0x40000000U;
+        }
+#else
         private static uint Decode32(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -46,21 +76,46 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             n[7] &= 0x7FFFFFFFU;
             n[7] |= 0x40000000U;
         }
+#endif
 
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+
+            k[0] &= 0xF8;
+            k[ScalarSize - 1] &= 0x7F;
+            k[ScalarSize - 1] |= 0x40;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
 
             k[0] &= 0xF8;
             k[ScalarSize - 1] &= 0x7F;
             k[ScalarSize - 1] |= 0x40;
         }
+#endif
 
         public static void GeneratePublicKey(byte[] k, int kOff, byte[] r, int rOff)
         {
             ScalarMultBase(k, kOff, r, rOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            ScalarMultBase(k, r);
+        }
+#endif
+
         private static void PointDouble(int[] x, int[] z)
         {
             int[] a = F.Create();
@@ -83,6 +138,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
         public static void ScalarMult(byte[] k, int kOff, byte[] u, int uOff, byte[] r, int rOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMult(k.AsSpan(kOff), u.AsSpan(uOff), r.AsSpan(rOff));
+#else
             uint[] n = new uint[8];     DecodeScalar(k, kOff, n);
 
             int[] x1 = F.Create();      F.Decode(u, uOff, x1);
@@ -140,10 +198,77 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
             F.Normalize(x2);
             F.Encode(x2, r, rOff);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ScalarMult(ReadOnlySpan<byte> k, ReadOnlySpan<byte> u, Span<byte> r)
+        {
+            uint[] n = new uint[8];     DecodeScalar(k, n);
+
+            int[] x1 = F.Create();      F.Decode(u, 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 = F.Create();
+            int[] t2 = F.Create();
+
+            Debug.Assert(n[7] >> 30 == 1U);
+
+            int bit = 254, swap = 1;
+            do
+            {
+                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);
+
+                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);
+
+                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 = (int)(n[word] >> shift) & 1;
+                swap ^= kt;
+                F.CSwap(swap, x2, x3);
+                F.CSwap(swap, z2, z3);
+                swap = kt;
+            }
+            while (bit >= 3);
+
+            Debug.Assert(swap == 0);
+
+            for (int i = 0; i < 3; ++i)
+            {
+                PointDouble(x2, z2);
+            }
+
+            F.Inv(z2, z2);
+            F.Mul(x2, z2, x2);
+
+            F.Normalize(x2);
+            F.Encode(x2, r);
+        }
+#endif
+
         public static void ScalarMultBase(byte[] k, int kOff, byte[] r, int rOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBase(k.AsSpan(kOff), r.AsSpan(rOff));
+#else
             int[] y = F.Create();
             int[] z = F.Create();
 
@@ -156,6 +281,25 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
             F.Normalize(y);
             F.Encode(y, r, rOff);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ScalarMultBase(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            int[] y = F.Create();
+            int[] z = F.Create();
+
+            Ed25519.ScalarMultBaseYZ(k, y, z);
+
+            F.Apm(z, y, y, z);
+
+            F.Inv(z, z);
+            F.Mul(y, z, y);
+
+            F.Normalize(y);
+            F.Encode(y, r);
         }
+#endif
     }
 }
diff --git a/crypto/src/math/ec/rfc7748/X25519Field.cs b/crypto/src/math/ec/rfc7748/X25519Field.cs
index e6d5687ec..5c9eadc6b 100644
--- a/crypto/src/math/ec/rfc7748/X25519Field.cs
+++ b/crypto/src/math/ec/rfc7748/X25519Field.cs
@@ -103,6 +103,20 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CMov(int cond, ReadOnlySpan<int> x, Span<int> z)
+        {
+            Debug.Assert(0 == cond || -1 == cond);
+
+            for (int i = 0; i < Size; ++i)
+            {
+                int z_i = z[i], diff = z_i ^ x[i];
+                z_i ^= (diff & cond);
+                z[i] = z_i;
+            }
+        }
+#endif
+
         public static void CNegate(int negate, int[] z)
         {
             Debug.Assert(negate >> 1 == 0);
@@ -122,6 +136,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Copy(ReadOnlySpan<int> x, Span<int> z)
+        {
+            x[..Size].CopyTo(z);
+        }
+#endif
+
         public static int[] Create()
         {
             return new int[Size];
@@ -155,6 +176,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[9] &= M24;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        [CLSCompliant(false)]
+        public static void Decode(ReadOnlySpan<uint> x, Span<int> z)
+        {
+            Decode128(x, z);
+            Decode128(x[4..], z[5..]);
+            z[9] &= M24;
+        }
+#endif
+
         public static void Decode(byte[] x, int xOff, int[] z)
         {
             Decode128(x, xOff, z, 0);
@@ -162,6 +193,15 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[9] &= M24;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Decode(ReadOnlySpan<byte> x, Span<int> z)
+        {
+            Decode128(x, z);
+            Decode128(x[16..], z[5..]);
+            z[9] &= M24;
+        }
+#endif
+
         private static void Decode128(uint[] x, int xOff, int[] z, int zOff)
         {
             uint t0 = x[xOff + 0], t1 = x[xOff + 1], t2 = x[xOff + 2], t3 = x[xOff + 3];
@@ -173,6 +213,19 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[zOff + 4] = (int)(t3 >> 7);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode128(ReadOnlySpan<uint> x, Span<int> z)
+        {
+            uint t0 = x[0], t1 = x[1], t2 = x[2], t3 = x[3];
+
+            z[0] = (int)t0 & M26;
+            z[1] = (int)((t1 <<  6) | (t0 >> 26)) & M26;
+            z[2] = (int)((t2 << 12) | (t1 >> 20)) & M25;
+            z[3] = (int)((t3 << 19) | (t2 >> 13)) & M26;
+            z[4] = (int)(t3 >> 7);
+        }
+#endif
+
         private static void Decode128(byte[] bs, int off, int[] z, int zOff)
         {
             uint t0 = Decode32(bs, off + 0);
@@ -181,12 +234,28 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             uint t3 = Decode32(bs, off + 12);
 
             z[zOff + 0] = (int)t0 & M26;
-            z[zOff + 1] = (int)((t1 << 6) | (t0 >> 26)) & M26;
+            z[zOff + 1] = (int)((t1 <<  6) | (t0 >> 26)) & M26;
             z[zOff + 2] = (int)((t2 << 12) | (t1 >> 20)) & M25;
             z[zOff + 3] = (int)((t3 << 19) | (t2 >> 13)) & M26;
             z[zOff + 4] = (int)(t3 >> 7);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode128(ReadOnlySpan<byte> bs, Span<int> z)
+        {
+            uint t0 = Decode32(bs);
+            uint t1 = Decode32(bs[4..]);
+            uint t2 = Decode32(bs[8..]);
+            uint t3 = Decode32(bs[12..]);
+
+            z[0] = (int)t0 & M26;
+            z[1] = (int)((t1 <<  6) | (t0 >> 26)) & M26;
+            z[2] = (int)((t2 << 12) | (t1 >> 20)) & M25;
+            z[3] = (int)((t3 << 19) | (t2 >> 13)) & M26;
+            z[4] = (int)(t3 >> 7);
+        }
+#endif
+
         private static uint Decode32(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -196,6 +265,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+#endif
+
         [CLSCompliant(false)]
         public static void Encode(int[] x, uint[] z, int zOff)
         {
@@ -203,12 +283,29 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Encode128(x, 5, z, zOff + 4);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        [CLSCompliant(false)]
+        public static void Encode(ReadOnlySpan<int> x, Span<uint> z)
+        {
+            Encode128(x, z);
+            Encode128(x[5..], z[4..]);
+        }
+#endif
+
         public static void Encode(int[] x, byte[] z, int zOff)
         {
             Encode128(x, 0, z, zOff);
             Encode128(x, 5, z, zOff + 16);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Encode(ReadOnlySpan<int> x, Span<byte> z)
+        {
+            Encode128(x, z);
+            Encode128(x[5..], z[16..]);
+        }
+#endif
+
         private static void Encode128(int[] x, int xOff, uint[] z, int zOff)
         {
             uint x0 = (uint)x[xOff + 0], x1 = (uint)x[xOff + 1], x2 = (uint)x[xOff + 2], x3 = (uint)x[xOff + 3],
@@ -220,6 +317,18 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[zOff + 3] = (x3 >> 19) | (x4 <<  7);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode128(ReadOnlySpan<int> x, Span<uint> z)
+        {
+            uint x0 = (uint)x[0], x1 = (uint)x[1], x2 = (uint)x[2], x3 = (uint)x[3], x4 = (uint)x[4];
+
+            z[0] =  x0        | (x1 << 26);
+            z[1] = (x1 >>  6) | (x2 << 20);
+            z[2] = (x2 >> 12) | (x3 << 13);
+            z[3] = (x3 >> 19) | (x4 <<  7);
+        }
+#endif
+
         private static void Encode128(int[] x, int xOff, byte[] bs, int off)
         {
             uint x0 = (uint)x[xOff + 0], x1 = (uint)x[xOff + 1], x2 = (uint)x[xOff + 2];
@@ -231,6 +340,19 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             uint t3 = (x3 >> 19) | (x4 <<  7);  Encode32(t3, bs, off + 12);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode128(ReadOnlySpan<int> x, Span<byte> bs)
+        {
+            uint x0 = (uint)x[0], x1 = (uint)x[1], x2 = (uint)x[2];
+            uint x3 = (uint)x[3], x4 = (uint)x[4];
+
+            uint t0 =  x0        | (x1 << 26);  Encode32(t0, bs);
+            uint t1 = (x1 >>  6) | (x2 << 20);  Encode32(t1, bs[4..]);
+            uint t2 = (x2 >> 12) | (x3 << 13);  Encode32(t2, bs[8..]);
+            uint t3 = (x3 >> 19) | (x4 <<  7);  Encode32(t3, bs[12..]);
+        }
+#endif
+
         private static void Encode32(uint n, byte[] bs, int off)
         {
             bs[  off] = (byte)(n      );
@@ -239,8 +361,21 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             bs[++off] = (byte)(n >> 24);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode32(uint n, Span<byte> bs)
+        {
+            bs[0] = (byte)(n      );
+            bs[1] = (byte)(n >>  8);
+            bs[2] = (byte)(n >> 16);
+            bs[3] = (byte)(n >> 24);
+        }
+#endif
+
         public static void Inv(int[] x, int[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Inv(x.AsSpan(), z.AsSpan());
+#else
             //int[] x2 = Create();
             //int[] t = Create();
             //PowPm5d8(x, x2, t);
@@ -257,10 +392,30 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Mod.ModOddInverse(P32, u, u);
 
             Decode(u, 0, z);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Inv(ReadOnlySpan<int> x, Span<int> z)
+        {
+            Span<int> t = stackalloc int[Size];
+            Span<uint> u = stackalloc uint[8];
+
+            Copy(x, t);
+            Normalize(t);
+            Encode(t, u);
+
+            Mod.ModOddInverse(P32, u, u);
+
+            Decode(u, z);
+        }
+#endif
+
         public static void InvVar(int[] x, int[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            InvVar(x.AsSpan(), z.AsSpan());
+#else
             int[] t = Create();
             uint[] u = new uint[8];
 
@@ -271,8 +426,25 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Mod.ModOddInverseVar(P32, u, u);
 
             Decode(u, 0, z);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void InvVar(ReadOnlySpan<int> x, Span<int> z)
+        {
+            Span<int> t = stackalloc int[Size];
+            Span<uint> u = stackalloc uint[8];
+
+            Copy(x, t);
+            Normalize(t);
+            Encode(t, u);
+
+            Mod.ModOddInverseVar(P32, u, u);
+
+            Decode(u, z);
+        }
+#endif
+
         public static int IsOne(int[] x)
         {
             int d = x[0] ^ 1;
@@ -507,6 +679,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Debug.Assert(z[9] >> 24 == 0);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Normalize(Span<int> z)
+        {
+            int x = (z[9] >> 23) & 1;
+            Reduce(z, x);
+            Reduce(z, -x);
+            Debug.Assert(z[9] >> 24 == 0);
+        }
+#endif
+
         public static void One(int[] z)
         {
             z[0] = 1;
@@ -556,6 +738,26 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[9] = z9 + (int)cc;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Reduce(Span<int> z, int x)
+        {
+            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;
+        }
+#endif
+
         public static void Sqr(int[] x, int[] z)
         {
             int x0 = x[0];
diff --git a/crypto/src/math/ec/rfc7748/X448.cs b/crypto/src/math/ec/rfc7748/X448.cs
index 7de78ebdc..7e078c5c6 100644
--- a/crypto/src/math/ec/rfc7748/X448.cs
+++ b/crypto/src/math/ec/rfc7748/X448.cs
@@ -27,6 +27,35 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             return !Arrays.AreAllZeroes(r, rOff, PointSize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool CalculateAgreement(ReadOnlySpan<byte> k, ReadOnlySpan<byte> u, Span<byte> r)
+        {
+            ScalarMult(k, u, r);
+            return !Arrays.AreAllZeroes(r[..PointSize]);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+
+        private static void DecodeScalar(ReadOnlySpan<byte> k, uint[] n)
+        {
+            for (int i = 0; i < 14; ++i)
+            {
+                n[i] = Decode32(k[(i * 4)..]);
+            }
+
+            n[ 0] &= 0xFFFFFFFCU;
+            n[13] |= 0x80000000U;
+        }
+#else
         private static uint Decode32(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -46,20 +75,44 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             n[ 0] &= 0xFFFFFFFCU;
             n[13] |= 0x80000000U;
         }
+#endif
 
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
 
             k[0] &= 0xFC;
             k[ScalarSize - 1] |= 0x80;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+
+            k[0] &= 0xFC;
+            k[ScalarSize - 1] |= 0x80;
+        }
+#endif
+
         public static void GeneratePublicKey(byte[] k, int kOff, byte[] r, int rOff)
         {
             ScalarMultBase(k, kOff, r, rOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            ScalarMultBase(k, r);
+        }
+#endif
+
         private static void PointDouble(uint[] x, uint[] z)
         {
             uint[] a = F.Create();
@@ -84,6 +137,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
         public static void ScalarMult(byte[] k, int kOff, byte[] u, int uOff, byte[] r, int rOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMult(k.AsSpan(kOff), u.AsSpan(uOff), r.AsSpan(rOff));
+#else
             uint[] n = new uint[14];    DecodeScalar(k, kOff, n);
 
             uint[] x1 = F.Create();     F.Decode(u, uOff, x1);
@@ -148,10 +204,84 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
             F.Normalize(x2);
             F.Encode(x2, r, rOff);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ScalarMult(ReadOnlySpan<byte> k, ReadOnlySpan<byte> u, Span<byte> r)
+        {
+            uint[] n = new uint[14];    DecodeScalar(k, n);
+
+            uint[] x1 = F.Create();     F.Decode(u, x1);
+            uint[] x2 = F.Create();     F.Copy(x1, 0, x2, 0);
+            uint[] z2 = F.Create();     z2[0] = 1;
+            uint[] x3 = F.Create();     x3[0] = 1;
+            uint[] z3 = F.Create();
+
+            uint[] t1 = F.Create();
+            uint[] t2 = F.Create();
+
+            Debug.Assert(n[13] >> 31 == 1U);
+
+            int bit = 447, swap = 1;
+            do
+            {
+                //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);
+
+                F.Mul(t1, x2, t1);
+                F.Mul(x3, z3, x3);
+                F.Sqr(z3, z3);
+                F.Sqr(x2, 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);
+
+                //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 = (int)(n[word] >> shift) & 1;
+                swap ^= kt;
+                F.CSwap(swap, x2, x3);
+                F.CSwap(swap, z2, z3);
+                swap = kt;
+            }
+            while (bit >= 2);
+
+            Debug.Assert(swap == 0);
+
+            for (int i = 0; i < 2; ++i)
+            {
+                PointDouble(x2, z2);
+            }
+
+            F.Inv(z2, z2);
+            F.Mul(x2, z2, x2);
+
+            F.Normalize(x2);
+            F.Encode(x2, r);
+        }
+#endif
+
         public static void ScalarMultBase(byte[] k, int kOff, byte[] r, int rOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBase(k.AsSpan(kOff), r.AsSpan(rOff));
+#else
             uint[] x = F.Create();
             uint[] y = F.Create();
 
@@ -163,6 +293,24 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
             F.Normalize(x);
             F.Encode(x, r, rOff);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ScalarMultBase(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            uint[] x = F.Create();
+            uint[] y = F.Create();
+
+            Ed448.ScalarMultBaseXY(k, x, y);
+
+            F.Inv(x, x);
+            F.Mul(x, y, x);
+            F.Sqr(x, x);
+
+            F.Normalize(x);
+            F.Encode(x, r);
         }
+#endif
     }
 }
diff --git a/crypto/src/math/ec/rfc7748/X448Field.cs b/crypto/src/math/ec/rfc7748/X448Field.cs
index 70273aea8..1e1d379fe 100644
--- a/crypto/src/math/ec/rfc7748/X448Field.cs
+++ b/crypto/src/math/ec/rfc7748/X448Field.cs
@@ -112,6 +112,22 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CMov(int cond, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            Debug.Assert(0 == cond || -1 == cond);
+
+            uint MASK = (uint)cond;
+
+            for (int i = 0; i < Size; ++i)
+            {
+                uint z_i = z[i], diff = z_i ^ x[i];
+                z_i ^= (diff & MASK);
+                z[i] = z_i;
+            }
+        }
+#endif
+
         public static void CNegate(int negate, uint[] z)
         {
             Debug.Assert(negate >> 1 == 0);
@@ -130,6 +146,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Copy(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            x[..Size].CopyTo(z);
+        }
+#endif
+
         public static uint[] Create()
         {
             return new uint[Size];
@@ -161,6 +184,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Decode224(x, xOff + 7, z, 8);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Decode(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            Decode224(x, z);
+            Decode224(x[7..], z[8..]);
+        }
+#endif
+
         public static void Decode(byte[] x, int xOff, uint[] z)
         {
             Decode56(x, xOff, z, 0);
@@ -173,6 +204,20 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Decode56(x, xOff + 49, z, 14);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Decode(ReadOnlySpan<byte> x, Span<uint> z)
+        {
+            Decode56(x, z);
+            Decode56(x[7..], z[2..]);
+            Decode56(x[14..], z[4..]);
+            Decode56(x[21..], z[6..]);
+            Decode56(x[28..], z[8..]);
+            Decode56(x[35..], z[10..]);
+            Decode56(x[42..], z[12..]);
+            Decode56(x[49..], z[14..]);
+        }
+#endif
+
         private static void Decode224(uint[] x, int xOff, uint[] z, int zOff)
         {
             uint x0 = x[xOff + 0], x1 = x[xOff + 1], x2 = x[xOff + 2], x3 = x[xOff + 3];
@@ -188,6 +233,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[zOff + 7] = x6 >> 4;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode224(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            uint x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3];
+            uint x4 = x[4], x5 = x[5], x6 = x[6];
+
+            z[0] = x0 & M28;
+            z[1] = (x0 >> 28 | x1 <<  4) & M28;
+            z[2] = (x1 >> 24 | x2 <<  8) & M28;
+            z[3] = (x2 >> 20 | x3 << 12) & M28;
+            z[4] = (x3 >> 16 | x4 << 16) & M28;
+            z[5] = (x4 >> 12 | x5 << 20) & M28;
+            z[6] = (x5 >>  8 | x6 << 24) & M28;
+            z[7] = x6 >> 4;
+        }
+#endif
+
         private static uint Decode24(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -196,6 +258,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode24(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            return n;
+        }
+#endif
+
         private static uint Decode32(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -205,6 +277,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+#endif
+
         private static void Decode56(byte[] bs, int off, uint[] z, int zOff)
         {
             uint lo = Decode32(bs, off);
@@ -213,12 +296,30 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[zOff + 1] = (lo >> 28) | (hi << 4);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode56(ReadOnlySpan<byte> bs, Span<uint> z)
+        {
+            uint lo = Decode32(bs);
+            uint hi = Decode24(bs[4..]);
+            z[0] = lo & M28;
+            z[1] = (lo >> 28) | (hi << 4);
+        }
+#endif
+
         public static void Encode(uint[] x, uint[] z, int zOff)
         {
             Encode224(x, 0, z, zOff);
             Encode224(x, 8, z, zOff + 7);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Encode(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            Encode224(x, z);
+            Encode224(x[8..], z[7..]);
+        }
+#endif
+
         public static void Encode(uint[] x, byte[] z, int zOff)
         {
             Encode56(x, 0, z, zOff);
@@ -231,6 +332,20 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Encode56(x, 14, z, zOff + 49);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Encode(ReadOnlySpan<uint> x, Span<byte> z)
+        {
+            Encode56(x, z);
+            Encode56(x[2..], z[7..]);
+            Encode56(x[4..], z[14..]);
+            Encode56(x[6..], z[21..]);
+            Encode56(x[8..], z[28..]);
+            Encode56(x[10..], z[35..]);
+            Encode56(x[12..], z[42..]);
+            Encode56(x[14..], z[49..]);
+        }
+#endif
+
         private static void Encode224(uint[] x, int xOff, uint[] z, int zOff)
         {
             uint x0 = x[xOff + 0], x1 = x[xOff + 1], x2 = x[xOff + 2], x3 = x[xOff + 3];
@@ -245,6 +360,22 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[zOff + 6] = (x6 >> 24) | (x7 <<  4);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode224(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            uint x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3];
+            uint x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7];
+
+            z[0] =  x0        | (x1 << 28);
+            z[1] = (x1 >>  4) | (x2 << 24);
+            z[2] = (x2 >>  8) | (x3 << 20);
+            z[3] = (x3 >> 12) | (x4 << 16);
+            z[4] = (x4 >> 16) | (x5 << 12);
+            z[5] = (x5 >> 20) | (x6 <<  8);
+            z[6] = (x6 >> 24) | (x7 <<  4);
+        }
+#endif
+
         private static void Encode24(uint n, byte[] bs, int off)
         {
             bs[  off] = (byte)(n      );
@@ -252,6 +383,15 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             bs[++off] = (byte)(n >> 16);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode24(uint n, Span<byte> bs)
+        {
+            bs[0] = (byte)(n      );
+            bs[1] = (byte)(n >>  8);
+            bs[2] = (byte)(n >> 16);
+        }
+#endif
+
         private static void Encode32(uint n, byte[] bs, int off)
         {
             bs[  off] = (byte)(n      );
@@ -260,6 +400,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             bs[++off] = (byte)(n >> 24);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode32(uint n, Span<byte> bs)
+        {
+            bs[0] = (byte)(n      );
+            bs[1] = (byte)(n >>  8);
+            bs[2] = (byte)(n >> 16);
+            bs[3] = (byte)(n >> 24);
+        }
+#endif
+
         private static void Encode56(uint[] x, int xOff, byte[] bs, int off)
         {
             uint lo = x[xOff], hi = x[xOff + 1];
@@ -267,8 +417,20 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Encode24(hi >> 4, bs, off + 4);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode56(ReadOnlySpan<uint> x, Span<byte> bs)
+        {
+            uint lo = x[0], hi = x[1];
+            Encode32(lo | (hi << 28), bs);
+            Encode24(hi >> 4, bs[4..]);
+        }
+#endif
+
         public static void Inv(uint[] x, uint[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Inv(x.AsSpan(), z.AsSpan());
+#else
             //uint[] t = Create();
             //PowPm3d4(x, t);
             //Sqr(t, 2, t);
@@ -284,10 +446,30 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Mod.ModOddInverse(P32, u, u);
 
             Decode(u, 0, z);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Inv(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            Span<uint> t = stackalloc uint[Size];
+            Span<uint> u = stackalloc uint[14];
+
+            Copy(x, t);
+            Normalize(t);
+            Encode(t, u);
+
+            Mod.ModOddInverse(P32, u, u);
+
+            Decode(u, z);
+        }
+#endif
+
         public static void InvVar(uint[] x, uint[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            InvVar(x.AsSpan(), z.AsSpan());
+#else
             uint[] t = Create();
             uint[] u = new uint[14];
 
@@ -298,8 +480,25 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Mod.ModOddInverseVar(P32, u, u);
 
             Decode(u, 0, z);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void InvVar(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            Span<uint> t = stackalloc uint[Size];
+            Span<uint> u = stackalloc uint[14];
+
+            Copy(x, t);
+            Normalize(t);
+            Encode(t, u);
+
+            Mod.ModOddInverseVar(P32, u, u);
+
+            Decode(u, z);
+        }
+#endif
+
         public static int IsOne(uint[] x)
         {
             uint d = x[0] ^ 1;
@@ -726,6 +925,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Debug.Assert(z[15] >> 28 == 0U);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Normalize(Span<uint> z)
+        {
+            //int x = (z[15] >> (28 - 1)) & 1;
+            Reduce(z, 1);
+            Reduce(z, -1);
+            Debug.Assert(z[15] >> 28 == 0U);
+        }
+#endif
+
         public static void One(uint[] z)
         {
             z[0] = 1U;
@@ -775,6 +984,26 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             z[15] = z15 + (uint)cc;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Reduce(Span<uint> z, int x)
+        {
+            uint u = z[15], z15 = u & M28;
+            int t = (int)(u >> 28) + x;
+
+            long cc = t;
+            for (int i = 0; i < 8; ++i)
+            {
+                cc += z[i]; z[i] = (uint)cc & M28; cc >>= 28;
+            }
+            cc += t;
+            for (int i = 8; i < 15; ++i)
+            {
+                cc += z[i]; z[i] = (uint)cc & M28; cc >>= 28;
+            }
+            z[15] = z15 + (uint)cc;
+        }
+#endif
+
         public static void Sqr(uint[] x, uint[] z)
         {
             uint x0 = x[0];
diff --git a/crypto/src/math/ec/rfc8032/Ed25519.cs b/crypto/src/math/ec/rfc8032/Ed25519.cs
index d88914c90..f3b63f3b3 100644
--- a/crypto/src/math/ec/rfc8032/Ed25519.cs
+++ b/crypto/src/math/ec/rfc8032/Ed25519.cs
@@ -92,12 +92,12 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         private static PointPrecomp[] PrecompBaseWnaf = null;
         private static int[] PrecompBaseComb = null;
 
-        private ref struct PointAccum
+        private struct PointAccum
         {
             internal int[] x, y, z, u, v;
         }
 
-        private ref struct PointAffine
+        private struct PointAffine
         {
             internal int[] x, y;
         }
@@ -238,6 +238,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+#endif
+
         private static void Decode32(byte[] bs, int bsOff, uint[] n, int nOff, int nLen)
         {
             for (int i = 0; i < nLen; ++i)
@@ -246,6 +257,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode32(ReadOnlySpan<byte> bs, Span<uint> n)
+        {
+            for (int i = 0; i < n.Length; ++i)
+            {
+                n[i] = Decode32(bs[(i * 4)..]);
+            }
+        }
+#endif
+
         private static bool DecodePointVar(byte[] p, int pOff, bool negate, ref PointAffine r)
         {
             byte[] py = Copy(p, pOff, PointBytes);
@@ -285,6 +306,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Decode32(k, kOff, n, 0, ScalarUints);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void DecodeScalar(ReadOnlySpan<byte> k, Span<uint> n)
+        {
+            Decode32(k, n[..ScalarUints]);
+        }
+#endif
+
         private static void Dom2(IDigest d, byte phflag, byte[] ctx)
         {
             if (ctx != null)
@@ -323,6 +351,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static int EncodePoint(ref PointAccum p, byte[] r, int rOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodePoint(ref p, r.AsSpan(rOff));
+#else
             int[] x = F.Create();
             int[] y = F.Create();
 
@@ -338,15 +369,53 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             r[rOff + PointBytes - 1] |= (byte)((x[0] & 1) << 7);
 
             return result;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int EncodePoint(ref PointAccum p, Span<byte> r)
+        {
+            int[] x = F.Create();
+            int[] y = F.Create();
+
+            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);
+
+            F.Encode(y, r);
+            r[PointBytes - 1] |= (byte)((x[0] & 1) << 7);
+
+            return result;
         }
+#endif
 
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+        }
+#endif
+
         public static void GeneratePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GeneratePublicKey(sk.AsSpan(skOff), pk.AsSpan(pkOff));
+#else
             IDigest d = CreateDigest();
             byte[] h = new byte[d.GetDigestSize()];
 
@@ -357,9 +426,33 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             PruneScalar(h, 0, s);
 
             ScalarMultBaseEncoded(s, pk, pkOff);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> sk, Span<byte> pk)
+        {
+            IDigest d = CreateDigest();
+            int digestSize = d.GetDigestSize();
+            Span<byte> h = digestSize <= 128
+                ? stackalloc byte[digestSize]
+                : new byte[digestSize];
+
+            d.BlockUpdate(sk[..SecretKeySize]);
+            d.DoFinal(h);
+
+            Span<byte> s = stackalloc byte[ScalarBytes];
+            PruneScalar(h, s);
+
+            ScalarMultBaseEncoded(s, pk);
         }
+#endif
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint GetWindow4(ReadOnlySpan<uint> x, int n)
+#else
         private static uint GetWindow4(uint[] x, int n)
+#endif
         {
             int w = (int)((uint)n >> 3), b = (n & 7) << 2;
             return (x[w] >> b) & 15U;
@@ -496,7 +589,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!CheckScalarVar(S, nS))
                 return false;
 
-            PointAffine pA; Init(out pA);
+            Init(out PointAffine pA);
             if (!DecodePointVar(pk, pkOff, true, ref pA))
                 return false;
 
@@ -514,7 +607,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             uint[] nA = new uint[ScalarUints];
             DecodeScalar(k, 0, nA);
 
-            PointAccum pR; Init(out pR);
+            Init(out PointAccum pR);
             ScalarMultStrausVar(nS, nA, ref pA, ref pR);
 
             byte[] check = new byte[PointBytes];
@@ -818,6 +911,32 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PointLookupZ(ReadOnlySpan<uint> x, int n, ReadOnlySpan<int> table, ref PointPrecompZ r)
+        {
+            // TODO This method is currently hard-coded to 4-bit windows and 8 precomputed points
+
+            uint w = GetWindow4(x, n);
+
+            int sign = (int)(w >> (4 - 1)) ^ 1;
+            int abs = ((int)w ^ -sign) & 7;
+
+            Debug.Assert(sign == 0 || sign == 1);
+            Debug.Assert(0 <= abs && abs < 8);
+
+            for (int i = 0; i < 8; ++i)
+            {
+                int cond = ((i ^ abs) - 1) >> 31;
+                F.CMov(cond, table, r.ymx_h);       table = table[F.Size..];
+                F.CMov(cond, table, r.ypx_h);       table = table[F.Size..];
+                F.CMov(cond, table, r.xyd);         table = table[F.Size..];
+                F.CMov(cond, table, r.z);           table = table[F.Size..];
+            }
+
+            F.CSwap(sign, r.ymx_h, r.ypx_h);
+            F.CNegate(sign, r.xyd);
+        }
+#else
         private static void PointLookupZ(uint[] x, int n, int[] table, ref PointPrecompZ r)
         {
             // TODO This method is currently hard-coded to 4-bit windows and 8 precomputed points
@@ -842,6 +961,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.CSwap(sign, r.ymx_h, r.ypx_h);
             F.CNegate(sign, r.xyd);
         }
+#endif
 
         private static void PointPrecompute(ref PointAffine p, PointExtended[] points, int count, ref PointTemp t)
         {
@@ -850,7 +970,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Init(out points[0]);
             PointCopy(ref p, ref points[0]);
 
-            PointExtended d; Init(out d);
+            Init(out PointExtended d);
             PointAdd(ref points[0], ref points[0], ref d, ref t);
 
             for (int i = 1; i < count; ++i)
@@ -864,13 +984,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             Debug.Assert(count > 0);
 
-            PointExtended q; Init(out q);
+            Init(out PointExtended q);
             PointCopy(ref p, ref q);
 
-            PointExtended d; Init(out d);
+            Init(out PointExtended d);
             PointAdd(ref q, ref q, ref d, ref t);
 
-            PointPrecompZ r; Init(out r);
+            Init(out PointPrecompZ r);
             int[] table = F.CreateTable(count * 4);
             int off = 0;
 
@@ -897,10 +1017,10 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             Debug.Assert(count > 0);
 
-            PointExtended q; Init(out q);
+            Init(out PointExtended q);
             PointCopy(ref p, ref q);
 
-            PointExtended d; Init(out d);
+            Init(out PointExtended d);
             PointAdd(ref q, ref q, ref d, ref t);
 
             int i = 0;
@@ -938,15 +1058,15 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 int totalPoints = wnafPoints + combPoints;
 
                 PointExtended[] points = new PointExtended[totalPoints];
-                PointTemp t; Init(out t);
+                Init(out PointTemp t);
 
-                PointAffine b; Init(out b);
+                Init(out PointAffine b);
                 F.Copy(B_x, 0, b.x, 0);
                 F.Copy(B_y, 0, b.y, 0);
 
                 PointPrecompute(ref b, points, wnafPoints, ref t);
 
-                PointAccum p; Init(out p);
+                Init(out PointAccum p);
                 F.Copy(B_x, 0, p.x, 0);
                 F.Copy(B_y, 0, p.y, 0);
                 F.One(p.z);
@@ -959,7 +1079,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 {
                     Init(out toothPowers[tooth]);
                 }
-                PointExtended u; Init(out u);
+                Init(out PointExtended u);
                 for (int block = 0; block < PrecompBlocks; ++block)
                 {
                     ref PointExtended sum = ref points[pointsIndex++];
@@ -1032,7 +1152,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 }
 
                 PrecompBaseComb = F.CreateTable(combPoints * 3);
-                PointPrecomp s; Init(out s);
+                Init(out PointPrecomp s);
                 int off = 0;
                 for (int i = wnafPoints; i < totalPoints; ++i)
                 {
@@ -1070,6 +1190,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             r[ScalarBytes - 1] |= 0x40;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PruneScalar(ReadOnlySpan<byte> n, Span<byte> r)
+        {
+            n[..ScalarBytes].CopyTo(r);
+
+            r[0] &= 0xF8;
+            r[ScalarBytes - 1] &= 0x7F;
+            r[ScalarBytes - 1] |= 0x40;
+        }
+#endif
+
         private static byte[] ReduceScalar(byte[] n)
         {
             long x00 = Decode32(n,  0) & M32L;          // x00:32/--
@@ -1208,6 +1339,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static void ScalarMult(byte[] k, ref PointAffine p, ref PointAccum r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMult(k.AsSpan(), ref p, ref r);
+#else
             uint[] n = new uint[ScalarUints];
             DecodeScalar(k, 0, n);
 
@@ -1217,8 +1351,8 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 uint c2 = Nat.ShiftDownBit(ScalarUints, n, 1U);             Debug.Assert(c2 == (1U << 31));
             }
 
-            PointPrecompZ q; Init(out q);
-            PointTemp t; Init(out t);
+            Init(out PointPrecompZ q);
+            Init(out PointTemp t);
             int[] table = PointPrecomputeZ(ref p, 8, ref t);
 
             PointSetNeutral(ref r);
@@ -1237,12 +1371,51 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                     PointDouble(ref r);
                 }
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMult(ReadOnlySpan<byte> k, ref PointAffine p, ref PointAccum r)
+        {
+            Span<uint> n = stackalloc uint[ScalarUints];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form
+            {
+                uint c1 = Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);   Debug.Assert(c1 == 0U);
+                uint c2 = Nat.ShiftDownBit(ScalarUints, n, 1U);             Debug.Assert(c2 == (1U << 31));
+            }
+
+            Init(out PointPrecompZ q);
+            Init(out PointTemp t);
+            int[] table = PointPrecomputeZ(ref p, 8, ref t);
+
+            PointSetNeutral(ref r);
+
+            int w = 63;
+            for (;;)
+            {
+                PointLookupZ(n, w, table, ref q);
+                PointAdd(ref q, ref r, ref t);
+
+                if (--w < 0)
+                    break;
+
+                for (int i = 0; i < 4; ++i)
+                {
+                    PointDouble(ref r);
+                }
+            }
+        }
+#endif
+
         private static void ScalarMultBase(byte[] k, ref PointAccum r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBase(k.AsSpan(), ref r);
+#else
             // Equivalent (but much slower)
-            //PointAffine p; Init(out p);
+            //Init(out PointAffine p);
             //F.Copy(B_x, 0, p.x, 0);
             //F.Copy(B_y, 0, p.y, 0);
             //ScalarMult(k, ref p, ref r);
@@ -1267,8 +1440,8 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 }
             }
 
-            PointPrecomp p; Init(out p);
-            PointTemp t; Init(out t);
+            Init(out PointPrecomp p);
+            Init(out PointTemp t);
 
             PointSetNeutral(ref r);
             int resultSign = 0;
@@ -1302,22 +1475,107 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             F.CNegate(resultSign, r.x);
             F.CNegate(resultSign, r.u);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBase(ReadOnlySpan<byte> k, ref PointAccum r)
+        {
+            // Equivalent (but much slower)
+            //Init(out PointAffine p);
+            //F.Copy(B_x, 0, p.x, 0);
+            //F.Copy(B_y, 0, p.y, 0);
+            //ScalarMult(k, ref p, ref r);
+
+            Precompute();
+
+            Span<uint> n = stackalloc uint[ScalarUints];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form, then group comb bits in each block
+            {
+                uint c1 = Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);   Debug.Assert(c1 == 0U);
+                uint c2 = Nat.ShiftDownBit(ScalarUints, n, 1U);             Debug.Assert(c2 == (1U << 31));
+
+                /*
+                 * Because we are using 4 teeth and 8 spacing, each limb of n corresponds to one of the 8 blocks.
+                 * Therefore we can efficiently group the bits for each comb position using a (double) shuffle. 
+                 */
+                for (int i = 0; i < ScalarUints; ++i)
+                {
+                    n[i] = Interleave.Shuffle2(n[i]);
+                }
+            }
+
+            Init(out PointPrecomp p);
+            Init(out PointTemp t);
+
+            PointSetNeutral(ref r);
+            int resultSign = 0;
+
+            int cOff = (PrecompSpacing - 1) * PrecompTeeth;
+            for (;;)
+            {
+                for (int b = 0; b < PrecompBlocks; ++b)
+                {
+                    uint w = n[b] >> cOff;
+                    int sign = (int)(w >> (PrecompTeeth - 1)) & 1;
+                    int abs = ((int)w ^ -sign) & PrecompMask;
+
+                    Debug.Assert(sign == 0 || sign == 1);
+                    Debug.Assert(0 <= abs && abs < PrecompPoints);
+
+                    PointLookup(b, abs, ref p);
+
+                    F.CNegate(resultSign ^ sign, r.x);
+                    F.CNegate(resultSign ^ sign, r.u);
+                    resultSign = sign;
+
+                    PointAdd(ref p, ref r, ref t);
+                }
+
+                if ((cOff -= PrecompTeeth) < 0)
+                    break;
+
+                PointDouble(ref r);
+            }
+
+            F.CNegate(resultSign, r.x);
+            F.CNegate(resultSign, r.u);
+        }
+#endif
+
         private static void ScalarMultBaseEncoded(byte[] k, byte[] r, int rOff)
         {
-            PointAccum p; Init(out p);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBaseEncoded(k.AsSpan(), r.AsSpan(rOff));
+#else
+            Init(out PointAccum p);
             ScalarMultBase(k, ref p);
             if (0 == EncodePoint(ref p, r, rOff))
                 throw new InvalidOperationException();
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBaseEncoded(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            Init(out PointAccum p);
+            ScalarMultBase(k, ref p);
+            if (0 == EncodePoint(ref p, r))
+                throw new InvalidOperationException();
         }
+#endif
 
         internal static void ScalarMultBaseYZ(byte[] k, int kOff, int[] y, int[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBaseYZ(k.AsSpan(kOff), y.AsSpan(), z.AsSpan());
+#else
             byte[] n = new byte[ScalarBytes];
             PruneScalar(k, kOff, n);
 
-            PointAccum p; Init(out p);
+            Init(out PointAccum p);
             ScalarMultBase(n, ref p);
 
             if (0 == CheckPoint(p.x, p.y, p.z))
@@ -1325,7 +1583,25 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             F.Copy(p.y, 0, y, 0);
             F.Copy(p.z, 0, z, 0);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void ScalarMultBaseYZ(ReadOnlySpan<byte> k, Span<int> y, Span<int> z)
+        {
+            Span<byte> n = stackalloc byte[ScalarBytes];
+            PruneScalar(k, n);
+
+            Init(out PointAccum p);
+            ScalarMultBase(n, ref p);
+
+            if (0 == CheckPoint(p.x, p.y, p.z))
+                throw new InvalidOperationException();
+
+            F.Copy(p.y, y);
+            F.Copy(p.z, z);
         }
+#endif
 
         private static void ScalarMultOrderVar(ref PointAffine p, ref PointAccum r)
         {
@@ -1333,7 +1609,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             int count = 1 << (WnafWidth - 2);
             PointPrecompZ[] tp = new PointPrecompZ[count];
-            PointTemp t; Init(out t);
+            Init(out PointTemp t);
             PointPrecomputeZ(ref p, tp, count, ref t);
 
             PointSetNeutral(ref r);
@@ -1365,7 +1641,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             int count = 1 << (WnafWidth - 2);
             PointPrecompZ[] tp = new PointPrecompZ[count];
-            PointTemp t; Init(out t);
+            Init(out PointTemp t);
             PointPrecomputeZ(ref p, tp, count, ref t);
 
             PointSetNeutral(ref r);
@@ -1465,7 +1741,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static bool ValidatePublicKeyFull(byte[] pk, int pkOff)
         {
-            PointAffine p; Init(out p);
+            Init(out PointAffine p);
             if (!DecodePointVar(pk, pkOff, false, ref p))
                 return false;
 
@@ -1475,7 +1751,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (IsNeutralElementVar(p.x, p.y))
                 return false;
 
-            PointAccum r; Init(out r);
+            Init(out PointAccum r);
             ScalarMultOrderVar(ref p, ref r);
 
             F.Normalize(r.x);
@@ -1487,7 +1763,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff)
         {
-            PointAffine p; Init(out p);
+            Init(out PointAffine p);
             return DecodePointVar(pk, pkOff, false, ref p);
         }
 
diff --git a/crypto/src/math/ec/rfc8032/Ed448.cs b/crypto/src/math/ec/rfc8032/Ed448.cs
index 55ec5f03b..b73aaa7f8 100644
--- a/crypto/src/math/ec/rfc8032/Ed448.cs
+++ b/crypto/src/math/ec/rfc8032/Ed448.cs
@@ -222,6 +222,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+#endif
+
         private static void Decode32(byte[] bs, int bsOff, uint[] n, int nOff, int nLen)
         {
             for (int i = 0; i < nLen; ++i)
@@ -230,6 +241,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode32(ReadOnlySpan<byte> bs, Span<uint> n)
+        {
+            for (int i = 0; i < n.Length; ++i)
+            {
+                n[i] = Decode32(bs[(i * 4)..]);
+            }
+        }
+#endif
+
         private static bool DecodePointVar(byte[] p, int pOff, bool negate, ref PointProjective r)
         {
             byte[] py = Copy(p, pOff, PointBytes);
@@ -273,6 +294,15 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Decode32(k, kOff, n, 0, ScalarUints);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void DecodeScalar(ReadOnlySpan<byte> k, Span<uint> n)
+        {
+            Debug.Assert(k[ScalarBytes - 1] == 0x00);
+
+            Decode32(k, n[..ScalarUints]);
+        }
+#endif
+
         private static void Dom4(IXof d, byte phflag, byte[] ctx)
         {
             int n = Dom4Prefix.Length;
@@ -325,26 +355,84 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return result;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int EncodePoint(ref PointProjective p, Span<byte> r)
+        {
+            uint[] x = F.Create();
+            uint[] y = F.Create();
+
+            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);
+
+            F.Encode(y, r);
+            r[PointBytes - 1] = (byte)((x[0] & 1) << 7);
+
+            return result;
+        }
+#endif
+
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+        }
+#endif
+
         public static void GeneratePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GeneratePublicKey(sk.AsSpan(skOff), pk.AsSpan(pkOff));
+#else
             IXof d = CreateXof();
             byte[] h = new byte[ScalarBytes * 2];
 
             d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0, h.Length);
+            d.OutputFinal(h, 0, h.Length);
 
             byte[] s = new byte[ScalarBytes];
             PruneScalar(h, 0, s);
 
             ScalarMultBaseEncoded(s, pk, pkOff);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> sk, Span<byte> pk)
+        {
+            IXof d = CreateXof();
+            Span<byte> h = stackalloc byte[ScalarBytes * 2];
+
+            d.BlockUpdate(sk[..SecretKeySize]);
+            d.OutputFinal(h);
+
+            Span<byte> s = stackalloc byte[ScalarBytes];
+            PruneScalar(h, s);
+
+            ScalarMultBaseEncoded(s, pk);
         }
+#endif
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint GetWindow4(ReadOnlySpan<uint> x, int n)
+#else
         private static uint GetWindow4(uint[] x, int n)
+#endif
         {
             int w = (int)((uint)n >> 3), b = (n & 7) << 2;
             return (x[w] >> b) & 15U;
@@ -407,7 +495,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Dom4(d, phflag, ctx);
             d.BlockUpdate(h, ScalarBytes, ScalarBytes);
             d.BlockUpdate(m, mOff, mLen);
-            d.DoFinal(h, 0, h.Length);
+            d.OutputFinal(h, 0, h.Length);
 
             byte[] r = ReduceScalar(h);
             byte[] R = new byte[PointBytes];
@@ -417,7 +505,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             d.BlockUpdate(R, 0, PointBytes);
             d.BlockUpdate(pk, pkOff, PointBytes);
             d.BlockUpdate(m, mOff, mLen);
-            d.DoFinal(h, 0, h.Length);
+            d.OutputFinal(h, 0, h.Length);
 
             byte[] k = ReduceScalar(h);
             byte[] S = CalculateS(r, k, s);
@@ -436,7 +524,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             byte[] h = new byte[ScalarBytes * 2];
 
             d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0, h.Length);
+            d.OutputFinal(h, 0, h.Length);
 
             byte[] s = new byte[ScalarBytes];
             PruneScalar(h, 0, s);
@@ -457,7 +545,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             byte[] h = new byte[ScalarBytes * 2];
 
             d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0, h.Length);
+            d.OutputFinal(h, 0, h.Length);
 
             byte[] s = new byte[ScalarBytes];
             PruneScalar(h, 0, s);
@@ -481,7 +569,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!CheckScalarVar(S, nS))
                 return false;
 
-            PointProjective pA; Init(out pA);
+            Init(out PointProjective pA);
             if (!DecodePointVar(pk, pkOff, true, ref pA))
                 return false;
 
@@ -492,14 +580,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             d.BlockUpdate(R, 0, PointBytes);
             d.BlockUpdate(pk, pkOff, PointBytes);
             d.BlockUpdate(m, mOff, mLen);
-            d.DoFinal(h, 0, h.Length);
+            d.OutputFinal(h, 0, h.Length);
 
             byte[] k = ReduceScalar(h);
 
             uint[] nA = new uint[ScalarUints];
             DecodeScalar(k, 0, nA);
 
-            PointProjective pR; Init(out pR);
+            Init(out PointProjective pR);
             ScalarMultStrausVar(nS, nA, ref pA, ref pR);
 
             byte[] check = new byte[PointBytes];
@@ -763,6 +851,30 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PointLookup(ReadOnlySpan<uint> x, int n, ReadOnlySpan<uint> table, ref PointProjective r)
+        {
+            // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points
+
+            uint w = GetWindow4(x, n);
+
+            int sign = (int)(w >> (4 - 1)) ^ 1;
+            int abs = ((int)w ^ -sign) & 7;
+
+            Debug.Assert(sign == 0 || sign == 1);
+            Debug.Assert(0 <= abs && abs < 8);
+
+            for (int i = 0; i < 8; ++i)
+            {
+                int cond = ((i ^ abs) - 1) >> 31;
+                F.CMov(cond, table, r.x);       table = table[F.Size..];
+                F.CMov(cond, table, r.y);       table = table[F.Size..];
+                F.CMov(cond, table, r.z);       table = table[F.Size..];
+            }
+
+            F.CNegate(sign, r.x);
+        }
+#else
         private static void PointLookup(uint[] x, int n, uint[] table, ref PointProjective r)
         {
             // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points
@@ -785,6 +897,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             F.CNegate(sign, r.x);
         }
+#endif
 
         private static void PointLookup15(uint[] table, ref PointProjective r)
         {
@@ -799,10 +912,10 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             Debug.Assert(count > 0);
 
-            PointProjective q; Init(out q);
+            Init(out PointProjective q);
             PointCopy(ref p, ref q);
 
-            PointProjective d; Init(out d);
+            Init(out PointProjective d);
             PointCopy(ref q, ref d);
             PointDouble(ref d);
 
@@ -829,7 +942,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             Debug.Assert(count > 0);
 
-            PointProjective d; Init(out d);
+            Init(out PointProjective d);
             PointCopy(ref p, ref d);
             PointDouble(ref d);
 
@@ -866,7 +979,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
                 PointProjective[] points = new PointProjective[totalPoints];
 
-                PointProjective p; Init(out p);
+                Init(out PointProjective p);
                 F.Copy(B_x, 0, p.x, 0);
                 F.Copy(B_y, 0, p.y, 0);
                 F.One(p.z);
@@ -960,6 +1073,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             r[ScalarBytes - 1]  = 0x00;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PruneScalar(ReadOnlySpan<byte> n, Span<byte> r)
+        {
+            n[..(ScalarBytes - 1)].CopyTo(r);
+
+            r[0] &= 0xFC;
+            r[ScalarBytes - 2] |= 0x80;
+            r[ScalarBytes - 1]  = 0x00;
+        }
+#endif
+
         private static byte[] ReduceScalar(byte[] n)
         {
             ulong x00 =  Decode32(n,   0);          // x00:32/--
@@ -1239,6 +1363,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static void ScalarMult(byte[] k, ref PointProjective p, ref PointProjective r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMult(k.AsSpan(), ref p, ref r);
+#else
             uint[] n = new uint[ScalarUints];
             DecodeScalar(k, 0, n);
 
@@ -1251,7 +1378,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
 
             uint[] table = PointPrecompute(ref p, 8);
-            PointProjective q; Init(out q);
+            Init(out PointProjective q);
 
             // Replace first 4 doublings (2^4 * P) with 1 addition (P + 15 * P)
             PointLookup15(table, ref r);
@@ -1271,12 +1398,54 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                     PointDouble(ref r);
                 }
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMult(ReadOnlySpan<byte> k, ref PointProjective p, ref PointProjective r)
+        {
+            Span<uint> n = stackalloc uint[ScalarUints];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form
+            {
+                uint c1 = Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);
+                uint c2 = Nat.ShiftDownBit(ScalarUints, n, c1);             Debug.Assert(c2 == (1U << 31));
+
+                // NOTE: Bit 448 is implicitly set after the signed-digit recoding
+            }
+
+            uint[] table = PointPrecompute(ref p, 8);
+            Init(out PointProjective q);
+
+            // Replace first 4 doublings (2^4 * P) with 1 addition (P + 15 * P)
+            PointLookup15(table, ref r);
+            PointAdd(ref p, ref r);
+
+            int w = 111;
+            for (;;)
+            {
+                PointLookup(n, w, table, ref q);
+                PointAdd(ref q, ref r);
+
+                if (--w < 0)
+                    break;
+
+                for (int i = 0; i < 4; ++i)
+                {
+                    PointDouble(ref r);
+                }
+            }
+        }
+#endif
+
         private static void ScalarMultBase(byte[] k, ref PointProjective r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBase(k.AsSpan(), ref r);
+#else
             // Equivalent (but much slower)
-            //PointProjective p; Init(out p);
+            //Init(out PointProjective p);
             //F.Copy(B_x, 0, p.x, 0);
             //F.Copy(B_y, 0, p.y, 0);
             //F.One(p.z);
@@ -1295,7 +1464,71 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 Debug.Assert(c == (1U << 31));
             }
 
-            PointAffine p; Init(out p);
+            Init(out PointAffine p);
+
+            PointSetNeutral(ref r);
+
+            int cOff = PrecompSpacing - 1;
+            for (;;)
+            {
+                int tPos = cOff;
+
+                for (int b = 0; b < PrecompBlocks; ++b)
+                {
+                    uint w = 0;
+                    for (int t = 0; t < PrecompTeeth; ++t)
+                    {
+                        uint tBit = n[tPos >> 5] >> (tPos & 0x1F);
+                        w &= ~(1U << t);
+                        w ^= (tBit << t);
+                        tPos += PrecompSpacing;
+                    }
+
+                    int sign = (int)(w >> (PrecompTeeth - 1)) & 1;
+                    int abs = ((int)w ^ -sign) & PrecompMask;
+
+                    Debug.Assert(sign == 0 || sign == 1);
+                    Debug.Assert(0 <= abs && abs < PrecompPoints);
+
+                    PointLookup(b, abs, ref p);
+
+                    F.CNegate(sign, p.x);
+
+                    PointAdd(ref p, ref r);
+                }
+
+                if (--cOff < 0)
+                    break;
+
+                PointDouble(ref r);
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBase(ReadOnlySpan<byte> k, ref PointProjective r)
+        {
+            // Equivalent (but much slower)
+            //Init(out PointProjective p);
+            //F.Copy(B_x, 0, p.x, 0);
+            //F.Copy(B_y, 0, p.y, 0);
+            //F.One(p.z);
+            //ScalarMult(k, ref p, ref r);
+
+            Precompute();
+
+            Span<uint> n = stackalloc uint[ScalarUints + 1];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form
+            {
+                n[ScalarUints] = (1U << (PrecompRange - 448))
+                               + Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);
+                uint c = Nat.ShiftDownBit(n.Length, n, 0);
+                Debug.Assert(c == (1U << 31));
+            }
+
+            Init(out PointAffine p);
 
             PointSetNeutral(ref r);
 
@@ -1334,21 +1567,39 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 PointDouble(ref r);
             }
         }
+#endif
 
         private static void ScalarMultBaseEncoded(byte[] k, byte[] r, int rOff)
         {
-            PointProjective p; Init(out p);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBaseEncoded(k.AsSpan(), r.AsSpan(rOff));
+#else
+            Init(out PointProjective p);
             ScalarMultBase(k, ref p);
             if (0 == EncodePoint(ref p, r, rOff))
                 throw new InvalidOperationException();
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBaseEncoded(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            Init(out PointProjective p);
+            ScalarMultBase(k, ref p);
+            if (0 == EncodePoint(ref p, r))
+                throw new InvalidOperationException();
+        }
+#endif
+
         internal static void ScalarMultBaseXY(byte[] k, int kOff, uint[] x, uint[] y)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBaseXY(k.AsSpan(kOff), x.AsSpan(), y.AsSpan());
+#else
             byte[] n = new byte[ScalarBytes];
             PruneScalar(k, kOff, n);
 
-            PointProjective p; Init(out p);
+            Init(out PointProjective p);
             ScalarMultBase(n, ref p);
 
             if (0 == CheckPoint(p.x, p.y, p.z))
@@ -1356,7 +1607,25 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             F.Copy(p.x, 0, x, 0);
             F.Copy(p.y, 0, y, 0);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void ScalarMultBaseXY(ReadOnlySpan<byte> k, Span<uint> x, Span<uint> y)
+        {
+            Span<byte> n = stackalloc byte[ScalarBytes];
+            PruneScalar(k, n);
+
+            Init(out PointProjective p);
+            ScalarMultBase(n, ref p);
+
+            if (0 == CheckPoint(p.x, p.y, p.z))
+                throw new InvalidOperationException();
+
+            F.Copy(p.x, x);
+            F.Copy(p.y, y);
         }
+#endif
 
         private static void ScalarMultOrderVar(ref PointProjective p, ref PointProjective r)
         {
@@ -1457,7 +1726,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static void SignPrehash(byte[] sk, int skOff, byte[] ctx, IXof ph, byte[] sig, int sigOff)
         {
             byte[] m = new byte[PrehashSize];
-            if (PrehashSize != ph.DoFinal(m, 0, PrehashSize))
+            if (PrehashSize != ph.OutputFinal(m, 0, PrehashSize))
                 throw new ArgumentException("ph");
 
             byte phflag = 0x01;
@@ -1468,7 +1737,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static void SignPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, IXof ph, byte[] sig, int sigOff)
         {
             byte[] m = new byte[PrehashSize];
-            if (PrehashSize != ph.DoFinal(m, 0, PrehashSize))
+            if (PrehashSize != ph.OutputFinal(m, 0, PrehashSize))
                 throw new ArgumentException("ph");
 
             byte phflag = 0x01;
@@ -1478,7 +1747,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static bool ValidatePublicKeyFull(byte[] pk, int pkOff)
         {
-            PointProjective p; Init(out p);
+            Init(out PointProjective p);
             if (!DecodePointVar(pk, pkOff, false, ref p))
                 return false;
 
@@ -1489,7 +1758,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (IsNeutralElementVar(p.x, p.y, p.z))
                 return false;
 
-            PointProjective r; Init(out r);
+            Init(out PointProjective r);
             ScalarMultOrderVar(ref p, ref r);
 
             F.Normalize(r.x);
@@ -1501,7 +1770,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff)
         {
-            PointProjective p; Init(out p);
+            Init(out PointProjective p);
             return DecodePointVar(pk, pkOff, false, ref p);
         }
 
@@ -1522,7 +1791,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, IXof ph)
         {
             byte[] m = new byte[PrehashSize];
-            if (PrehashSize != ph.DoFinal(m, 0, PrehashSize))
+            if (PrehashSize != ph.OutputFinal(m, 0, PrehashSize))
                 throw new ArgumentException("ph");
 
             byte phflag = 0x01;
diff --git a/crypto/src/math/raw/Bits.cs b/crypto/src/math/raw/Bits.cs
index d344e1672..423151651 100644
--- a/crypto/src/math/raw/Bits.cs
+++ b/crypto/src/math/raw/Bits.cs
@@ -1,28 +1,85 @@
-using System;
+using System.Diagnostics;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
 
 namespace Org.BouncyCastle.Math.Raw
 {
-    internal abstract class Bits
+    internal static class Bits
     {
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         internal static uint BitPermuteStep(uint x, uint m, int s)
         {
+            Debug.Assert((m & (m << s)) == 0U);
+            Debug.Assert((m << s) >> s == m);
+
             uint t = (x ^ (x >> s)) & m;
-            return (t ^ (t << s)) ^ x;
+            return t ^ (t << s) ^ x;
         }
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         internal static ulong BitPermuteStep(ulong x, ulong m, int s)
         {
+            Debug.Assert((m & (m << s)) == 0UL);
+            Debug.Assert((m << s) >> s == m);
+
             ulong t = (x ^ (x >> s)) & m;
-            return (t ^ (t << s)) ^ x;
+            return t ^ (t << s) ^ x;
+        }
+
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+        internal static void BitPermuteStep2(ref uint hi, ref uint lo, uint m, int s)
+        {
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_1_OR_GREATER
+            Debug.Assert(!Unsafe.AreSame(ref hi, ref lo) || (m & (m << s)) == 0U);
+#endif
+            Debug.Assert((m << s) >> s == m);
+
+            uint t = ((lo >> s) ^ hi) & m;
+            lo ^= t << s;
+            hi ^= t;
         }
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+        internal static void BitPermuteStep2(ref ulong hi, ref ulong lo, ulong m, int s)
+        {
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_1_OR_GREATER
+            Debug.Assert(!Unsafe.AreSame(ref hi, ref lo) || (m & (m << s)) == 0UL);
+#endif
+            Debug.Assert((m << s) >> s == m);
+
+            ulong t = ((lo >> s) ^ hi) & m;
+            lo ^= t << s;
+            hi ^= t;
+        }
+
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         internal static uint BitPermuteStepSimple(uint x, uint m, int s)
         {
+            Debug.Assert((m & (m << s)) == 0U);
+            Debug.Assert((m << s) >> s == m);
+
             return ((x & m) << s) | ((x >> s) & m);
         }
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         internal static ulong BitPermuteStepSimple(ulong x, ulong m, int s)
         {
+            Debug.Assert((m & (m << s)) == 0UL);
+            Debug.Assert((m << s) >> s == m);
+
             return ((x & m) << s) | ((x >> s) & m);
         }
     }
diff --git a/crypto/src/math/raw/Interleave.cs b/crypto/src/math/raw/Interleave.cs
index 9ff85c572..02aa79551 100644
--- a/crypto/src/math/raw/Interleave.cs
+++ b/crypto/src/math/raw/Interleave.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 #if NETCOREAPP3_0_OR_GREATER
 using System.Runtime.Intrinsics.X86;
 #endif
@@ -71,25 +72,53 @@ namespace Org.BouncyCastle.Math.Raw
             z[zOff + 1] = (x >> 1) & M64;
         }
 
-        internal static void Expand64To128(ulong[] zs, int zsOff, int zsLen)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void Expand64To128(ulong x, Span<ulong> z)
         {
-            int i = zsLen, zsPos = zsOff + zsLen << 1;
-            while (--i >= 0)
+#if NETCOREAPP3_0_OR_GREATER
+            if (Bmi2.X64.IsSupported)
             {
-                zsPos -= 2;
-                Expand64To128(zs[zsOff + i], zs, zsPos);
+                z[0] = Bmi2.X64.ParallelBitDeposit(x      , 0x5555555555555555UL);
+                z[1] = Bmi2.X64.ParallelBitDeposit(x >> 32, 0x5555555555555555UL);
+                return;
             }
+#endif
+
+            // "shuffle" low half to even bits and high half to odd bits
+            x = Bits.BitPermuteStep(x, 0x00000000FFFF0000UL, 16);
+            x = Bits.BitPermuteStep(x, 0x0000FF000000FF00UL, 8);
+            x = Bits.BitPermuteStep(x, 0x00F000F000F000F0UL, 4);
+            x = Bits.BitPermuteStep(x, 0x0C0C0C0C0C0C0C0CUL, 2);
+            x = Bits.BitPermuteStep(x, 0x2222222222222222UL, 1);
+
+            z[0] = (x     ) & M64;
+            z[1] = (x >> 1) & M64;
         }
+#endif
 
         internal static void Expand64To128(ulong[] xs, int xsOff, int xsLen, ulong[] zs, int zsOff)
         {
-            for (int i = 0; i < xsLen; ++i)
+            int xsPos = xsLen, zsPos = zsOff + (xsLen << 1);
+            while (--xsPos >= 0)
             {
-                Expand64To128(xs[xsOff + i], zs, zsOff);
-                zsOff += 2;
+                zsPos -= 2;
+                Expand64To128(xs[xsOff + xsPos], zs, zsPos);
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void Expand64To128(ReadOnlySpan<ulong> xs, Span<ulong> zs)
+        {
+            int xsPos = xs.Length, zsPos = xs.Length << 1;
+            Debug.Assert(!zs[xsPos..zsPos].Overlaps(xs));
+            while (--xsPos >= 0)
+            {
+                zsPos -= 2;
+                Expand64To128(xs[xsPos], zs[zsPos..]);
+            }
+        }
+#endif
+
         internal static ulong Expand64To128Rev(ulong x, out ulong low)
         {
 #if NETCOREAPP3_0_OR_GREATER
diff --git a/crypto/src/math/raw/Mod.cs b/crypto/src/math/raw/Mod.cs
index d4d1f716d..721134b0c 100644
--- a/crypto/src/math/raw/Mod.cs
+++ b/crypto/src/math/raw/Mod.cs
@@ -12,20 +12,26 @@ namespace Org.BouncyCastle.Math.Raw
      * computation and modular inversion" by Daniel J. Bernstein and Bo-Yin Yang.
      */
 
-    internal abstract class Mod
+    internal static class Mod
     {
-        private static readonly SecureRandom RandomSource = new SecureRandom();
-
         private const int M30 = 0x3FFFFFFF;
         private const ulong M32UL = 0xFFFFFFFFUL;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CheckedModOddInverse(ReadOnlySpan<uint> m, ReadOnlySpan<uint> x, Span<uint> z)
+#else
         public static void CheckedModOddInverse(uint[] m, uint[] x, uint[] z)
+#endif
         {
             if (0 == ModOddInverse(m, x, z))
                 throw new ArithmeticException("Inverse does not exist.");
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CheckedModOddInverseVar(ReadOnlySpan<uint> m, ReadOnlySpan<uint> x, Span<uint> z)
+#else
         public static void CheckedModOddInverseVar(uint[] m, uint[] x, uint[] z)
+#endif
         {
             if (!ModOddInverseVar(m, x, z))
                 throw new ArithmeticException("Inverse does not exist.");
@@ -33,7 +39,7 @@ namespace Org.BouncyCastle.Math.Raw
 
         public static uint Inverse32(uint d)
         {
-            Debug.Assert((d & 1) == 1);
+            Debug.Assert((d & 1U) == 1U);
 
             //int x = d + (((d + 1) & 4) << 1);   // d.x == 1 mod 2**4
             uint x = d;                         // d.x == 1 mod 2**3
@@ -41,12 +47,30 @@ namespace Org.BouncyCastle.Math.Raw
             x *= 2 - d * x;                     // d.x == 1 mod 2**12
             x *= 2 - d * x;                     // d.x == 1 mod 2**24
             x *= 2 - d * x;                     // d.x == 1 mod 2**48
-            Debug.Assert(d * x == 1);
+            Debug.Assert(d * x == 1U);
+            return x;
+        }
+
+        public static ulong Inverse64(ulong d)
+        {
+            Debug.Assert((d & 1UL) == 1UL);
+
+            //ulong x = d + (((d + 1) & 4) << 1);   // d.x == 1 mod 2**4
+            ulong x = d;                            // d.x == 1 mod 2**3
+            x *= 2 - d * x;                         // d.x == 1 mod 2**6
+            x *= 2 - d * x;                         // d.x == 1 mod 2**12
+            x *= 2 - d * x;                         // d.x == 1 mod 2**24
+            x *= 2 - d * x;                         // d.x == 1 mod 2**48
+            x *= 2 - d * x;                         // d.x == 1 mod 2**96
+            Debug.Assert(d * x == 1UL);
             return x;
         }
 
         public static uint ModOddInverse(uint[] m, uint[] x, uint[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ModOddInverse(m.AsSpan(), x.AsSpan(), z.AsSpan());
+#else
             int len32 = m.Length;
             Debug.Assert(len32 > 0);
             Debug.Assert((m[0] & 1) != 0);
@@ -89,13 +113,72 @@ namespace Org.BouncyCastle.Math.Raw
             CNormalize30(len30, signF, D, M);
 
             Decode30(bits, D, 0, z, 0);
-            Debug.Assert(0 != Nat.LessThan(len32, z, m));
+            Debug.Assert(0 != Nat.LessThan(m.Length, z, m));
 
             return (uint)(EqualTo(len30, F, 1) & EqualToZero(len30, G));
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ModOddInverse(ReadOnlySpan<uint> m, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            int len32 = m.Length;
+            Debug.Assert(len32 > 0);
+            Debug.Assert((m[0] & 1) != 0);
+            Debug.Assert(m[len32 - 1] != 0);
+
+            int bits = (len32 << 5) - Integers.NumberOfLeadingZeros((int)m[len32 - 1]);
+            int len30 = (bits + 29) / 30;
+
+            Span<int> alloc = len30 <= 50
+                ? stackalloc int[len30 * 5]
+                : new int[len30 * 5];
+
+            Span<int> t = stackalloc int[4];
+            Span<int> D = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> E = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> F = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> G = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> M = alloc[..len30];
+
+            E[0] = 1;
+            Encode30(bits, x, G);
+            Encode30(bits, m, M);
+            M.CopyTo(F);
+
+            int delta = 0;
+            int m0Inv32 = (int)Inverse32((uint)M[0]);
+            int maxDivsteps = GetMaximumDivsteps(bits);
+
+            for (int divSteps = 0; divSteps < maxDivsteps; divSteps += 30)
+            {
+                delta = Divsteps30(delta, F[0], G[0], t);
+                UpdateDE30(len30, D, E, t, m0Inv32, M);
+                UpdateFG30(len30, F, G, t);
+            }
+
+            int signF = F[len30 - 1] >> 31;
+            CNegate30(len30, signF, F);
+
+            /*
+             * 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, z);
+            Debug.Assert(0 != Nat.LessThan(m.Length, z, m));
+
+            return (uint)(EqualTo(len30, F, 1) & EqualToZero(len30, G));
+        }
+#endif
+
         public static bool ModOddInverseVar(uint[] m, uint[] x, uint[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ModOddInverseVar(m.AsSpan(), x.AsSpan(), z.AsSpan());
+#else
             int len32 = m.Length;
             Debug.Assert(len32 > 0);
             Debug.Assert((m[0] & 1) != 0);
@@ -178,12 +261,108 @@ namespace Org.BouncyCastle.Math.Raw
             Debug.Assert(0 == signD);
 
             Decode30(bits, D, 0, z, 0);
-            Debug.Assert(!Nat.Gte(len32, z, m));
+            Debug.Assert(!Nat.Gte(m.Length, z, m));
+
+            return true;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ModOddInverseVar(ReadOnlySpan<uint> m, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            int len32 = m.Length;
+            Debug.Assert(len32 > 0);
+            Debug.Assert((m[0] & 1) != 0);
+            Debug.Assert(m[len32 - 1] != 0);
+
+            int bits = (len32 << 5) - Integers.NumberOfLeadingZeros((int)m[len32 - 1]);
+            int len30 = (bits + 29) / 30;
+
+            Span<int> alloc = len30 <= 50
+                ? stackalloc int[len30 * 5]
+                : new int[len30 * 5];
+
+            Span<int> t = stackalloc int[4];
+            Span<int> D = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> E = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> F = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> G = alloc[..len30]; alloc = alloc[len30..];
+            Span<int> M = alloc[..len30];
+
+            E[0] = 1;
+            Encode30(bits, x, G);
+            Encode30(bits, m, M);
+            M.CopyTo(F);
+
+            int clzG = Integers.NumberOfLeadingZeros(G[len30 - 1] | 1) - (len30 * 30 + 2 - bits);
+            int eta = -1 - clzG;
+            int lenDE = len30, lenFG = len30;
+            int m0Inv32 = (int)Inverse32((uint)M[0]);
+            int maxDivsteps = GetMaximumDivsteps(bits);
+
+            int divsteps = 0;
+            while (!IsZero(lenFG, G))
+            {
+                if (divsteps >= maxDivsteps)
+                    return false;
+
+                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)
+                {
+                    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);
+            }
+            Debug.Assert(0 == signF);
+
+            if (!IsOne(lenFG, F))
+                return false;
+
+            if (signD < 0)
+            {
+                signD = Add30(lenDE, D, M);
+            }
+            Debug.Assert(0 == signD);
+
+            Decode30(bits, D, z);
+            Debug.Assert(!Nat.Gte(m.Length, z, m));
 
             return true;
         }
+#endif
 
-        public static uint[] Random(uint[] p)
+        public static uint[] Random(SecureRandom random, uint[] p)
         {
             int len = p.Length;
             uint[] s = Nat.Create(len);
@@ -195,10 +374,10 @@ namespace Org.BouncyCastle.Math.Raw
             m |= m >> 8;
             m |= m >> 16;
 
+            byte[] bytes = new byte[len << 2];
             do
             {
-                byte[] bytes = new byte[len << 2];
-                RandomSource.NextBytes(bytes);
+                random.NextBytes(bytes);
                 Pack.BE_To_UInt32(bytes, 0, s);
                 s[len - 1] &= m;
             }
@@ -207,7 +386,41 @@ namespace Org.BouncyCastle.Math.Raw
             return s;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Random(SecureRandom random, ReadOnlySpan<uint> p, Span<uint> z)
+        {
+            int len = p.Length;
+            if (z.Length < len)
+                throw new ArgumentException("insufficient space", nameof(z));
+
+            var s = z[..len];
+
+            uint m = p[len - 1];
+            m |= m >> 1;
+            m |= m >> 2;
+            m |= m >> 4;
+            m |= m >> 8;
+            m |= m >> 16;
+
+            Span<byte> bytes = len <= 256
+                ? stackalloc byte[len << 2]
+                : new byte[len << 2];
+
+            do
+            {
+                random.NextBytes(bytes);
+                Pack.BE_To_UInt32(bytes, s);
+                s[len - 1] &= m;
+            }
+            while (Nat.Gte(len, s, p));
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int Add30(int len30, Span<int> D, ReadOnlySpan<int> M)
+#else
         private static int Add30(int len30, int[] D, int[] M)
+#endif
         {
             Debug.Assert(len30 > 0);
             Debug.Assert(D.Length >= len30);
@@ -224,7 +437,11 @@ namespace Org.BouncyCastle.Math.Raw
             return c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void CNegate30(int len30, int cond, Span<int> D)
+#else
         private static void CNegate30(int len30, int cond, int[] D)
+#endif
         {
             Debug.Assert(len30 > 0);
             Debug.Assert(D.Length >= len30);
@@ -239,7 +456,11 @@ namespace Org.BouncyCastle.Math.Raw
             D[last] = c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void CNormalize30(int len30, int condNegate, Span<int> D, ReadOnlySpan<int> M)
+#else
         private static void CNormalize30(int len30, int condNegate, int[] D, int[] M)
+#endif
         {
             Debug.Assert(len30 > 0);
             Debug.Assert(D.Length >= len30);
@@ -277,6 +498,29 @@ namespace Org.BouncyCastle.Math.Raw
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode30(int bits, ReadOnlySpan<int> x, Span<uint> z)
+        {
+            Debug.Assert(bits > 0);
+
+            int avail = 0;
+            ulong data = 0L;
+
+            int xOff = 0, zOff = 0;
+            while (bits > 0)
+            {
+                while (avail < System.Math.Min(32, bits))
+                {
+                    data |= (ulong)x[xOff++] << avail;
+                    avail += 30;
+                }
+
+                z[zOff++] = (uint)data; data >>= 32;
+                avail -= 32;
+                bits -= 32;
+            }
+        }
+#else
         private static void Decode30(int bits, int[] x, int xOff, uint[] z, int zOff)
         {
             Debug.Assert(bits > 0);
@@ -297,8 +541,13 @@ namespace Org.BouncyCastle.Math.Raw
                 bits -= 32;
             }
         }
+#endif
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int Divsteps30(int delta, int f0, int g0, Span<int> t)
+#else
         private static int Divsteps30(int delta, int f0, int g0, int[] t)
+#endif
         {
             int u = 1 << 30, v = 0, q = 0, r = 1 << 30;
             int f = f0, g = g0;
@@ -340,7 +589,11 @@ namespace Org.BouncyCastle.Math.Raw
             return delta;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int Divsteps30Var(int eta, int f0, int g0, Span<int> t)
+#else
         private static int Divsteps30Var(int eta, int f0, int g0, int[] t)
+#endif
         {
             int u = 1, v = 0, q = 0, r = 1;
             int f = f0, g = g0, m, w, x, y, z;
@@ -403,6 +656,29 @@ namespace Org.BouncyCastle.Math.Raw
             return eta;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Encode30(int bits, ReadOnlySpan<uint> x, Span<int> z)
+        {
+            Debug.Assert(bits > 0);
+
+            int avail = 0;
+            ulong data = 0UL;
+
+            int xOff = 0, zOff = 0;
+            while (bits > 0)
+            {
+                if (avail < System.Math.Min(30, bits))
+                {
+                    data |= (x[xOff++] & M32UL) << avail;
+                    avail += 32;
+                }
+
+                z[zOff++] = (int)data & M30; data >>= 30;
+                avail -= 30;
+                bits -= 30;
+            }
+        }
+#else
         private static void Encode30(int bits, uint[] x, int xOff, int[] z, int zOff)
         {
             Debug.Assert(bits > 0);
@@ -423,8 +699,13 @@ namespace Org.BouncyCastle.Math.Raw
                 bits -= 30;
             }
         }
+#endif
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int EqualTo(int len, ReadOnlySpan<int> x, int y)
+#else
         private static int EqualTo(int len, int[] x, int y)
+#endif
         {
             int d = x[0] ^ y;
             for (int i = 1; i < len; ++i)
@@ -435,7 +716,11 @@ namespace Org.BouncyCastle.Math.Raw
             return (d - 1) >> 31;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int EqualToZero(int len, ReadOnlySpan<int> x)
+#else
         private static int EqualToZero(int len, int[] x)
+#endif
         {
             int d = 0;
             for (int i = 0; i < len; ++i)
@@ -451,7 +736,11 @@ namespace Org.BouncyCastle.Math.Raw
             return (49 * bits + (bits < 46 ? 80 : 47)) / 17;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static bool IsOne(int len, ReadOnlySpan<int> x)
+#else
         private static bool IsOne(int len, int[] x)
+#endif
         {
             if (x[0] != 1)
             {
@@ -467,7 +756,11 @@ namespace Org.BouncyCastle.Math.Raw
             return true;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static bool IsZero(int len, ReadOnlySpan<int> x)
+#else
         private static bool IsZero(int len, int[] x)
+#endif
         {
             if (x[0] != 0)
             {
@@ -483,7 +776,11 @@ namespace Org.BouncyCastle.Math.Raw
             return true;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int Negate30(int len30, Span<int> D)
+#else
         private static int Negate30(int len30, int[] D)
+#endif
         {
             Debug.Assert(len30 > 0);
             Debug.Assert(D.Length >= len30);
@@ -499,7 +796,12 @@ namespace Org.BouncyCastle.Math.Raw
             return c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void UpdateDE30(int len30, Span<int> D, Span<int> E, ReadOnlySpan<int> t, int m0Inv32,
+            ReadOnlySpan<int> M)
+#else
         private static void UpdateDE30(int len30, int[] D, int[] E, int[] t, int m0Inv32, int[] M)
+#endif
         {
             Debug.Assert(len30 > 0);
             Debug.Assert(D.Length >= len30);
@@ -563,7 +865,11 @@ namespace Org.BouncyCastle.Math.Raw
             E[len30 - 1] = (int)ce;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void UpdateFG30(int len30, Span<int> F, Span<int> G, ReadOnlySpan<int> t)
+#else
         private static void UpdateFG30(int len30, int[] F, int[] G, int[] t)
+#endif
         {
             Debug.Assert(len30 > 0);
             Debug.Assert(F.Length >= len30);
@@ -601,4 +907,4 @@ namespace Org.BouncyCastle.Math.Raw
             G[len30 - 1] = (int)cg;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/math/raw/Nat.cs b/crypto/src/math/raw/Nat.cs
index 8e5b7a04c..3bc983430 100644
--- a/crypto/src/math/raw/Nat.cs
+++ b/crypto/src/math/raw/Nat.cs
@@ -21,6 +21,20 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint Add(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            ulong c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)x[i] + y[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static uint Add33At(int len, uint x, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 2));
@@ -45,6 +59,20 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : IncAt(len, z, zOff, zPos + 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint Add33At(int len, uint x, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 2));
+            ulong c = (ulong)z[zPos + 0] + x;
+            z[zPos + 0] = (uint)c;
+            c >>= 32;
+            c += (ulong)z[zPos + 1] + 1;
+            z[zPos + 1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, zPos + 2);
+        }
+#endif
+
         public static uint Add33To(int len, uint x, uint[] z)
         {
             ulong c = (ulong)z[0] + x;
@@ -67,6 +95,19 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : IncAt(len, z, zOff, 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint Add33To(int len, uint x, Span<uint> z)
+        {
+            ulong c = (ulong)z[0] + x;
+            z[0] = (uint)c;
+            c >>= 32;
+            c += (ulong)z[1] + 1;
+            z[1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, 2);
+        }
+#endif
+
         public static uint AddBothTo(int len, uint[] x, uint[] y, uint[] z)
         {
             ulong c = 0;
@@ -91,13 +132,27 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddBothTo(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            ulong c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)x[i] + y[i] + z[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static uint AddDWordAt(int len, ulong x, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 2));
-            ulong c = (ulong)z[zPos + 0] + (x & M);
+            ulong c = z[zPos + 0] + (x & M);
             z[zPos + 0] = (uint)c;
             c >>= 32;
-            c += (ulong)z[zPos + 1] + (x >> 32);
+            c += z[zPos + 1] + (x >> 32);
             z[zPos + 1] = (uint)c;
             c >>= 32;
             return c == 0 ? 0 : IncAt(len, z, zPos + 2);
@@ -106,15 +161,29 @@ namespace Org.BouncyCastle.Math.Raw
         public static uint AddDWordAt(int len, ulong x, uint[] z, int zOff, int zPos)
         {
             Debug.Assert(zPos <= (len - 2));
-            ulong c = (ulong)z[zOff + zPos] + (x & M);
+            ulong c = z[zOff + zPos] + (x & M);
             z[zOff + zPos] = (uint)c;
             c >>= 32;
-            c += (ulong)z[zOff + zPos + 1] + (x >> 32);
+            c += z[zOff + zPos + 1] + (x >> 32);
             z[zOff + zPos + 1] = (uint)c;
             c >>= 32;
             return c == 0 ? 0 : IncAt(len, z, zOff, zPos + 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddDWordAt(int len, ulong x, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 2));
+            ulong c = z[zPos + 0] + (x & M);
+            z[zPos + 0] = (uint)c;
+            c >>= 32;
+            c += z[zPos + 1] + (x >> 32);
+            z[zPos + 1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, zPos + 2);
+        }
+#endif
+
         public static uint AddDWordTo(int len, ulong x, uint[] z)
         {
             ulong c = (ulong)z[0] + (x & M);
@@ -137,6 +206,19 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : IncAt(len, z, zOff, 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddDWordTo(int len, ulong x, Span<uint> z)
+        {
+            ulong c = z[0] + (x & M);
+            z[0] = (uint)c;
+            c >>= 32;
+            c += z[1] + (x >> 32);
+            z[1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, 2);
+        }
+#endif
+
         public static uint AddTo(int len, uint[] x, uint[] z)
         {
             ulong c = 0;
@@ -161,6 +243,20 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddTo(int len, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            ulong c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)x[i] + z[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static uint AddTo(int len, uint[] x, int xOff, uint[] z, int zOff, uint cIn)
         {
             ulong c = cIn;
@@ -173,6 +269,20 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddTo(int len, ReadOnlySpan<uint> x, Span<uint> z, uint cIn)
+        {
+            ulong c = cIn;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)x[i] + z[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static uint AddToEachOther(int len, uint[] u, int uOff, uint[] v, int vOff)
         {
             ulong c = 0;
@@ -186,6 +296,21 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddToEachOther(int len, Span<uint> u, Span<uint> v)
+        {
+            ulong c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)u[i] + v[i];
+                u[i] = (uint)c;
+                v[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static uint AddWordAt(int len, uint x, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 1));
@@ -204,6 +329,17 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : IncAt(len, z, zOff, zPos + 1);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddWordAt(int len, uint x, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 1));
+            ulong c = (ulong)x + z[zPos];
+            z[zPos] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, zPos + 1);
+        }
+#endif
+
         public static uint AddWordTo(int len, uint x, uint[] z)
         {
             ulong c = (ulong)x + z[0];
@@ -220,6 +356,16 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : IncAt(len, z, zOff, 1);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint AddWordTo(int len, uint x, Span<uint> z)
+        {
+            ulong c = (ulong)x + z[0];
+            z[0] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, 1);
+        }
+#endif
+
         public static uint CAdd(int len, int mask, uint[] x, uint[] y, uint[] z)
         {
             uint MASK = (uint)-(mask & 1);
@@ -234,6 +380,22 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint CAdd(int len, int mask, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            uint MASK = (uint)-(mask & 1);
+
+            ulong c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)x[i] + (y[i] & MASK);
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static void CMov(int len, int mask, uint[] x, int xOff, uint[] z, int zOff)
         {
             uint MASK = (uint)-(mask & 1);
@@ -241,7 +403,7 @@ namespace Org.BouncyCastle.Math.Raw
             for (int i = 0; i < len; ++i)
             {
                 uint z_i = z[zOff + i], diff = z_i ^ x[xOff + i];
-                z_i ^= (diff & MASK);
+                z_i ^= diff & MASK;
                 z[zOff + i] = z_i;
             }
 
@@ -256,27 +418,29 @@ namespace Org.BouncyCastle.Math.Raw
             //}
         }
 
-        public static void CMov(int len, int mask, int[] x, int xOff, int[] z, int zOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CMov(int len, int mask, ReadOnlySpan<uint> x, Span<uint> z)
         {
-            mask = -(mask & 1);
+            uint MASK = (uint)-(mask & 1);
 
             for (int i = 0; i < len; ++i)
             {
-                int z_i = z[zOff + i], diff = z_i ^ x[xOff + i];
-                z_i ^= (diff & mask);
-                z[zOff + i] = z_i;
+                uint z_i = z[i], diff = z_i ^ x[i];
+                z_i ^= diff & MASK;
+                z[i] = z_i;
             }
 
-            //int half = 0x55555555, rest = half << (-mask);
+            //uint half = 0x55555555U, rest = half << (-(int)MASK);
 
             //for (int i = 0; i < len; ++i)
             //{
-            //    int z_i = z[zOff + i], diff = z_i ^ x[xOff + i];
+            //    uint z_i = z[i], diff = z_i ^ x[i];
             //    z_i ^= (diff & half);
             //    z_i ^= (diff & rest);
-            //    z[zOff + i] = z_i;
+            //    z[i] = z_i;
             //}
         }
+#endif
 
         public static int Compare(int len, uint[] x, uint[] y)
         {
@@ -306,10 +470,21 @@ namespace Org.BouncyCastle.Math.Raw
             return 0;
         }
 
-        public static void Copy(int len, uint[] x, uint[] z)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int Compare(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y)
         {
-            Array.Copy(x, 0, z, 0, len);
+            for (int i = len - 1; i >= 0; --i)
+            {
+                uint x_i = x[i];
+                uint y_i = y[i];
+                if (x_i < y_i)
+                    return -1;
+                if (x_i > y_i)
+                    return 1;
+            }
+            return 0;
         }
+#endif
 
         public static uint[] Copy(int len, uint[] x)
         {
@@ -318,11 +493,23 @@ namespace Org.BouncyCastle.Math.Raw
             return z;
         }
 
+        public static void Copy(int len, uint[] x, uint[] z)
+        {
+            Array.Copy(x, 0, z, 0, len);
+        }
+
         public static void Copy(int len, uint[] x, int xOff, uint[] z, int zOff)
         {
             Array.Copy(x, xOff, z, zOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Copy(int len, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            x[..len].CopyTo(z);
+        }
+#endif
+
         public static ulong[] Copy64(int len, ulong[] x)
         {
             ulong[] z = new ulong[len];
@@ -340,6 +527,13 @@ namespace Org.BouncyCastle.Math.Raw
             Array.Copy(x, xOff, z, zOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Copy64(int len, ReadOnlySpan<ulong> x, Span<ulong> z)
+        {
+            x[..len].CopyTo(z);
+        }
+#endif
+
         public static uint[] Create(int len)
         {
             return new uint[len];
@@ -356,7 +550,7 @@ namespace Org.BouncyCastle.Math.Raw
             long c = 0;
             for (int i = 0; i < len; ++i)
             {
-                c += (long)x[i] - (y[i] & MASK);
+                c += x[i] - (y[i] & MASK);
                 z[i] = (uint)c;
                 c >>= 32;
             }
@@ -369,26 +563,73 @@ namespace Org.BouncyCastle.Math.Raw
             long c = 0;
             for (int i = 0; i < len; ++i)
             {
-                c += (long)x[xOff + i] - (y[yOff + i] & MASK);
+                c += x[xOff + i] - (y[yOff + i] & MASK);
                 z[zOff + i] = (uint)c;
                 c >>= 32;
             }
             return (int)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int CSub(int len, int mask, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            long MASK = (uint)-(mask & 1);
+            long c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += x[i] - (y[i] & MASK);
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (int)c;
+        }
+#endif
+
         public static int Dec(int len, uint[] z)
         {
             for (int i = 0; i < len; ++i)
             {
                 if (--z[i] != uint.MaxValue)
+                    return 0;
+            }
+            return -1;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int Dec(int len, Span<uint> z)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                if (--z[i] != uint.MaxValue)
+                    return 0;
+            }
+            return -1;
+        }
+#endif
+
+        public static int Dec(int len, uint[] x, uint[] z)
+        {
+            int i = 0;
+            while (i < len)
+            {
+                uint c = x[i] - 1;
+                z[i] = c;
+                ++i;
+                if (c != uint.MaxValue)
                 {
+                    while (i < len)
+                    {
+                        z[i] = x[i];
+                        ++i;
+                    }
                     return 0;
                 }
             }
             return -1;
         }
 
-        public static int Dec(int len, uint[] x, uint[] z)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int Dec(int len, ReadOnlySpan<uint> x, Span<uint> z)
         {
             int i = 0;
             while (i < len)
@@ -408,6 +649,7 @@ namespace Org.BouncyCastle.Math.Raw
             }
             return -1;
         }
+#endif
 
         public static int DecAt(int len, uint[] z, int zPos)
         {
@@ -415,9 +657,7 @@ namespace Org.BouncyCastle.Math.Raw
             for (int i = zPos; i < len; ++i)
             {
                 if (--z[i] != uint.MaxValue)
-                {
                     return 0;
-                }
             }
             return -1;
         }
@@ -428,25 +668,46 @@ namespace Org.BouncyCastle.Math.Raw
             for (int i = zPos; i < len; ++i)
             {
                 if (--z[zOff + i] != uint.MaxValue)
-                {
                     return 0;
-                }
             }
             return -1;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int DecAt(int len, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= len);
+            for (int i = zPos; i < len; ++i)
+            {
+                if (--z[i] != uint.MaxValue)
+                    return 0;
+            }
+            return -1;
+        }
+#endif
+
         public static bool Eq(int len, uint[] x, uint[] y)
         {
             for (int i = len - 1; i >= 0; --i)
             {
                 if (x[i] != y[i])
-                {
                     return false;
-                }
             }
             return true;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool Eq(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y)
+        {
+            for (int i = len - 1; i >= 0; --i)
+            {
+                if (x[i] != y[i])
+                    return false;
+            }
+            return true;
+        }
+#endif
+
         public static uint EqualTo(int len, uint[] x, uint y)
         {
             uint d = x[0] ^ y;
@@ -469,6 +730,19 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)(((int)d - 1) >> 31);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint EqualTo(int len, ReadOnlySpan<uint> x, uint y)
+        {
+            uint d = x[0] ^ y;
+            for (int i = 1; i < len; ++i)
+            {
+                d |= x[i];
+            }
+            d = (d >> 1) | (d & 1);
+            return (uint)(((int)d - 1) >> 31);
+        }
+#endif
+
         public static uint EqualTo(int len, uint[] x, uint[] y)
         {
             uint d = 0;
@@ -491,6 +765,19 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)(((int)d - 1) >> 31);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint EqualTo(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y)
+        {
+            uint d = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                d |= x[i] ^ y[i];
+            }
+            d = (d >> 1) | (d & 1);
+            return (uint)(((int)d - 1) >> 31);
+        }
+#endif
+
         public static uint EqualToZero(int len, uint[] x)
         {
             uint d = 0;
@@ -513,15 +800,26 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)(((int)d - 1) >> 31);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint EqualToZero(int len, ReadOnlySpan<uint> x)
+        {
+            uint d = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                d |= x[i];
+            }
+            d = (d >> 1) | (d & 1);
+            return (uint)(((int)d - 1) >> 31);
+        }
+#endif
+
         public static uint[] FromBigInteger(int bits, BigInteger x)
         {
-            if (bits < 1)
-                throw new ArgumentException();
+            int len = GetLengthForBits(bits);
+
             if (x.SignValue < 0 || x.BitLength > bits)
                 throw new ArgumentException();
 
-            int len = (bits + 31) >> 5;
-            Debug.Assert(len > 0);
             uint[] z = Create(len);
 
             // NOTE: Use a fixed number of loop iterations
@@ -534,15 +832,33 @@ namespace Org.BouncyCastle.Math.Raw
             return z;
         }
 
-        public static ulong[] FromBigInteger64(int bits, BigInteger x)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void FromBigInteger(int bits, BigInteger x, Span<uint> z)
         {
-            if (bits < 1)
+            int len = GetLengthForBits(bits);
+
+            if (x.SignValue < 0 || x.BitLength > bits)
+                throw new ArgumentException();
+            if (z.Length < len)
                 throw new ArgumentException();
+
+            // NOTE: Use a fixed number of loop iterations
+            z[0] = (uint)x.IntValue;
+            for (int i = 1; i < len; ++i)
+            {
+                x = x.ShiftRight(32);
+                z[i] = (uint)x.IntValue;
+            }
+        }
+#endif
+
+        public static ulong[] FromBigInteger64(int bits, BigInteger x)
+        {
+            int len = GetLengthForBits64(bits);
+
             if (x.SignValue < 0 || x.BitLength > bits)
                 throw new ArgumentException();
 
-            int len = (bits + 63) >> 6;
-            Debug.Assert(len > 0);
             ulong[] z = Create64(len);
 
             // NOTE: Use a fixed number of loop iterations
@@ -555,21 +871,70 @@ namespace Org.BouncyCastle.Math.Raw
             return z;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void FromBigInteger64(int bits, BigInteger x, Span<ulong> z)
+        {
+            int len = GetLengthForBits64(bits);
+
+            if (x.SignValue < 0 || x.BitLength > bits)
+                throw new ArgumentException();
+            if (z.Length < len)
+                throw new ArgumentException();
+
+            // NOTE: Use a fixed number of loop iterations
+            z[0] = (ulong)x.LongValue;
+            for (int i = 1; i < len; ++i)
+            {
+                x = x.ShiftRight(64);
+                z[i] = (ulong)x.LongValue;
+            }
+        }
+#endif
+
         public static uint GetBit(uint[] x, int bit)
         {
             if (bit == 0)
-            {
                 return x[0] & 1;
-            }
+
             int w = bit >> 5;
             if (w < 0 || w >= x.Length)
-            {
                 return 0;
-            }
+
             int b = bit & 31;
             return (x[w] >> b) & 1;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint GetBit(ReadOnlySpan<uint> x, int bit)
+        {
+            if (bit == 0)
+                return x[0] & 1;
+
+            int w = bit >> 5;
+            if (w < 0 || w >= x.Length)
+                return 0;
+
+            int b = bit & 31;
+            return (x[w] >> b) & 1;
+        }
+#endif
+
+        public static int GetLengthForBits(int bits)
+        {
+            if (bits < 1)
+                throw new ArgumentException();
+
+            return (int)(((uint)bits + 31) >> 5);
+        }
+
+        public static int GetLengthForBits64(int bits)
+        {
+            if (bits < 1)
+                throw new ArgumentException();
+
+            return (int)(((uint)bits + 63) >> 6);
+        }
+
         public static bool Gte(int len, uint[] x, uint[] y)
         {
             for (int i = len - 1; i >= 0; --i)
@@ -583,19 +948,66 @@ namespace Org.BouncyCastle.Math.Raw
             return true;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool Gte(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y)
+        {
+            for (int i = len - 1; i >= 0; --i)
+            {
+                uint x_i = x[i], y_i = y[i];
+                if (x_i < y_i)
+                    return false;
+                if (x_i > y_i)
+                    return true;
+            }
+            return true;
+        }
+#endif
+
         public static uint Inc(int len, uint[] z)
         {
             for (int i = 0; i < len; ++i)
             {
                 if (++z[i] != uint.MinValue)
+                    return 0;
+            }
+            return 1;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint Inc(int len, Span<uint> z)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                if (++z[i] != uint.MinValue)
+                    return 0;
+            }
+            return 1;
+        }
+#endif
+
+        public static uint Inc(int len, uint[] x, uint[] z)
+        {
+            int i = 0;
+            while (i < len)
+            {
+                uint c = x[i] + 1;
+                z[i] = c;
+                ++i;
+                if (c != 0)
                 {
+                    while (i < len)
+                    {
+                        z[i] = x[i];
+                        ++i;
+                    }
                     return 0;
                 }
             }
             return 1;
         }
 
-        public static uint Inc(int len, uint[] x, uint[] z)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint Inc(int len, ReadOnlySpan<uint> x, Span<uint> z)
         {
             int i = 0;
             while (i < len)
@@ -615,6 +1027,7 @@ namespace Org.BouncyCastle.Math.Raw
             }
             return 1;
         }
+#endif
 
         public static uint IncAt(int len, uint[] z, int zPos)
         {
@@ -622,9 +1035,7 @@ namespace Org.BouncyCastle.Math.Raw
             for (int i = zPos; i < len; ++i)
             {
                 if (++z[i] != uint.MinValue)
-                {
                     return 0;
-                }
             }
             return 1;
         }
@@ -635,44 +1046,79 @@ namespace Org.BouncyCastle.Math.Raw
             for (int i = zPos; i < len; ++i)
             {
                 if (++z[zOff + i] != uint.MinValue)
-                {
                     return 0;
-                }
             }
             return 1;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint IncAt(int len, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= len);
+            for (int i = zPos; i < len; ++i)
+            {
+                if (++z[i] != uint.MinValue)
+                    return 0;
+            }
+            return 1;
+        }
+#endif
+
         public static bool IsOne(int len, uint[] x)
         {
             if (x[0] != 1)
-            {
                 return false;
+
+            for (int i = 1; i < len; ++i)
+            {
+                if (x[i] != 0)
+                    return false;
             }
+            return true;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool IsOne(int len, ReadOnlySpan<uint> x)
+        {
+            if (x[0] != 1)
+                return false;
+
             for (int i = 1; i < len; ++i)
             {
                 if (x[i] != 0)
-                {
                     return false;
-                }
             }
             return true;
         }
+#endif
 
         public static bool IsZero(int len, uint[] x)
         {
             if (x[0] != 0)
-            {
                 return false;
+
+            for (int i = 1; i < len; ++i)
+            {
+                if (x[i] != 0)
+                    return false;
             }
+            return true;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool IsZero(int len, ReadOnlySpan<uint> x)
+        {
+            if (x[0] != 0)
+                return false;
+
             for (int i = 1; i < len; ++i)
             {
                 if (x[i] != 0)
-                {
                     return false;
-                }
             }
             return true;
         }
+#endif
 
         public static int LessThan(int len, uint[] x, uint[] y)
         {
@@ -698,6 +1144,20 @@ namespace Org.BouncyCastle.Math.Raw
             return (int)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int LessThan(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y)
+        {
+            long c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (long)x[i] - y[i];
+                c >>= 32;
+            }
+            Debug.Assert(c == 0L || c == -1L);
+            return (int)c;
+        }
+#endif
+
         public static void Mul(int len, uint[] x, uint[] y, uint[] zz)
         {
             zz[len] = MulWord(len, x[0], y, zz);
@@ -718,6 +1178,18 @@ namespace Org.BouncyCastle.Math.Raw
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Mul(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> zz)
+        {
+            zz[len] = MulWord(len, x[0], y, zz);
+
+            for (int i = 1; i < len; ++i)
+            {
+                zz[i + len] = MulWordAddTo(len, x[i], y, zz[i..]);
+            }
+        }
+#endif
+
         public static void Mul(uint[] x, int xOff, int xLen, uint[] y, int yOff, int yLen, uint[] zz, int zzOff)
         {
             zz[zzOff + yLen] = MulWord(yLen, x[xOff], y, yOff, zz, zzOff);
@@ -728,6 +1200,19 @@ namespace Org.BouncyCastle.Math.Raw
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Mul(ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> zz)
+        {
+            int xLen = x.Length, yLen = y.Length;
+            zz[yLen] = MulWord(yLen, x[0], y, zz);
+
+            for (int i = 1; i < xLen; ++i)
+            {
+                zz[i + yLen] = MulWordAddTo(yLen, x[i], y, zz[i..]);
+            }
+        }
+#endif
+
         public static uint MulAddTo(int len, uint[] x, uint[] y, uint[] zz)
         {
             ulong zc = 0;
@@ -755,9 +1240,24 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)zc;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint MulAddTo(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> zz)
+        {
+            ulong zc = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                zc += MulWordAddTo(len, x[i], y, zz[i..]) & M;
+                zc += zz[i + len] & M;
+                zz[i + len] = (uint)zc;
+                zc >>= 32;
+            }
+            return (uint)zc;
+        }
+#endif
+
         public static uint Mul31BothAdd(int len, uint a, uint[] x, uint b, uint[] y, uint[] z, int zOff)
         {
-            ulong c = 0, aVal = (ulong)a, bVal = (ulong)b;
+            ulong c = 0, aVal = a, bVal = b;
             int i = 0;
             do
             {
@@ -769,9 +1269,26 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint Mul31BothAdd(int len, uint a, ReadOnlySpan<uint> x, uint b, ReadOnlySpan<uint> y,
+            Span<uint> z)
+        {
+            ulong c = 0, aVal = a, bVal = b;
+            int i = 0;
+            do
+            {
+                c += aVal * x[i] + bVal * y[i] + z[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            while (++i < len);
+            return (uint)c;
+        }
+#endif
+
         public static uint MulWord(int len, uint x, uint[] y, uint[] z)
         {
-            ulong c = 0, xVal = (ulong)x;
+            ulong c = 0, xVal = x;
             int i = 0;
             do
             {
@@ -785,7 +1302,7 @@ namespace Org.BouncyCastle.Math.Raw
 
         public static uint MulWord(int len, uint x, uint[] y, int yOff, uint[] z, int zOff)
         {
-            ulong c = 0, xVal = (ulong)x;
+            ulong c = 0, xVal = x;
             int i = 0;
             do
             {
@@ -797,9 +1314,25 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint MulWord(int len, uint x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            ulong c = 0, xVal = x;
+            int i = 0;
+            do
+            {
+                c += xVal * y[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            while (++i < len);
+            return (uint)c;
+        }
+#endif
+
         public static uint MulWordAddTo(int len, uint x, uint[] y, int yOff, uint[] z, int zOff)
         {
-            ulong c = 0, xVal = (ulong)x;
+            ulong c = 0, xVal = x;
             int i = 0;
             do
             {
@@ -811,21 +1344,55 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint MulWordAddTo(int len, uint x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            ulong c = 0, xVal = x;
+            int i = 0;
+            do
+            {
+                c += xVal * y[i] + z[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            while (++i < len);
+            return (uint)c;
+        }
+#endif
+
         public static uint MulWordDwordAddAt(int len, uint x, ulong y, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 3));
-            ulong c = 0, xVal = (ulong)x;
+            ulong c = 0, xVal = x;
+            c += xVal * (uint)y + z[zPos + 0];
+            z[zPos + 0] = (uint)c;
+            c >>= 32;
+            c += xVal * (y >> 32) + z[zPos + 1];
+            z[zPos + 1] = (uint)c;
+            c >>= 32;
+            c += z[zPos + 2];
+            z[zPos + 2] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : IncAt(len, z, zPos + 3);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint MulWordDwordAddAt(int len, uint x, ulong y, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 3));
+            ulong c = 0, xVal = x;
             c += xVal * (uint)y + z[zPos + 0];
             z[zPos + 0] = (uint)c;
             c >>= 32;
             c += xVal * (y >> 32) + z[zPos + 1];
             z[zPos + 1] = (uint)c;
             c >>= 32;
-            c += (ulong)z[zPos + 2];
+            c += z[zPos + 2];
             z[zPos + 2] = (uint)c;
             c >>= 32;
             return c == 0 ? 0 : IncAt(len, z, zPos + 3);
         }
+#endif
 
         public static uint ShiftDownBit(int len, uint[] z, uint c)
         {
@@ -851,6 +1418,20 @@ namespace Org.BouncyCastle.Math.Raw
             return c << 31;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftDownBit(int len, Span<uint> z, uint c)
+        {
+            int i = len;
+            while (--i >= 0)
+            {
+                uint next = z[i];
+                z[i] = (next >> 1) | (c << 31);
+                c = next;
+            }
+            return c << 31;
+        }
+#endif
+
         public static uint ShiftDownBit(int len, uint[] x, uint c, uint[] z)
         {
             int i = len;
@@ -875,6 +1456,20 @@ namespace Org.BouncyCastle.Math.Raw
             return c << 31;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftDownBit(int len, ReadOnlySpan<uint> x, uint c, Span<uint> z)
+        {
+            int i = len;
+            while (--i >= 0)
+            {
+                uint next = x[i];
+                z[i] = (next >> 1) | (c << 31);
+                c = next;
+            }
+            return c << 31;
+        }
+#endif
+
         public static uint ShiftDownBits(int len, uint[] z, int bits, uint c)
         {
             Debug.Assert(bits > 0 && bits < 32);
@@ -901,6 +1496,21 @@ namespace Org.BouncyCastle.Math.Raw
             return c << -bits;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftDownBits(int len, Span<uint> z, int bits, uint c)
+        {
+            Debug.Assert(bits > 0 && bits < 32);
+            int i = len;
+            while (--i >= 0)
+            {
+                uint next = z[i];
+                z[i] = (next >> bits) | (c << -bits);
+                c = next;
+            }
+            return c << -bits;
+        }
+#endif
+
         public static uint ShiftDownBits(int len, uint[] x, int bits, uint c, uint[] z)
         {
             Debug.Assert(bits > 0 && bits < 32);
@@ -927,6 +1537,21 @@ namespace Org.BouncyCastle.Math.Raw
             return c << -bits;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftDownBits(int len, ReadOnlySpan<uint> x, int bits, uint c, Span<uint> z)
+        {
+            Debug.Assert(bits > 0 && bits < 32);
+            int i = len;
+            while (--i >= 0)
+            {
+                uint next = x[i];
+                z[i] = (next >> bits) | (c << -bits);
+                c = next;
+            }
+            return c << -bits;
+        }
+#endif
+
         public static uint ShiftDownWord(int len, uint[] z, uint c)
         {
             int i = len;
@@ -939,133 +1564,648 @@ namespace Org.BouncyCastle.Math.Raw
             return c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftDownWord(int len, Span<uint> z, uint c)
+        {
+            int i = len;
+            while (--i >= 0)
+            {
+                uint next = z[i];
+                z[i] = c;
+                c = next;
+            }
+            return c;
+        }
+#endif
+
         public static uint ShiftUpBit(int len, uint[] z, uint c)
         {
-            for (int i = 0; i < len; ++i)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBit(len, z.AsSpan(0, len), c);
+#else
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = z[i + 0];
+                uint next1 = z[i + 1];
+                uint next2 = z[i + 2];
+                uint next3 = z[i + 3];
+                z[i + 0] = (next0 << 1) | (c     >> 31);
+                z[i + 1] = (next1 << 1) | (next0 >> 31);
+                z[i + 2] = (next2 << 1) | (next1 >> 31);
+                z[i + 3] = (next3 << 1) | (next2 >> 31);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = z[i];
                 z[i] = (next << 1) | (c >> 31);
                 c = next;
+                ++i;
             }
             return c >> 31;
+#endif
         }
 
         public static uint ShiftUpBit(int len, uint[] z, int zOff, uint c)
         {
-            for (int i = 0; i < len; ++i)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBit(len, z.AsSpan(zOff, len), c);
+#else
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = z[zOff + i + 0];
+                uint next1 = z[zOff + i + 1];
+                uint next2 = z[zOff + i + 2];
+                uint next3 = z[zOff + i + 3];
+                z[zOff + i + 0] = (next0 << 1) | (c     >> 31);
+                z[zOff + i + 1] = (next1 << 1) | (next0 >> 31);
+                z[zOff + i + 2] = (next2 << 1) | (next1 >> 31);
+                z[zOff + i + 3] = (next3 << 1) | (next2 >> 31);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = z[zOff + i];
                 z[zOff + i] = (next << 1) | (c >> 31);
                 c = next;
+                ++i;
+            }
+            return c >> 31;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftUpBit(int len, Span<uint> z, uint c)
+        {
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = z[i + 0];
+                uint next1 = z[i + 1];
+                uint next2 = z[i + 2];
+                uint next3 = z[i + 3];
+                z[i + 0] = (next0 << 1) | (c     >> 31);
+                z[i + 1] = (next1 << 1) | (next0 >> 31);
+                z[i + 2] = (next2 << 1) | (next1 >> 31);
+                z[i + 3] = (next3 << 1) | (next2 >> 31);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                uint next = z[i];
+                z[i] = (next << 1) | (c >> 31);
+                c = next;
+                ++i;
             }
             return c >> 31;
         }
+#endif
 
         public static uint ShiftUpBit(int len, uint[] x, uint c, uint[] z)
         {
-            for (int i = 0; i < len; ++i)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBit(len, x.AsSpan(0, len), c, z.AsSpan(0, len));
+#else
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = x[i + 0];
+                uint next1 = x[i + 1];
+                uint next2 = x[i + 2];
+                uint next3 = x[i + 3];
+                z[i + 0] = (next0 << 1) | (c     >> 31);
+                z[i + 1] = (next1 << 1) | (next0 >> 31);
+                z[i + 2] = (next2 << 1) | (next1 >> 31);
+                z[i + 3] = (next3 << 1) | (next2 >> 31);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = x[i];
                 z[i] = (next << 1) | (c >> 31);
                 c = next;
+                ++i;
             }
             return c >> 31;
+#endif
         }
 
         public static uint ShiftUpBit(int len, uint[] x, int xOff, uint c, uint[] z, int zOff)
         {
-            for (int i = 0; i < len; ++i)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBit(len, x.AsSpan(xOff, len), c, z.AsSpan(zOff, len));
+#else
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = x[xOff + i + 0];
+                uint next1 = x[xOff + i + 1];
+                uint next2 = x[xOff + i + 2];
+                uint next3 = x[xOff + i + 3];
+                z[zOff + i + 0] = (next0 << 1) | (c     >> 31);
+                z[zOff + i + 1] = (next1 << 1) | (next0 >> 31);
+                z[zOff + i + 2] = (next2 << 1) | (next1 >> 31);
+                z[zOff + i + 3] = (next3 << 1) | (next2 >> 31);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = x[xOff + i];
                 z[zOff + i] = (next << 1) | (c >> 31);
                 c = next;
+                ++i;
             }
             return c >> 31;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftUpBit(int len, ReadOnlySpan<uint> x, uint c, Span<uint> z)
+        {
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = x[i + 0];
+                uint next1 = x[i + 1];
+                uint next2 = x[i + 2];
+                uint next3 = x[i + 3];
+                z[i + 0] = (next0 << 1) | (c     >> 31);
+                z[i + 1] = (next1 << 1) | (next0 >> 31);
+                z[i + 2] = (next2 << 1) | (next1 >> 31);
+                z[i + 3] = (next3 << 1) | (next2 >> 31);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                uint next = x[i];
+                z[i] = (next << 1) | (c >> 31);
+                c = next;
+                ++i;
+            }
+            return c >> 31;
+        }
+#endif
+
+        public static ulong ShiftUpBit64(int len, ulong[] x, ulong c, ulong[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBit64(len, x.AsSpan(0, len), c, z.AsSpan(0, len));
+#else
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = x[i + 0];
+                ulong next1 = x[i + 1];
+                ulong next2 = x[i + 2];
+                ulong next3 = x[i + 3];
+                z[i + 0] = (next0 << 1) | (c     >> 63);
+                z[i + 1] = (next1 << 1) | (next0 >> 63);
+                z[i + 2] = (next2 << 1) | (next1 >> 63);
+                z[i + 3] = (next3 << 1) | (next2 >> 63);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = x[i];
+                z[i] = (next << 1) | (c >> 63);
+                c = next;
+                ++i;
+            }
+            return c >> 63;
+#endif
         }
 
         public static ulong ShiftUpBit64(int len, ulong[] x, int xOff, ulong c, ulong[] z, int zOff)
         {
-            for (int i = 0; i < len; ++i)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBit64(len, x.AsSpan(xOff, len), c, z.AsSpan(zOff, len));
+#else
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = x[xOff + i + 0];
+                ulong next1 = x[xOff + i + 1];
+                ulong next2 = x[xOff + i + 2];
+                ulong next3 = x[xOff + i + 3];
+                z[zOff + i + 0] = (next0 << 1) | (c     >> 63);
+                z[zOff + i + 1] = (next1 << 1) | (next0 >> 63);
+                z[zOff + i + 2] = (next2 << 1) | (next1 >> 63);
+                z[zOff + i + 3] = (next3 << 1) | (next2 >> 63);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 ulong next = x[xOff + i];
                 z[zOff + i] = (next << 1) | (c >> 63);
                 c = next;
+                ++i;
             }
             return c >> 63;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static ulong ShiftUpBit64(int len, ReadOnlySpan<ulong> x, ulong c, Span<ulong> z)
+        {
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = x[i + 0];
+                ulong next1 = x[i + 1];
+                ulong next2 = x[i + 2];
+                ulong next3 = x[i + 3];
+                z[i + 0] = (next0 << 1) | (c     >> 63);
+                z[i + 1] = (next1 << 1) | (next0 >> 63);
+                z[i + 2] = (next2 << 1) | (next1 >> 63);
+                z[i + 3] = (next3 << 1) | (next2 >> 63);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = x[i];
+                z[i] = (next << 1) | (c >> 63);
+                c = next;
+                ++i;
+            }
+            return c >> 63;
+        }
+#endif
+
         public static uint ShiftUpBits(int len, uint[] z, int bits, uint c)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits(len, z.AsSpan(0, len), bits, c);
+#else
             Debug.Assert(bits > 0 && bits < 32);
-            for (int i = 0; i < len; ++i)
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = z[i + 0];
+                uint next1 = z[i + 1];
+                uint next2 = z[i + 2];
+                uint next3 = z[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = z[i];
                 z[i] = (next << bits) | (c >> -bits);
                 c = next;
+                ++i;
             }
             return c >> -bits;
+#endif
         }
 
         public static uint ShiftUpBits(int len, uint[] z, int zOff, int bits, uint c)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits(len, z.AsSpan(zOff, len), bits, c);
+#else
             Debug.Assert(bits > 0 && bits < 32);
-            for (int i = 0; i < len; ++i)
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = z[zOff + i + 0];
+                uint next1 = z[zOff + i + 1];
+                uint next2 = z[zOff + i + 2];
+                uint next3 = z[zOff + i + 3];
+                z[zOff + i + 0] = (next0 << bits) | (c     >> -bits);
+                z[zOff + i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[zOff + i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[zOff + i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = z[zOff + i];
                 z[zOff + i] = (next << bits) | (c >> -bits);
                 c = next;
+                ++i;
             }
             return c >> -bits;
+#endif
         }
 
-        public static ulong ShiftUpBits64(int len, ulong[] z, int zOff, int bits, ulong c)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftUpBits(int len, Span<uint> z, int bits, uint c)
         {
-            Debug.Assert(bits > 0 && bits < 64);
-            for (int i = 0; i < len; ++i)
+            Debug.Assert(bits > 0 && bits < 32);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = z[i + 0];
+                uint next1 = z[i + 1];
+                uint next2 = z[i + 2];
+                uint next3 = z[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
-                ulong next = z[zOff + i];
-                z[zOff + i] = (next << bits) | (c >> -bits);
+                uint next = z[i];
+                z[i] = (next << bits) | (c >> -bits);
                 c = next;
+                ++i;
             }
             return c >> -bits;
         }
+#endif
 
         public static uint ShiftUpBits(int len, uint[] x, int bits, uint c, uint[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits(len, x.AsSpan(0, len), bits, c, z.AsSpan(0, len));
+#else
             Debug.Assert(bits > 0 && bits < 32);
-            for (int i = 0; i < len; ++i)
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = x[i + 0];
+                uint next1 = x[i + 1];
+                uint next2 = x[i + 2];
+                uint next3 = x[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = x[i];
                 z[i] = (next << bits) | (c >> -bits);
                 c = next;
+                ++i;
             }
             return c >> -bits;
+#endif
         }
 
         public static uint ShiftUpBits(int len, uint[] x, int xOff, int bits, uint c, uint[] z, int zOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits(len, x.AsSpan(xOff, len), bits, c, z.AsSpan(zOff, len));
+#else
             Debug.Assert(bits > 0 && bits < 32);
-            for (int i = 0; i < len; ++i)
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = x[xOff + i + 0];
+                uint next1 = x[xOff + i + 1];
+                uint next2 = x[xOff + i + 2];
+                uint next3 = x[xOff + i + 3];
+                z[zOff + i + 0] = (next0 << bits) | (c     >> -bits);
+                z[zOff + i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[zOff + i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[zOff + i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 uint next = x[xOff + i];
                 z[zOff + i] = (next << bits) | (c >> -bits);
                 c = next;
+                ++i;
             }
             return c >> -bits;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftUpBits(int len, ReadOnlySpan<uint> x, int bits, uint c, Span<uint> z)
+        {
+            Debug.Assert(bits > 0 && bits < 32);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                uint next0 = x[i + 0];
+                uint next1 = x[i + 1];
+                uint next2 = x[i + 2];
+                uint next3 = x[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                uint next = x[i];
+                z[i] = (next << bits) | (c >> -bits);
+                c = next;
+                ++i;
+            }
+            return c >> -bits;
+        }
+#endif
+
+        public static ulong ShiftUpBits64(int len, ulong[] z, int bits, ulong c)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits64(len, z.AsSpan(0, len), bits, c);
+#else
+            Debug.Assert(bits > 0 && bits < 64);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = z[i + 0];
+                ulong next1 = z[i + 1];
+                ulong next2 = z[i + 2];
+                ulong next3 = z[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = z[i];
+                z[i] = (next << bits) | (c >> -bits);
+                c = next;
+                ++i;
+            }
+            return c >> -bits;
+#endif
+        }
+
+        public static ulong ShiftUpBits64(int len, ulong[] z, int zOff, int bits, ulong c)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits64(len, z.AsSpan(zOff, len), bits, c);
+#else
+            Debug.Assert(bits > 0 && bits < 64);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = z[zOff + i + 0];
+                ulong next1 = z[zOff + i + 1];
+                ulong next2 = z[zOff + i + 2];
+                ulong next3 = z[zOff + i + 3];
+                z[zOff + i + 0] = (next0 << bits) | (c     >> -bits);
+                z[zOff + i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[zOff + i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[zOff + i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = z[zOff + i];
+                z[zOff + i] = (next << bits) | (c >> -bits);
+                c = next;
+                ++i;
+            }
+            return c >> -bits;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static ulong ShiftUpBits64(int len, Span<ulong> z, int bits, ulong c)
+        {
+            Debug.Assert(bits > 0 && bits < 64);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = z[i + 0];
+                ulong next1 = z[i + 1];
+                ulong next2 = z[i + 2];
+                ulong next3 = z[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = z[i];
+                z[i] = (next << bits) | (c >> -bits);
+                c = next;
+                ++i;
+            }
+            return c >> -bits;
+        }
+#endif
+
+        public static ulong ShiftUpBits64(int len, ulong[] x, int bits, ulong c, ulong[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits64(len, x.AsSpan(0, len), bits, c, z.AsSpan(0, len));
+#else
+            Debug.Assert(bits > 0 && bits < 64);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = x[i + 0];
+                ulong next1 = x[i + 1];
+                ulong next2 = x[i + 2];
+                ulong next3 = x[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = x[i];
+                z[i] = (next << bits) | (c >> -bits);
+                c = next;
+                ++i;
+            }
+            return c >> -bits;
+#endif
         }
 
         public static ulong ShiftUpBits64(int len, ulong[] x, int xOff, int bits, ulong c, ulong[] z, int zOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ShiftUpBits64(len, x.AsSpan(xOff, len), bits, c, z.AsSpan(zOff, len));
+#else
             Debug.Assert(bits > 0 && bits < 64);
-            for (int i = 0; i < len; ++i)
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = x[xOff + i + 0];
+                ulong next1 = x[xOff + i + 1];
+                ulong next2 = x[xOff + i + 2];
+                ulong next3 = x[xOff + i + 3];
+                z[zOff + i + 0] = (next0 << bits) | (c     >> -bits);
+                z[zOff + i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[zOff + i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[zOff + i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
             {
                 ulong next = x[xOff + i];
                 z[zOff + i] = (next << bits) | (c >> -bits);
                 c = next;
+                ++i;
             }
             return c >> -bits;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static ulong ShiftUpBits64(int len, ReadOnlySpan<ulong> x, int bits, ulong c, Span<ulong> z)
+        {
+            Debug.Assert(bits > 0 && bits < 64);
+            int i = 0, limit4 = len - 4;
+            while (i <= limit4)
+            {
+                ulong next0 = x[i + 0];
+                ulong next1 = x[i + 1];
+                ulong next2 = x[i + 2];
+                ulong next3 = x[i + 3];
+                z[i + 0] = (next0 << bits) | (c     >> -bits);
+                z[i + 1] = (next1 << bits) | (next0 >> -bits);
+                z[i + 2] = (next2 << bits) | (next1 >> -bits);
+                z[i + 3] = (next3 << bits) | (next2 >> -bits);
+                c = next3;
+                i += 4;
+            }
+            while (i < len)
+            {
+                ulong next = x[i];
+                z[i] = (next << bits) | (c >> -bits);
+                c = next;
+                ++i;
+            }
+            return c >> -bits;
+        }
+#endif
+
         public static void Square(int len, uint[] x, uint[] zz)
         {
             int extLen = len << 1;
@@ -1128,6 +2268,39 @@ namespace Org.BouncyCastle.Math.Raw
             ShiftUpBit(extLen, zz, zzOff, x[xOff] << 31);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Square(int len, ReadOnlySpan<uint> x, Span<uint> zz)
+        {
+            int extLen = len << 1;
+            uint c = 0;
+            int j = len, k = extLen;
+            do
+            {
+                ulong xVal = (ulong)x[--j];
+                ulong p = xVal * xVal;
+                zz[--k] = (c << 31) | (uint)(p >> 33);
+                zz[--k] = (uint)(p >> 1);
+                c = (uint)p;
+            }
+            while (j > 0);
+
+            ulong d = 0UL;
+            int zzPos = 2;
+
+            for (int i = 1; i < len; ++i)
+            {
+                d += SquareWordAddTo(x, i, zz);
+                d += zz[zzPos];
+                zz[zzPos++] = (uint)d; d >>= 32;
+                d += zz[zzPos];
+                zz[zzPos++] = (uint)d; d >>= 32;
+            }
+            Debug.Assert(0UL == d);
+
+            ShiftUpBit(extLen, zz, x[0] << 31);
+        }
+#endif
+
         public static uint SquareWordAddTo(uint[] x, int xPos, uint[] z)
         {
             ulong c = 0, xVal = (ulong)x[xPos];
@@ -1157,6 +2330,22 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint SquareWordAddTo(ReadOnlySpan<uint> x, int xPos, Span<uint> z)
+        {
+            ulong c = 0, xVal = x[xPos];
+            int i = 0;
+            do
+            {
+                c += xVal * x[i] + z[xPos + i];
+                z[xPos + i] = (uint)c;
+                c >>= 32;
+            }
+            while (++i < xPos);
+            return (uint)c;
+        }
+#endif
+
         public static int Sub(int len, uint[] x, uint[] y, uint[] z)
         {
             long c = 0;
@@ -1180,6 +2369,21 @@ namespace Org.BouncyCastle.Math.Raw
             }
             return (int)c;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int Sub(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            long c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (long)x[i] - y[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (int)c;
+        }
+#endif
+
         public static int Sub33At(int len, uint x, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 2));
@@ -1204,6 +2408,20 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : DecAt(len, z, zOff, zPos + 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int Sub33At(int len, uint x, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 2));
+            long c = (long)z[zPos + 0] - x;
+            z[zPos + 0] = (uint)c;
+            c >>= 32;
+            c += (long)z[zPos + 1] - 1;
+            z[zPos + 1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : DecAt(len, z, zPos + 2);
+        }
+#endif
+
         public static int Sub33From(int len, uint x, uint[] z)
         {
             long c = (long)z[0] - x;
@@ -1226,6 +2444,19 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : DecAt(len, z, zOff, 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int Sub33From(int len, uint x, Span<uint> z)
+        {
+            long c = (long)z[0] - x;
+            z[0] = (uint)c;
+            c >>= 32;
+            c += (long)z[1] - 1;
+            z[1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : DecAt(len, z, 2);
+        }
+#endif
+
         public static int SubBothFrom(int len, uint[] x, uint[] y, uint[] z)
         {
             long c = 0;
@@ -1250,13 +2481,27 @@ namespace Org.BouncyCastle.Math.Raw
             return (int)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int SubBothFrom(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            long c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (long)z[i] - x[i] - y[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (int)c;
+        }
+#endif
+
         public static int SubDWordAt(int len, ulong x, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 2));
-            long c = (long)z[zPos + 0] - (long)(x & M);
+            long c = z[zPos + 0] - (long)(x & M);
             z[zPos + 0] = (uint)c;
             c >>= 32;
-            c += (long)z[zPos + 1] - (long)(x >> 32);
+            c += z[zPos + 1] - (long)(x >> 32);
             z[zPos + 1] = (uint)c;
             c >>= 32;
             return c == 0 ? 0 : DecAt(len, z, zPos + 2);
@@ -1265,21 +2510,35 @@ namespace Org.BouncyCastle.Math.Raw
         public static int SubDWordAt(int len, ulong x, uint[] z, int zOff, int zPos)
         {
             Debug.Assert(zPos <= (len - 2));
-            long c = (long)z[zOff + zPos] - (long)(x & M);
+            long c = z[zOff + zPos] - (long)(x & M);
             z[zOff + zPos] = (uint)c;
             c >>= 32;
-            c += (long)z[zOff + zPos + 1] - (long)(x >> 32);
+            c += z[zOff + zPos + 1] - (long)(x >> 32);
             z[zOff + zPos + 1] = (uint)c;
             c >>= 32;
-            return c == 0 ? 0 : DecAt(len, z,  zOff, zPos + 2);
+            return c == 0 ? 0 : DecAt(len, z, zOff, zPos + 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int SubDWordAt(int len, ulong x, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 2));
+            long c = z[zPos + 0] - (long)(x & M);
+            z[zPos + 0] = (uint)c;
+            c >>= 32;
+            c += z[zPos + 1] - (long)(x >> 32);
+            z[zPos + 1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : DecAt(len, z, zPos + 2);
+        }
+#endif
+
         public static int SubDWordFrom(int len, ulong x, uint[] z)
         {
-            long c = (long)z[0] - (long)(x & M);
+            long c = z[0] - (long)(x & M);
             z[0] = (uint)c;
             c >>= 32;
-            c += (long)z[1] - (long)(x >> 32);
+            c += z[1] - (long)(x >> 32);
             z[1] = (uint)c;
             c >>= 32;
             return c == 0 ? 0 : DecAt(len, z, 2);
@@ -1287,15 +2546,28 @@ namespace Org.BouncyCastle.Math.Raw
 
         public static int SubDWordFrom(int len, ulong x, uint[] z, int zOff)
         {
-            long c = (long)z[zOff + 0] - (long)(x & M);
+            long c = z[zOff + 0] - (long)(x & M);
             z[zOff + 0] = (uint)c;
             c >>= 32;
-            c += (long)z[zOff + 1] - (long)(x >> 32);
+            c += z[zOff + 1] - (long)(x >> 32);
             z[zOff + 1] = (uint)c;
             c >>= 32;
             return c == 0 ? 0 : DecAt(len, z, zOff, 2);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int SubDWordFrom(int len, ulong x, Span<uint> z)
+        {
+            long c = z[0] - (long)(x & M);
+            z[0] = (uint)c;
+            c >>= 32;
+            c += z[1] - (long)(x >> 32);
+            z[1] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : DecAt(len, z, 2);
+        }
+#endif
+
         public static int SubFrom(int len, uint[] x, uint[] z)
         {
             long c = 0;
@@ -1320,6 +2592,20 @@ namespace Org.BouncyCastle.Math.Raw
             return (int)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int SubFrom(int len, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            long c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (long)z[i] - x[i];
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (int)c;
+        }
+#endif
+
         public static int SubWordAt(int len, uint x, uint[] z, int zPos)
         {
             Debug.Assert(zPos <= (len - 1));
@@ -1338,6 +2624,17 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : DecAt(len, z, zOff, zPos + 1);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int SubWordAt(int len, uint x, Span<uint> z, int zPos)
+        {
+            Debug.Assert(zPos <= (len - 1));
+            long c = (long)z[zPos] - x;
+            z[zPos] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : DecAt(len, z, zPos + 1);
+        }
+#endif
+
         public static int SubWordFrom(int len, uint x, uint[] z)
         {
             long c = (long)z[0] - x;
@@ -1354,34 +2651,251 @@ namespace Org.BouncyCastle.Math.Raw
             return c == 0 ? 0 : DecAt(len, z, zOff, 1);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int SubWordFrom(int len, uint x, Span<uint> z)
+        {
+            long c = (long)z[0] - x;
+            z[0] = (uint)c;
+            c >>= 32;
+            return c == 0 ? 0 : DecAt(len, z, 1);
+        }
+#endif
+
         public static BigInteger ToBigInteger(int len, uint[] x)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ToBigInteger(len, x.AsSpan());
+#else
             byte[] bs = new byte[len << 2];
-            for (int i = 0; i < len; ++i)
+            int xPos = len, bsPos = 0;
+            while (--xPos >= 0)
             {
-                uint x_i = x[i];
-                if (x_i != 0)
-                {
-                    Pack.UInt32_To_BE(x_i, bs, (len - 1 - i) << 2);
-                }
+                Pack.UInt32_To_BE(x[xPos], bs, bsPos);
+                bsPos += 4;
             }
             return new BigInteger(1, bs);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static BigInteger ToBigInteger(int len, ReadOnlySpan<uint> x)
+        {
+            int bsLen = len << 2;
+            Span<byte> bs = bsLen <= 512
+                ? stackalloc byte[bsLen]
+                : new byte[bsLen];
+
+            int xPos = len;
+            Span<byte> t = bs;
+            while (--xPos >= 0)
+            {
+                Pack.UInt32_To_BE(x[xPos], t);
+                t = t[4..];
+            }
+            return new BigInteger(1, bs);
+        }
+#endif
+
+        public static void Xor(int len, uint[] x, uint[] y, uint[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor(len, x.AsSpan(0, len), y.AsSpan(0, len), z.AsSpan(0, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[i] = x[i] ^ y[i];
+            }
+#endif
+        }
+
+        public static void Xor(int len, uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor(len, x.AsSpan(xOff, len), y.AsSpan(yOff, len), z.AsSpan(zOff, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[zOff + i] = x[xOff + i] ^ y[yOff + i];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Xor(int len, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            int i = 0, limit16 = len - 16;
+            while (i <= limit16)
+            {
+                Nat512.Xor(x[i..], y[i..], z[i..]);
+                i += 16;
+            }
+            while (i < len)
+            {
+                z[i] = x[i] ^ y[i];
+                ++i;
+            }
+        }
+#endif
+
+        public static void Xor64(int len, ulong[] x, ulong[] y, ulong[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor64(len, x.AsSpan(0, len), y.AsSpan(0, len), z.AsSpan(0, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[i] = x[i] ^ y[i];
+            }
+#endif
+        }
+
+        public static void Xor64(int len, ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor64(len, x.AsSpan(xOff, len), y.AsSpan(yOff, len), z.AsSpan(zOff, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[zOff + i] = x[xOff + i] ^ y[yOff + i];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Xor64(int len, ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulong> z)
+        {
+            int i = 0, limit8 = len - 8;
+            while (i <= limit8)
+            {
+                Nat512.Xor64(x[i..], y[i..], z[i..]);
+                i += 8;
+            }
+            while (i < len)
+            {
+                z[i] = x[i] ^ y[i];
+                ++i;
+            }
+        }
+#endif
+
+        public static void XorTo(int len, uint[] x, uint[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo(len, x.AsSpan(0, len), z.AsSpan(0, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[i] ^= x[i];
+            }
+#endif
+        }
+
+        public static void XorTo(int len, uint[] x, int xOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo(len, x.AsSpan(xOff, len), z.AsSpan(zOff, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[zOff + i] ^= x[xOff + i];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorTo(int len, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            int i = 0, limit16 = len - 16;
+            while (i <= limit16)
+            {
+                Nat512.XorTo(x[i..], z[i..]);
+                i += 16;
+            }
+            while (i < len)
+            {
+                z[i] ^= x[i];
+                ++i;
+            }
+        }
+#endif
+
+        public static void XorTo64(int len, ulong[] x, ulong[] z)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo64(len, x.AsSpan(0, len), z.AsSpan(0, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[i] ^= x[i];
+            }
+#endif
+        }
+
+        public static void XorTo64(int len, ulong[] x, int xOff, ulong[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo64(len, x.AsSpan(xOff, len), z.AsSpan(zOff, len));
+#else
+            for (int i = 0; i < len; ++i)
+            {
+                z[zOff + i] ^= x[xOff + i];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorTo64(int len, ReadOnlySpan<ulong> x, Span<ulong> z)
+        {
+            int i = 0, limit8 = len - 8;
+            while (i <= limit8)
+            {
+                Nat512.XorTo64(x[i..], z[i..]);
+                i += 8;
+            }
+            while (i < len)
+            {
+                z[i] ^= x[i];
+                ++i;
+            }
+        }
+#endif
+
         public static void Zero(int len, uint[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            z.AsSpan(0, len).Fill(0U);
+#else
             for (int i = 0; i < len; ++i)
             {
-                z[i] = 0;
+                z[i] = 0U;
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Zero(int len, Span<uint> z)
+        {
+            z[..len].Fill(0U);
         }
+#endif
 
         public static void Zero64(int len, ulong[] z)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            z.AsSpan(0, len).Fill(0UL);
+#else
             for (int i = 0; i < len; ++i)
             {
                 z[i] = 0UL;
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Zero64(int len, Span<ulong> z)
+        {
+            z[..len].Fill(0UL);
         }
+#endif
     }
 }
diff --git a/crypto/src/math/raw/Nat256.cs b/crypto/src/math/raw/Nat256.cs
index 710060bee..47e0644f6 100644
--- a/crypto/src/math/raw/Nat256.cs
+++ b/crypto/src/math/raw/Nat256.cs
@@ -1,5 +1,11 @@
 using System;
 using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 using Org.BouncyCastle.Crypto.Utilities;
 
@@ -1364,6 +1370,71 @@ namespace Org.BouncyCastle.Math.Raw
             return new BigInteger(1, bs);
         }
 
+        public static void Xor(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[zOff + i + 0] = x[xOff + i + 0] ^ y[yOff + i + 0];
+                z[zOff + i + 1] = x[xOff + i + 1] ^ y[yOff + i + 1];
+                z[zOff + i + 2] = x[xOff + i + 2] ^ y[yOff + i + 2];
+                z[zOff + i + 3] = x[xOff + i + 3] ^ y[yOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Xor(ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Avx2.IsSupported && Unsafe.SizeOf<Vector256<byte>>() == 32)
+            {
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Y = MemoryMarshal.AsBytes(y[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Y[0x00..0x20]);
+
+                var Z0 = Avx2.Xor(X0, Y0);
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                return;
+            }
+
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Y = MemoryMarshal.AsBytes(y[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Y[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Y[0x10..0x20]);
+
+                var Z0 = Sse2.Xor(X0, Y0);
+                var Z1 = Sse2.Xor(X1, Y1);
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[i + 0] = x[i + 0] ^ y[i + 0];
+                z[i + 1] = x[i + 1] ^ y[i + 1];
+                z[i + 2] = x[i + 2] ^ y[i + 2];
+                z[i + 3] = x[i + 3] ^ y[i + 3];
+            }
+        }
+#endif
+
         public static void Zero(uint[] z)
         {
             z[0] = 0;
diff --git a/crypto/src/math/raw/Nat512.cs b/crypto/src/math/raw/Nat512.cs
index a9ef2b3b6..2312e1cf2 100644
--- a/crypto/src/math/raw/Nat512.cs
+++ b/crypto/src/math/raw/Nat512.cs
@@ -1,5 +1,10 @@
 using System;
-using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
 
 namespace Org.BouncyCastle.Math.Raw
 {
@@ -42,5 +47,313 @@ namespace Org.BouncyCastle.Math.Raw
             c24 += (uint)Nat.SubFrom(16, m, 0, zz, 8);
             Nat.AddWordAt(32, c24, zz, 24); 
         }
+
+        public static void Xor(uint[] x, int xOff, uint[] y, int yOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[zOff + i + 0] = x[xOff + i + 0] ^ y[yOff + i + 0];
+                z[zOff + i + 1] = x[xOff + i + 1] ^ y[yOff + i + 1];
+                z[zOff + i + 2] = x[xOff + i + 2] ^ y[yOff + i + 2];
+                z[zOff + i + 3] = x[xOff + i + 3] ^ y[yOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Xor(ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Avx2.IsSupported && Unsafe.SizeOf<Vector256<byte>>() == 32)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Y = MemoryMarshal.AsBytes(y[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Y[0x00..0x20]);
+                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Y[0x20..0x40]);
+
+                var Z0 = Avx2.Xor(X0, Y0);
+                var Z1 = Avx2.Xor(X1, Y1);
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
+                return;
+            }
+
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Y = MemoryMarshal.AsBytes(y[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+                var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
+                var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Y[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Y[0x10..0x20]);
+                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Y[0x20..0x30]);
+                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Y[0x30..0x40]);
+
+                var Z0 = Sse2.Xor(X0, Y0);
+                var Z1 = Sse2.Xor(X1, Y1);
+                var Z2 = Sse2.Xor(X2, Y2);
+                var Z3 = Sse2.Xor(X3, Y3);
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                MemoryMarshal.Write(Z[0x20..0x30], ref Z2);
+                MemoryMarshal.Write(Z[0x30..0x40], ref Z3);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] = x[i + 0] ^ y[i + 0];
+                z[i + 1] = x[i + 1] ^ y[i + 1];
+                z[i + 2] = x[i + 2] ^ y[i + 2];
+                z[i + 3] = x[i + 3] ^ y[i + 3];
+            }
+        }
+#endif
+
+        public static void XorTo(uint[] x, int xOff, uint[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo(x.AsSpan(xOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[zOff + i + 0] ^= x[xOff + i + 0];
+                z[zOff + i + 1] ^= x[xOff + i + 1];
+                z[zOff + i + 2] ^= x[xOff + i + 2];
+                z[zOff + i + 3] ^= x[xOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorTo(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Avx2.IsSupported && Unsafe.SizeOf<Vector256<byte>>() == 32)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Z[0x00..0x20]);
+                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Z[0x20..0x40]);
+
+                var Z0 = Avx2.Xor(X0, Y0);
+                var Z1 = Avx2.Xor(X1, Y1);
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
+                return;
+            }
+
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                var X = MemoryMarshal.AsBytes(x[..16]);
+                var Z = MemoryMarshal.AsBytes(z[..16]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+                var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
+                var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Z[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Z[0x10..0x20]);
+                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Z[0x20..0x30]);
+                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Z[0x30..0x40]);
+
+                var Z0 = Sse2.Xor(X0, Y0);
+                var Z1 = Sse2.Xor(X1, Y1);
+                var Z2 = Sse2.Xor(X2, Y2);
+                var Z3 = Sse2.Xor(X3, Y3);
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                MemoryMarshal.Write(Z[0x20..0x30], ref Z2);
+                MemoryMarshal.Write(Z[0x30..0x40], ref Z3);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 16; i += 4)
+            {
+                z[i + 0] ^= x[i + 0];
+                z[i + 1] ^= x[i + 1];
+                z[i + 2] ^= x[i + 2];
+                z[i + 3] ^= x[i + 3];
+            }
+        }
+#endif
+
+        public static void Xor64(ulong[] x, int xOff, ulong[] y, int yOff, ulong[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Xor64(x.AsSpan(xOff), y.AsSpan(yOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[zOff + i + 0] = x[xOff + i + 0] ^ y[yOff + i + 0];
+                z[zOff + i + 1] = x[xOff + i + 1] ^ y[yOff + i + 1];
+                z[zOff + i + 2] = x[xOff + i + 2] ^ y[yOff + i + 2];
+                z[zOff + i + 3] = x[xOff + i + 3] ^ y[yOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Xor64(ReadOnlySpan<ulong> x, ReadOnlySpan<ulong> y, Span<ulong> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Avx2.IsSupported && Unsafe.SizeOf<Vector256<byte>>() == 32)
+            {
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Y = MemoryMarshal.AsBytes(y[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Y[0x00..0x20]);
+                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Y[0x20..0x40]);
+
+                var Z0 = Avx2.Xor(X0, Y0);
+                var Z1 = Avx2.Xor(X1, Y1);
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
+                return;
+            }
+
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Y = MemoryMarshal.AsBytes(y[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+                var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
+                var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Y[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Y[0x10..0x20]);
+                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Y[0x20..0x30]);
+                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Y[0x30..0x40]);
+
+                var Z0 = Sse2.Xor(X0, Y0);
+                var Z1 = Sse2.Xor(X1, Y1);
+                var Z2 = Sse2.Xor(X2, Y2);
+                var Z3 = Sse2.Xor(X3, Y3);
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                MemoryMarshal.Write(Z[0x20..0x30], ref Z2);
+                MemoryMarshal.Write(Z[0x30..0x40], ref Z3);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[i + 0] = x[i + 0] ^ y[i + 0];
+                z[i + 1] = x[i + 1] ^ y[i + 1];
+                z[i + 2] = x[i + 2] ^ y[i + 2];
+                z[i + 3] = x[i + 3] ^ y[i + 3];
+            }
+        }
+#endif
+
+        public static void XorTo64(ulong[] x, int xOff, ulong[] z, int zOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            XorTo64(x.AsSpan(xOff), z.AsSpan(zOff));
+#else
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[zOff + i + 0] ^= x[xOff + i + 0];
+                z[zOff + i + 1] ^= x[xOff + i + 1];
+                z[zOff + i + 2] ^= x[xOff + i + 2];
+                z[zOff + i + 3] ^= x[xOff + i + 3];
+            }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void XorTo64(ReadOnlySpan<ulong> x, Span<ulong> z)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Avx2.IsSupported && Unsafe.SizeOf<Vector256<byte>>() == 32)
+            {
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
+
+                var X0 = MemoryMarshal.Read<Vector256<byte>>(X[0x00..0x20]);
+                var X1 = MemoryMarshal.Read<Vector256<byte>>(X[0x20..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector256<byte>>(Z[0x00..0x20]);
+                var Y1 = MemoryMarshal.Read<Vector256<byte>>(Z[0x20..0x40]);
+
+                var Z0 = Avx2.Xor(X0, Y0);
+                var Z1 = Avx2.Xor(X1, Y1);
+
+                MemoryMarshal.Write(Z[0x00..0x20], ref Z0);
+                MemoryMarshal.Write(Z[0x20..0x40], ref Z1);
+                return;
+            }
+
+            if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == 16)
+            {
+                var X = MemoryMarshal.AsBytes(x[..8]);
+                var Z = MemoryMarshal.AsBytes(z[..8]);
+
+                var X0 = MemoryMarshal.Read<Vector128<byte>>(X[0x00..0x10]);
+                var X1 = MemoryMarshal.Read<Vector128<byte>>(X[0x10..0x20]);
+                var X2 = MemoryMarshal.Read<Vector128<byte>>(X[0x20..0x30]);
+                var X3 = MemoryMarshal.Read<Vector128<byte>>(X[0x30..0x40]);
+
+                var Y0 = MemoryMarshal.Read<Vector128<byte>>(Z[0x00..0x10]);
+                var Y1 = MemoryMarshal.Read<Vector128<byte>>(Z[0x10..0x20]);
+                var Y2 = MemoryMarshal.Read<Vector128<byte>>(Z[0x20..0x30]);
+                var Y3 = MemoryMarshal.Read<Vector128<byte>>(Z[0x30..0x40]);
+
+                var Z0 = Sse2.Xor(X0, Y0);
+                var Z1 = Sse2.Xor(X1, Y1);
+                var Z2 = Sse2.Xor(X2, Y2);
+                var Z3 = Sse2.Xor(X3, Y3);
+
+                MemoryMarshal.Write(Z[0x00..0x10], ref Z0);
+                MemoryMarshal.Write(Z[0x10..0x20], ref Z1);
+                MemoryMarshal.Write(Z[0x20..0x30], ref Z2);
+                MemoryMarshal.Write(Z[0x30..0x40], ref Z3);
+                return;
+            }
+#endif
+
+            for (int i = 0; i < 8; i += 4)
+            {
+                z[i + 0] ^= x[i + 0];
+                z[i + 1] ^= x[i + 1];
+                z[i + 2] ^= x[i + 2];
+                z[i + 3] ^= x[i + 3];
+            }
+        }
+#endif
     }
 }
diff --git a/crypto/src/ocsp/BasicOCSPRespGenerator.cs b/crypto/src/ocsp/BasicOCSPRespGenerator.cs
index 63d577404..ff7ae33d3 100644
--- a/crypto/src/ocsp/BasicOCSPRespGenerator.cs
+++ b/crypto/src/ocsp/BasicOCSPRespGenerator.cs
@@ -9,7 +9,6 @@ using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Operators;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Security.Certificates;
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.X509;
 
 namespace Org.BouncyCastle.Ocsp
@@ -28,8 +27,8 @@ namespace Org.BouncyCastle.Ocsp
 		{
 			internal CertificateID         certId;
 			internal CertStatus            certStatus;
-			internal DerGeneralizedTime    thisUpdate;
-			internal DerGeneralizedTime    nextUpdate;
+			internal Asn1GeneralizedTime   thisUpdate;
+			internal Asn1GeneralizedTime   nextUpdate;
 			internal X509Extensions        extensions;
 
 			internal ResponseObject(
@@ -57,7 +56,7 @@ namespace Org.BouncyCastle.Ocsp
 						:	null;
 
 					this.certStatus = new CertStatus(
-						new RevokedInfo(new DerGeneralizedTime(rs.RevocationTime), revocationReason));
+						new RevokedInfo(new Asn1GeneralizedTime(rs.RevocationTime), revocationReason));
 				}
 
 				this.thisUpdate = new DerGeneralizedTime(thisUpdate);
@@ -187,19 +186,19 @@ namespace Org.BouncyCastle.Ocsp
 				}
 			}
 
-			ResponseData tbsResp = new ResponseData(responderID.ToAsn1Object(), new DerGeneralizedTime(producedAt),
+			ResponseData tbsResp = new ResponseData(responderID.ToAsn1Object(), new Asn1GeneralizedTime(producedAt),
 				new DerSequence(responses), responseExtensions);
 			DerBitString bitSig;
 
 			try
 			{
-                IStreamCalculator streamCalculator = signatureCalculator.CreateCalculator();
+                IStreamCalculator<IBlockResult> streamCalculator = signatureCalculator.CreateCalculator();
 				using (Stream sigStream = streamCalculator.Stream)
 				{
 					tbsResp.EncodeTo(sigStream, Asn1Encodable.Der);
 				}
 
-				bitSig = new DerBitString(((IBlockResult)streamCalculator.GetResult()).Collect());
+				bitSig = new DerBitString(streamCalculator.GetResult().Collect());
 			}
 			catch (Exception e)
 			{
diff --git a/crypto/src/ocsp/RevokedStatus.cs b/crypto/src/ocsp/RevokedStatus.cs
index edbeb57da..a37bdade9 100644
--- a/crypto/src/ocsp/RevokedStatus.cs
+++ b/crypto/src/ocsp/RevokedStatus.cs
@@ -6,52 +6,49 @@ using Org.BouncyCastle.Asn1.X509;
 
 namespace Org.BouncyCastle.Ocsp
 {
-	/**
-	 * wrapper for the RevokedInfo object
-	 */
-	public class RevokedStatus
+    /// <summary>Wrapper for the RevokedInfo object</summary>
+    public class RevokedStatus
 		: CertificateStatus
 	{
-		internal readonly RevokedInfo info;
+		private readonly RevokedInfo m_revokedInfo;
 
-		public RevokedStatus(
-			RevokedInfo info)
+		public RevokedStatus(RevokedInfo revokedInfo)
 		{
-			this.info = info;
+			m_revokedInfo = revokedInfo;
 		}
 
-		public RevokedStatus(
-			DateTime	revocationDate,
-			int			reason)
+		public RevokedStatus(DateTime revocationDate)
 		{
-			this.info = new RevokedInfo(new DerGeneralizedTime(revocationDate), new CrlReason(reason));
+			m_revokedInfo = new RevokedInfo(new Asn1GeneralizedTime(revocationDate));
+		}
+
+        public RevokedStatus(DateTime revocationDate, int reason)
+		{
+			m_revokedInfo = new RevokedInfo(new Asn1GeneralizedTime(revocationDate), new CrlReason(reason));
 		}
 
 		public DateTime RevocationTime
 		{
-			get { return info.RevocationTime.ToDateTime(); }
+			get { return m_revokedInfo.RevocationTime.ToDateTime(); }
 		}
 
 		public bool HasRevocationReason
 		{
-			get { return (info.RevocationReason != null); }
+			get { return m_revokedInfo.RevocationReason != null; }
 		}
 
-		/**
-		 * return the revocation reason. Note: this field is optional, test for it
-		 * with hasRevocationReason() first.
-		 * @exception InvalidOperationException if a reason is asked for and none is avaliable
-		 */
-		public int RevocationReason
+        /// <summary>Return the revocation reason, if there is one.</summary>
+		/// <remarks>This field is optional; test for it with <see cref="HasRevocationReason"/> first.</remarks>
+		/// <returns>The revocation reason, if available.</returns>
+		/// <exception cref="InvalidOperationException">If no revocation reason is available.</exception>
+        public int RevocationReason
 		{
 			get
 			{
-				if (info.RevocationReason == null)
-				{
+				if (m_revokedInfo.RevocationReason == null)
 					throw new InvalidOperationException("attempt to get a reason where none is available");
-				}
 
-                return info.RevocationReason.IntValueExact;
+                return m_revokedInfo.RevocationReason.IntValueExact;
 			}
 		}
 	}
diff --git a/crypto/src/ocsp/SingleResp.cs b/crypto/src/ocsp/SingleResp.cs
index 66b2ce4b1..28e40e047 100644
--- a/crypto/src/ocsp/SingleResp.cs
+++ b/crypto/src/ocsp/SingleResp.cs
@@ -2,7 +2,6 @@ using System;
 
 using Org.BouncyCastle.Asn1.Ocsp;
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509;
 
 namespace Org.BouncyCastle.Ocsp
@@ -56,15 +55,7 @@ namespace Org.BouncyCastle.Ocsp
 		*
 		* @return nextUpdate, or null if not present.
 		*/
-		public DateTimeObject NextUpdate
-		{
-			get
-			{
-				return resp.NextUpdate == null
-					?	null
-					:	new DateTimeObject(resp.NextUpdate.ToDateTime());
-			}
-		}
+		public DateTime? NextUpdate => resp.NextUpdate?.ToDateTime();
 
 		public X509Extensions SingleExtensions
 		{
diff --git a/crypto/src/openpgp/EdDsaSigner.cs b/crypto/src/openpgp/EdDsaSigner.cs
new file mode 100644
index 000000000..0e15ac609
--- /dev/null
+++ b/crypto/src/openpgp/EdDsaSigner.cs
@@ -0,0 +1,72 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    internal sealed class EdDsaSigner
+        : ISigner
+    {
+        private readonly ISigner m_signer;
+        private readonly IDigest m_digest;
+        private readonly byte[] m_digBuf;
+
+        internal EdDsaSigner(ISigner signer, IDigest digest)
+        {
+            m_signer = signer;
+            m_digest = digest;
+            m_digBuf = new byte[digest.GetDigestSize()];
+        }
+
+        public string AlgorithmName => m_signer.AlgorithmName;
+
+        public void Init(bool forSigning, ICipherParameters cipherParameters)
+        {
+            m_signer.Init(forSigning, cipherParameters);
+            m_digest.Reset();
+        }
+
+        public void Update(byte b)
+        {
+            m_digest.Update(b);
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            m_digest.BlockUpdate(input, inOff, inLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            m_digest.BlockUpdate(input);
+        }
+#endif
+
+        public byte[] GenerateSignature()
+        {
+            m_digest.DoFinal(m_digBuf, 0);
+
+            m_signer.BlockUpdate(m_digBuf, 0, m_digBuf.Length);
+
+            return m_signer.GenerateSignature();
+        }
+
+        public bool VerifySignature(byte[] signature)
+        {
+            m_digest.DoFinal(m_digBuf, 0);
+
+            m_signer.BlockUpdate(m_digBuf, 0, m_digBuf.Length);
+
+            return m_signer.VerifySignature(signature);
+        }
+
+        public void Reset()
+        {
+            Arrays.Clear(m_digBuf);
+            m_signer.Reset();
+            m_digest.Reset();
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpCompressedData.cs b/crypto/src/openpgp/PgpCompressedData.cs
index fc7d200d0..346b0b1a1 100644
--- a/crypto/src/openpgp/PgpCompressedData.cs
+++ b/crypto/src/openpgp/PgpCompressedData.cs
@@ -1,7 +1,6 @@
 using System.IO;
 
-using Org.BouncyCastle.Bzip2;
-using Org.BouncyCastle.Utilities.Zlib;
+using Org.BouncyCastle.Utilities.IO.Compression;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
@@ -38,16 +37,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
             switch (Algorithm)
             {
-				case CompressionAlgorithmTag.Uncompressed:
-					return GetInputStream();
-				case CompressionAlgorithmTag.Zip:
-					return new ZInputStream(GetInputStream(), true);
-                case CompressionAlgorithmTag.ZLib:
-					return new ZInputStream(GetInputStream());
-				case CompressionAlgorithmTag.BZip2:
-					return new CBZip2InputStream(GetInputStream());
-                default:
-                    throw new PgpException("can't recognise compression algorithm: " + Algorithm);
+			case CompressionAlgorithmTag.Uncompressed:
+				return GetInputStream();
+			case CompressionAlgorithmTag.Zip:
+                return Zip.DecompressInput(GetInputStream());
+            case CompressionAlgorithmTag.ZLib:
+				return ZLib.DecompressInput(GetInputStream());
+			case CompressionAlgorithmTag.BZip2:
+                return Bzip2.DecompressInput(GetInputStream());
+            default:
+                throw new PgpException("can't recognise compression algorithm: " + Algorithm);
             }
         }
     }
diff --git a/crypto/src/openpgp/PgpCompressedDataGenerator.cs b/crypto/src/openpgp/PgpCompressedDataGenerator.cs
index 88a1070d1..d13d7402b 100644
--- a/crypto/src/openpgp/PgpCompressedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpCompressedDataGenerator.cs
@@ -1,8 +1,8 @@
 using System;
 using System.IO;
 
-using Org.BouncyCastle.Bzip2;
 using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO.Compression;
 using Org.BouncyCastle.Utilities.Zlib;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
@@ -76,7 +76,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData);
 
-			doOpen();
+			DoOpen();
 
 			return new WrappedGeneratorStream(this, dOut);
 		}
@@ -120,32 +120,32 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData, buffer);
 
-			doOpen();
+			DoOpen();
 
 			return new WrappedGeneratorStream(this, dOut);
 		}
 
-		private void doOpen()
+		private void DoOpen()
 		{
 			pkOut.WriteByte((byte) algorithm);
 
 			switch (algorithm)
 			{
-				case CompressionAlgorithmTag.Uncompressed:
-					dOut = pkOut;
-					break;
-				case CompressionAlgorithmTag.Zip:
-					dOut = new SafeZOutputStream(pkOut, compression, true);
-					break;
-				case CompressionAlgorithmTag.ZLib:
-					dOut = new SafeZOutputStream(pkOut, compression, false);
-					break;
-				case CompressionAlgorithmTag.BZip2:
-					dOut = new SafeCBZip2OutputStream(pkOut);
-					break;
-				default:
-					// Constructor should guard against this possibility
-					throw new InvalidOperationException();
+			case CompressionAlgorithmTag.Uncompressed:
+				dOut = pkOut;
+				break;
+			case CompressionAlgorithmTag.Zip:
+                dOut = Zip.CompressOutput(pkOut, compression, true);
+                break;
+			case CompressionAlgorithmTag.ZLib:
+				dOut = ZLib.CompressOutput(pkOut, compression, true);
+				break;
+			case CompressionAlgorithmTag.BZip2:
+				dOut = Bzip2.CompressOutput(pkOut, true);
+				break;
+			default:
+				// Constructor should guard against this possibility
+				throw new InvalidOperationException();
 			}
 		}
 
@@ -165,57 +165,5 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				pkOut = null;
 			}
 		}
-
-		private class SafeCBZip2OutputStream : CBZip2OutputStream
-		{
-			public SafeCBZip2OutputStream(Stream output)
-				: base(output)
-			{
-			}
-
-#if PORTABLE
-            protected override void Dispose(bool disposing)
-            {
-                if (disposing)
-                {
-				    Finish();
-                    return;
-                }
-                base.Dispose(disposing);
-            }
-#else
-            public override void Close()
-			{
-				Finish();
-			}
-#endif
-		}
-
-		private class SafeZOutputStream : ZOutputStream
-		{
-			public SafeZOutputStream(Stream output, int level, bool nowrap)
-				: base(output, level, nowrap)
-			{
-			}
-
-#if PORTABLE
-            protected override void Dispose(bool disposing)
-            {
-                if (disposing)
-                {
-				    Finish();
-				    End();
-                    return;
-                }
-                base.Dispose(disposing);
-            }
-#else
-            public override void Close()
-			{
-				Finish();
-				End();
-			}
-#endif
-		}
 	}
 }
diff --git a/crypto/src/openpgp/PgpEncryptedData.cs b/crypto/src/openpgp/PgpEncryptedData.cs
index d3220fe86..bad4cb8cd 100644
--- a/crypto/src/openpgp/PgpEncryptedData.cs
+++ b/crypto/src/openpgp/PgpEncryptedData.cs
@@ -54,6 +54,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             {
 				Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                return Read(buffer.AsSpan(offset, count));
+#else
                 int avail = bufEnd - bufStart;
 
                 int pos = offset;
@@ -73,8 +76,34 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 bufStart += count;
 
                 return pos + count - offset;
+#endif
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override int Read(Span<byte> buffer)
+            {
+                int avail = bufEnd - bufStart;
+
+                int pos = 0, count = buffer.Length;
+                while (count > avail)
+                {
+                    lookAhead.AsSpan(bufStart, avail).CopyTo(buffer[pos..]);
+
+                    bufStart += avail;
+                    pos += avail;
+                    count -= avail;
+
+                    if ((avail = FillBuffer()) < 1)
+                        return pos;
+                }
+
+                lookAhead.AsSpan(bufStart, count).CopyTo(buffer[pos..]);
+                bufStart += count;
+
+                return pos + count;
+            }
+#endif
+
             public override int ReadByte()
             {
                 if (bufStart < bufEnd)
@@ -140,7 +169,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             //
 			byte[] lookAhead = truncStream.GetLookAhead();
 
-			IDigest hash = dIn.ReadDigest();
+			IDigest hash = dIn.ReadDigest;
 			hash.BlockUpdate(lookAhead, 0, 2);
 			byte[] digest = DigestUtilities.DoFinal(hash);
 
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index d16f328d9..69e0d5dbc 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -2,17 +2,21 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
+using Org.BouncyCastle.Asn1.EdEC;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>Generator for encrypted objects.</remarks>
+    /// <remarks>Generator for encrypted objects.</remarks>
     public class PgpEncryptedDataGenerator
 		: IStreamGenerator
     {
@@ -99,54 +103,108 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
             private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
             {
+                var cryptoPublicKey = pubKey.GetKey();
+
                 if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
                 {
                     IBufferedCipher c;
 				    switch (pubKey.Algorithm)
                     {
-                        case PublicKeyAlgorithmTag.RsaEncrypt:
-                        case PublicKeyAlgorithmTag.RsaGeneral:
-                            c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
-                            break;
-                        case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                        case PublicKeyAlgorithmTag.ElGamalGeneral:
-                            c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
-                            break;
-                        case PublicKeyAlgorithmTag.Dsa:
-                            throw new PgpException("Can't use DSA for encryption.");
-                        case PublicKeyAlgorithmTag.ECDsa:
-                            throw new PgpException("Can't use ECDSA for encryption.");
-                        default:
-                            throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+                        c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
+                        break;
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+                        c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+                        break;
+                    case PublicKeyAlgorithmTag.Dsa:
+                        throw new PgpException("Can't use DSA for encryption.");
+                    case PublicKeyAlgorithmTag.ECDsa:
+                        throw new PgpException("Can't use ECDSA for encryption.");
+                    case PublicKeyAlgorithmTag.EdDsa:
+                        throw new PgpException("Can't use EdDSA for encryption.");
+                    default:
+                        throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
                     }
 
-                    AsymmetricKeyParameter akp = pubKey.GetKey();
-				    c.Init(true, new ParametersWithRandom(akp, random));
+				    c.Init(true, new ParametersWithRandom(cryptoPublicKey, random));
                     return c.DoFinal(sessionInfo);
                 }
 
-                ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+                ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+                var curveOid = ecPubKey.CurveOid;
+
+                if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                    CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+                {
+                    X25519KeyPairGenerator gen = new X25519KeyPairGenerator();
+                    gen.Init(new X25519KeyGenerationParameters(random));
+
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
+
+                    X25519Agreement agreement = new X25519Agreement();
+                    agreement.Init(ephKp.Private);
+
+                    byte[] secret = new byte[agreement.AgreementSize];
+                    agreement.CalculateAgreement(cryptoPublicKey, secret, 0);
 
-                // Generate the ephemeral key pair
-                IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
-                gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random));
+                    byte[] ephPubEncoding = new byte[1 + X25519PublicKeyParameters.KeySize];
+                    ephPubEncoding[0] = 0x40;
+                    ((X25519PublicKeyParameters)ephKp.Public).Encode(ephPubEncoding, 1);
 
-                AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
-                ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private;
-                ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public;
+                    return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random);
+                }
+                else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                {
+                    X448KeyPairGenerator gen = new X448KeyPairGenerator();
+                    gen.Init(new X448KeyGenerationParameters(random));
+
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
+
+                    X448Agreement agreement = new X448Agreement();
+                    agreement.Init(ephKp.Private);
 
-                ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
-                ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
+                    byte[] secret = new byte[agreement.AgreementSize];
+                    agreement.CalculateAgreement(cryptoPublicKey, secret, 0);
 
-                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
+                    byte[] ephPubEncoding = new byte[1 + X448PublicKeyParameters.KeySize];
+                    ephPubEncoding[0] = 0x40;
+                    ((X448PublicKeyParameters)ephKp.Public).Encode(ephPubEncoding, 1);
 
-                IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
+                    return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random);
+                }
+                else
+                {
+                    // Generate the ephemeral key pair
+                    ECDomainParameters ecParams = ((ECPublicKeyParameters)cryptoPublicKey).Parameters;
+                    ECKeyPairGenerator gen = new ECKeyPairGenerator();
+                    gen.Init(new ECKeyGenerationParameters(ecParams, random));
+
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
+
+                    ECDHBasicAgreement agreement = new ECDHBasicAgreement();
+                    agreement.Init(ephKp.Private);
+                    BigInteger S = agreement.CalculateAgreement(cryptoPublicKey);
+                    byte[] secret = BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), S);
+
+                    byte[] ephPubEncoding = ((ECPublicKeyParameters)ephKp.Public).Q.GetEncoded(false);
+                    return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random);
+                }
+            }
+
+            private byte[] EncryptSessionInfo(ECDHPublicBcpgKey ecPubKey, byte[] sessionInfo, byte[] secret,
+                byte[] ephPubEncoding, SecureRandom random)
+            {
+                var key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, secret));
+
+                IWrapper w = PgpUtilities.CreateWrapper(ecPubKey.SymmetricKeyAlgorithm);
                 w.Init(true, new ParametersWithRandom(key, random));
 
                 byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo, sessionKeyObfuscation);
 
                 byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length);
-                byte[] VB = new MPInteger(new BigInteger(1, ephPub.Q.GetEncoded(false))).GetEncoded();
+                byte[] VB = new MPInteger(new BigInteger(1, ephPubEncoding)).GetEncoded();
 
                 byte[] rv = new byte[VB.Length + 1 + C.Length];
 
@@ -165,7 +223,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                 case PublicKeyAlgorithmTag.RsaEncrypt:
                 case PublicKeyAlgorithmTag.RsaGeneral:
-                    data = new byte[][] { ConvertToEncodedMpi(encryptedSessionInfo) };
+                    data = new byte[1][] { ConvertToEncodedMpi(encryptedSessionInfo) };
                     break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
@@ -176,13 +234,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     Array.Copy(encryptedSessionInfo, 0, b1, 0, halfLength);
                     Array.Copy(encryptedSessionInfo, halfLength, b2, 0, halfLength);
 
-                    data = new byte[][] {
+                    data = new byte[2][] {
                         ConvertToEncodedMpi(b1),
                         ConvertToEncodedMpi(b2),
                     };
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
-                    data = new byte[][]{ encryptedSessionInfo };
+                    data = new byte[1][]{ encryptedSessionInfo };
                     break;
                 default:
                     throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
@@ -219,7 +277,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			SymmetricKeyAlgorithmTag encAlgorithm)
 		{
 			this.defAlgorithm = encAlgorithm;
-			this.rand = new SecureRandom();
+            this.rand = CryptoServicesRegistrar.GetSecureRandom();
 		}
 
 		public PgpEncryptedDataGenerator(
@@ -228,42 +286,51 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		{
 			this.defAlgorithm = encAlgorithm;
 			this.withIntegrityPacket = withIntegrityPacket;
-			this.rand = new SecureRandom();
-		}
+            this.rand = CryptoServicesRegistrar.GetSecureRandom();
+        }
 
-		/// <summary>Existing SecureRandom constructor.</summary>
-		/// <param name="encAlgorithm">The symmetric algorithm to use.</param>
-		/// <param name="rand">Source of randomness.</param>
+        /// <summary>Existing SecureRandom constructor.</summary>
+        /// <param name="encAlgorithm">The symmetric algorithm to use.</param>
+        /// <param name="random">Source of randomness.</param>
         public PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag	encAlgorithm,
-            SecureRandom				rand)
+            SecureRandom				random)
         {
+            if (random == null)
+                throw new ArgumentNullException(nameof(random));
+
             this.defAlgorithm = encAlgorithm;
-            this.rand = rand;
+            this.rand = random;
         }
 
 		/// <summary>Creates a cipher stream which will have an integrity packet associated with it.</summary>
         public PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag	encAlgorithm,
             bool						withIntegrityPacket,
-            SecureRandom				rand)
+            SecureRandom				random)
         {
+            if (random == null)
+                throw new ArgumentNullException(nameof(random));
+
             this.defAlgorithm = encAlgorithm;
-            this.rand = rand;
+            this.rand = random;
             this.withIntegrityPacket = withIntegrityPacket;
         }
 
-		/// <summary>Base constructor.</summary>
-		/// <param name="encAlgorithm">The symmetric algorithm to use.</param>
-		/// <param name="rand">Source of randomness.</param>
-		/// <param name="oldFormat">PGP 2.6.x compatibility required.</param>
+        /// <summary>Base constructor.</summary>
+        /// <param name="encAlgorithm">The symmetric algorithm to use.</param>
+        /// <param name="random">Source of randomness.</param>
+        /// <param name="oldFormat">PGP 2.6.x compatibility required.</param>
         public PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag	encAlgorithm,
-            SecureRandom				rand,
+            SecureRandom				random,
             bool						oldFormat)
         {
+            if (random == null)
+                throw new ArgumentNullException(nameof(random));
+
             this.defAlgorithm = encAlgorithm;
-            this.rand = rand;
+            this.rand = random;
             this.oldFormat = oldFormat;
         }
 
@@ -480,8 +547,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 				if (withIntegrityPacket)
                 {
-					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
-					IDigest digest = DigestUtilities.GetDigest(digestName);
+                    IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 					myOut = digestOut = new DigestStream(myOut, null, digest);
                 }
 
@@ -561,7 +627,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     digestOut.Flush();
 
 					// TODO
-					byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest());
+					byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest);
 					cOut.Write(dig, 0, dig.Length);
                 }
 
diff --git a/crypto/src/openpgp/PgpKdfParameters.cs b/crypto/src/openpgp/PgpKdfParameters.cs
new file mode 100644
index 000000000..c78448939
--- /dev/null
+++ b/crypto/src/openpgp/PgpKdfParameters.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    internal sealed class PgpKdfParameters
+        //: IPgpAlgorithmParameters
+    {
+        private readonly HashAlgorithmTag m_hashAlgorithm;
+        private readonly SymmetricKeyAlgorithmTag m_symmetricWrapAlgorithm;
+
+        public PgpKdfParameters(HashAlgorithmTag hashAlgorithm, SymmetricKeyAlgorithmTag symmetricWrapAlgorithm)
+        {
+            m_hashAlgorithm = hashAlgorithm;
+            m_symmetricWrapAlgorithm = symmetricWrapAlgorithm;
+        }
+
+        public HashAlgorithmTag HashAlgorithm => m_hashAlgorithm;
+
+        public SymmetricKeyAlgorithmTag SymmetricWrapAlgorithm => m_symmetricWrapAlgorithm;
+    }
+}
diff --git a/crypto/src/openpgp/PgpObjectFactory.cs b/crypto/src/openpgp/PgpObjectFactory.cs
index f7bf89507..068b85154 100644
--- a/crypto/src/openpgp/PgpObjectFactory.cs
+++ b/crypto/src/openpgp/PgpObjectFactory.cs
@@ -72,9 +72,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             case PacketTag.PublicKey:
                 return new PgpPublicKeyRing(bcpgIn);
-			// TODO Make PgpPublicKey a PgpObject or return a PgpPublicKeyRing
-			//case PacketTag.PublicSubkey:
-			//	return PgpPublicKeyRing.ReadSubkey(bcpgIn);
+			case PacketTag.PublicSubkey:
+				return PgpPublicKeyRing.ReadSubkey(bcpgIn);
             case PacketTag.CompressedData:
                 return new PgpCompressedData(bcpgIn);
             case PacketTag.LiteralData:
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index 2fab5137e..c14e72bf7 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -2,7 +2,9 @@ using System;
 using System.IO;
 
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
@@ -11,10 +13,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
     {
         private static OnePassSignaturePacket Cast(Packet packet)
         {
-            if (!(packet is OnePassSignaturePacket))
-                throw new IOException("unexpected packet in stream: " + packet);
+            if (packet is OnePassSignaturePacket onePassSignaturePacket)
+                return onePassSignaturePacket;
 
-            return (OnePassSignaturePacket)packet;
+            throw new IOException("unexpected packet in stream: " + packet);
         }
 
         private readonly OnePassSignaturePacket sigPack;
@@ -36,15 +38,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         }
 
 		/// <summary>Initialise the signature object for verification.</summary>
-        public void InitVerify(
-            PgpPublicKey pubKey)
+        public void InitVerify(PgpPublicKey pubKey)
         {
 			lastb = 0;
+            AsymmetricKeyParameter key = pubKey.GetKey();
 
-			try
+            try
 			{
-				sig = SignerUtilities.GetSigner(
-					PgpUtilities.GetSignatureName(sigPack.KeyAlgorithm, sigPack.HashAlgorithm));
+				sig = PgpUtilities.CreateSigner(sigPack.KeyAlgorithm, sigPack.HashAlgorithm, key);
 			}
 			catch (Exception e)
 			{
@@ -53,7 +54,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			try
             {
-                sig.Init(false, pubKey.GetKey());
+                sig.Init(false, key);
             }
 			catch (InvalidKeyException e)
             {
@@ -61,12 +62,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		public void Update(
-            byte b)
+		public void Update(byte b)
         {
 			if (signatureType == PgpSignature.CanonicalTextDocument)
 			{
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
 			}
 			else
 			{
@@ -74,18 +74,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			}
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
@@ -96,51 +95,57 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
 			sig.Update((byte)'\r');
 			sig.Update((byte)'\n');
 		}
 
-		public void Update(
-            byte[] bytes)
+		public void Update(params byte[] bytes)
+        {
+            Update(bytes, 0, bytes.Length);
+        }
+
+        public void Update(byte[] bytes, int off, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(bytes.AsSpan(off, length));
+#else
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                for (int i = 0; i != bytes.Length; i++)
+                int finish = off + length;
+
+                for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(bytes[i]);
+                    DoCanonicalUpdateByte(bytes[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(bytes, 0, bytes.Length);
+                sig.BlockUpdate(bytes, off, length);
             }
+#endif
         }
 
-        public void Update(
-            byte[]  bytes,
-            int     off,
-            int     length)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                int finish = off + length;
-
-                for (int i = off; i != finish; i++)
+                for (int i = 0; i < input.Length; ++i)
                 {
-                    doCanonicalUpdateByte(bytes[i]);
+                    DoCanonicalUpdateByte(input[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(bytes, off, length);
+                sig.BlockUpdate(input);
             }
         }
+#endif
 
-		/// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
-        public bool Verify(
-            PgpSignature pgpSig)
+        /// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
+        public bool Verify(PgpSignature pgpSig)
         {
             byte[] trailer = pgpSig.GetSignatureTrailer();
 
@@ -171,15 +176,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 		public byte[] GetEncoded()
         {
-            MemoryStream bOut = new MemoryStream();
+            var bOut = new MemoryStream();
 
             Encode(bOut);
 
             return bOut.ToArray();
         }
 
-		public void Encode(
-            Stream outStr)
+		public void Encode(Stream outStr)
         {
             BcpgOutputStream.Wrap(outStr).WritePacket(sigPack);
         }
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index f43f2f512..7920f54ea 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -97,10 +97,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				{
 					truncStream = new TruncatedStream(encStream);
 
-					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
-					IDigest digest = DigestUtilities.GetDigest(digestName);
+                    IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 
-					encStream = new DigestStream(truncStream, digest, null);
+                    encStream = new DigestStream(truncStream, digest, null);
 				}
 
 				if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 09c8b9743..400cda071 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -3,13 +3,18 @@ using System.Collections.Generic;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
+using Org.BouncyCastle.Asn1.EdEC;
 using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Math.EC.Rfc7748;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
@@ -18,7 +23,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
     /// <remarks>General class to handle a PGP public key object.</remarks>
     public class PgpPublicKey
+        : PgpObject
     {
+        // We default to these as they are specified as mandatory in RFC 6631.
+        private static readonly PgpKdfParameters DefaultKdfParameters = new PgpKdfParameters(HashAlgorithmTag.Sha256,
+            SymmetricKeyAlgorithmTag.Aes128);
+
         public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
         {
             IBcpgKey key = publicPk.Key;
@@ -30,7 +40,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 try
                 {
-                    digest = DigestUtilities.GetDigest("MD5");
+                    digest = PgpUtilities.CreateDigest(HashAlgorithmTag.MD5);
+
                     UpdateDigest(digest, rK.Modulus);
                     UpdateDigest(digest, rK.PublicExponent);
                 }
@@ -45,7 +56,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     byte[] kBytes = publicPk.GetEncodedContents();
 
-                    digest = DigestUtilities.GetDigest("SHA1");
+                    digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 
                     digest.Update(0x99);
                     digest.Update((byte)(kBytes.Length >> 8));
@@ -124,27 +135,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength;
                 }
-                else if (key is ECPublicBcpgKey)
+                else if (key is EdDsaPublicBcpgKey eddsaK)
                 {
-                    DerObjectIdentifier curveOid = ((ECPublicBcpgKey)key).CurveOid;
-                    if (GnuObjectIdentifiers.Ed25519.Equals(curveOid)
-                        //|| CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)
-                        )
+                    var curveOid = eddsaK.CurveOid;
+                    if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) ||
+                        GnuObjectIdentifiers.Ed25519.Equals(curveOid) ||
+                        EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                        CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
                     {
                         this.keyStrength = 256;
                     }
+                    else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid) ||
+                        EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                    {
+                        this.keyStrength = 448;
+                    }
                     else
                     {
-                        X9ECParametersHolder ecParameters = ECKeyPairGenerator.FindECCurveByOidLazy(curveOid);
-
-                        if (ecParameters != null)
-                        {
-                            this.keyStrength = ecParameters.Curve.FieldSize;
-                        }
-                        else
-                        {
-                            this.keyStrength = -1; // unknown
-                        }
+                        this.keyStrength = -1; // unknown
+                    }
+                }
+                else if (key is ECPublicBcpgKey ecK)
+                {
+                    var curveOid = ecK.CurveOid;
+                    X9ECParametersHolder ecParameters = ECKeyPairGenerator.FindECCurveByOidLazy(curveOid);
+
+                    if (ecParameters != null)
+                    {
+                        this.keyStrength = ecParameters.Curve.FieldSize;
+                    }
+                    else
+                    {
+                        this.keyStrength = -1; // unknown
                     }
                 }
             }
@@ -165,7 +187,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
         {
             if (pubKey.IsPrivate)
-                throw new ArgumentException("Expected a public key", "pubKey");
+                throw new ArgumentException("Expected a public key", nameof(pubKey));
 
             IBcpgKey bcpgKey;
             if (pubKey is RsaKeyParameters rK)
@@ -178,6 +200,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
             }
+            else if (pubKey is ElGamalPublicKeyParameters eK)
+            {
+                ElGamalParameters eS = eK.Parameters;
+
+                bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+            }
             else if (pubKey is ECPublicKeyParameters ecK)
             {
                 if (algorithm == PublicKeyAlgorithmTag.ECDH)
@@ -194,11 +222,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     throw new PgpException("unknown EC algorithm");
                 }
             }
-            else if (pubKey is ElGamalPublicKeyParameters eK)
+            else if (pubKey is Ed25519PublicKeyParameters ed25519PubKey)
             {
-                ElGamalParameters eS = eK.Parameters;
+                byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize];
+                pointEnc[0] = 0x40;
+                ed25519PubKey.Encode(pointEnc, 1);
+                bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc));
+            }
+            else if (pubKey is Ed448PublicKeyParameters ed448PubKey)
+            {
+                byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize];
+                ed448PubKey.Encode(pointEnc, 0);
+                bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc));
+            }
+            else if (pubKey is X25519PublicKeyParameters x25519PubKey)
+            {
+                byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize];
+                pointEnc[0] = 0x40;
+                x25519PubKey.Encode(pointEnc, 1);
 
-                bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+                PgpKdfParameters kdfParams = DefaultKdfParameters;
+
+                bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc),
+                    kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+            }
+            else if (pubKey is X448PublicKeyParameters x448PubKey)
+            {
+                byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize];
+                x448PubKey.Encode(pointEnc, 0);
+
+                PgpKdfParameters kdfParams = DefaultKdfParameters;
+
+                bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc),
+                    kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
             }
             else
             {
@@ -473,24 +529,89 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             {
                 switch (publicPk.Algorithm)
                 {
-                    case PublicKeyAlgorithmTag.RsaEncrypt:
-                    case PublicKeyAlgorithmTag.RsaGeneral:
-                    case PublicKeyAlgorithmTag.RsaSign:
-                        RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key;
-                        return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
-                    case PublicKeyAlgorithmTag.Dsa:
-                        DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key;
-                        return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
-                    case PublicKeyAlgorithmTag.ECDsa:
-                        return GetECKey("ECDSA");
-                    case PublicKeyAlgorithmTag.ECDH:
-                        return GetECKey("ECDH");
-                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                    case PublicKeyAlgorithmTag.ElGamalGeneral:
-                        ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
-                        return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
-                    default:
-                        throw new PgpException("unknown public key algorithm encountered");
+                case PublicKeyAlgorithmTag.RsaEncrypt:
+                case PublicKeyAlgorithmTag.RsaGeneral:
+                case PublicKeyAlgorithmTag.RsaSign:
+                    RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key;
+                    return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
+                case PublicKeyAlgorithmTag.Dsa:
+                    DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key;
+                    return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
+                case PublicKeyAlgorithmTag.ECDsa:
+                    ECDsaPublicBcpgKey ecdsaK = (ECDsaPublicBcpgKey)publicPk.Key;
+                    return GetECKey("ECDSA", ecdsaK);
+                case PublicKeyAlgorithmTag.ECDH:
+                {
+                    ECDHPublicBcpgKey ecdhK = (ECDHPublicBcpgKey)publicPk.Key;
+                    var curveOid = ecdhK.CurveOid;
+
+                    if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                        CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + X25519.PointSize, ecdhK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid X25519 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + X448.PointSize, ecdhK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid X448 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else
+                    {
+                        return GetECKey("ECDH", ecdhK);
+                    }
+                }
+                case PublicKeyAlgorithmTag.EdDsa:
+                {
+                    EdDsaPublicBcpgKey eddsaK = (EdDsaPublicBcpgKey)publicPk.Key;
+                    var curveOid = eddsaK.CurveOid;
+
+                    if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) ||
+                        GnuObjectIdentifiers.Ed25519.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + Ed25519.PublicKeySize, eddsaK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid Ed25519 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + Ed448.PublicKeySize, eddsaK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid Ed448 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else 
+                    {
+                        throw new InvalidOperationException();
+                    }
+                }
+                case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                case PublicKeyAlgorithmTag.ElGamalGeneral:
+                    ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
+                    return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
+                default:
+                    throw new PgpException("unknown public key algorithm encountered");
                 }
             }
             catch (PgpException e)
@@ -503,11 +624,22 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        private ECPublicKeyParameters GetECKey(string algorithm)
+        private ECPublicKeyParameters GetECKey(string algorithm, ECPublicBcpgKey ecK)
         {
-            ECPublicBcpgKey ecK = (ECPublicBcpgKey)publicPk.Key;
             X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(ecK.CurveOid);
-            ECPoint q = x9.Curve.DecodePoint(BigIntegers.AsUnsignedByteArray(ecK.EncodedPoint));
+            BigInteger encodedPoint = ecK.EncodedPoint;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int encodedLength = BigIntegers.GetUnsignedByteLength(encodedPoint);
+            Span<byte> encoding = encodedLength <= 512
+                ? stackalloc byte[encodedLength]
+                : new byte[encodedLength];
+            BigIntegers.AsUnsignedByteArray(encodedPoint, encoding);
+            ECPoint q = x9.Curve.DecodePoint(encoding);
+#else
+            ECPoint q = x9.Curve.DecodePoint(BigIntegers.AsUnsignedByteArray(encodedPoint));
+#endif
+
             return new ECPublicKeyParameters(algorithm, q, ecK.CurveOid);
         }
 
@@ -551,7 +683,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public IEnumerable<PgpSignature> GetSignaturesForId(string id)
         {
             if (id == null)
-                throw new ArgumentNullException("id");
+                throw new ArgumentNullException(nameof(id));
 
             var result = new List<PgpSignature>();
             bool userIdFound = false;
@@ -568,13 +700,48 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return userIdFound ? CollectionUtilities.Proxy(result) : null;
         }
 
+        private IEnumerable<PgpSignature> GetSignaturesForID(UserIdPacket id)
+        {
+            var signatures = new List<PgpSignature>();
+            bool userIdFound = false;
+
+            for (int i = 0; i != ids.Count; i++)
+            {
+                if (id.Equals(ids[i]))
+                {
+                    userIdFound = true;
+                    signatures.AddRange(idSigs[i]);
+                }
+            }
+
+            return userIdFound ? signatures : null;
+        }
+
+        /// <summary>Return any signatures associated with the passed in key identifier keyID.</summary>
+        /// <param name="keyID">the key id to be matched.</param>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects issued by the key with keyID.</returns>
+        public IEnumerable<PgpSignature> GetSignaturesForKeyID(long keyID)
+        {
+            var sigs = new List<PgpSignature>();
+
+            foreach (var sig in GetSignatures())
+            {
+                if (sig.KeyId == keyID)
+                {
+                    sigs.Add(sig);
+                }
+            }
+
+            return CollectionUtilities.Proxy(sigs);
+        }
+
         /// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary>
         /// <param name="userAttributes">The vector of user attributes to be matched.</param>
         /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
         public IEnumerable<PgpSignature> GetSignaturesForUserAttribute(PgpUserAttributeSubpacketVector userAttributes)
         {
             if (userAttributes == null)
-                throw new ArgumentNullException("userAttributes");
+                throw new ArgumentNullException(nameof(userAttributes));
 
             var result = new List<PgpSignature>();
             bool attributeFound = false;
@@ -636,11 +803,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
          */
         public IEnumerable<PgpSignature> GetKeySignatures()
         {
-            var result = subSigs;
-            if (result == null)
-            {
-                result = new List<PgpSignature>(keySigs);
-            }
+            var result = subSigs ?? new List<PgpSignature>(keySigs);
 
             return CollectionUtilities.Proxy(result);
         }
@@ -935,56 +1098,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// <param name="key">The key the certifications are to be removed from.</param>
         /// <param name="certification">The certfication to be removed.</param>
         /// <returns>The modified key, null if the certification was not found.</returns>
-        public static PgpPublicKey RemoveCertification(
-            PgpPublicKey	key,
-            PgpSignature	certification)
+        public static PgpPublicKey RemoveCertification(PgpPublicKey	key, PgpSignature certification)
         {
-            PgpPublicKey returnKey = new PgpPublicKey(key);
-            var sigs = returnKey.subSigs != null
-                ?	returnKey.subSigs
-                :	returnKey.keySigs;
+            var returnKey = new PgpPublicKey(key);
+            var sigs = returnKey.subSigs ?? returnKey.keySigs;
 
-//			bool found = sigs.Remove(certification);
-            int pos = sigs.IndexOf(certification);
-            bool found = pos >= 0;
+            if (sigs.Remove(certification))
+                return returnKey;
 
-            if (found)
+            // TODO Java uses getRawUserIDs
+            foreach (string id in key.GetUserIds())
             {
-                sigs.RemoveAt(pos);
+                if (ContainsSignature(key.GetSignaturesForId(id), certification))
+                    return RemoveCertification(returnKey, id, certification);
             }
-            else
-            {
-                foreach (string id in key.GetUserIds())
-                {
-                    foreach (object sig in key.GetSignaturesForId(id))
-                    {
-                        // TODO Is this the right type of equality test?
-                        if (certification == sig)
-                        {
-                            found = true;
-                            returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
-                        }
-                    }
-                }
 
-                if (!found)
-                {
-                    foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
-                    {
-                        foreach (object sig in key.GetSignaturesForUserAttribute(id))
-                        {
-                            // TODO Is this the right type of equality test?
-                            if (certification == sig)
-                            {
-                                found = true;
-                                returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
-                            }
-                        }
-                    }
-                }
+            foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
+            {
+                if (ContainsSignature(key.GetSignaturesForUserAttribute(id), certification))
+                    return RemoveCertification(returnKey, id, certification);
             }
 
             return returnKey;
         }
+
+        private static bool ContainsSignature(IEnumerable<PgpSignature> signatures, PgpSignature signature)
+        {
+            foreach (PgpSignature candidate in signatures)
+            {
+                if (signature == candidate)
+                    return true;
+            }
+            return false;
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 04fe3ad37..8c6fcda53 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -1,19 +1,20 @@
 using System;
 using System.IO;
 
-using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Asn1.Cryptlib;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.IO;
-using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Asn1.EdEC;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>A public key encrypted data object.</remarks>
+    /// <remarks>A public key encrypted data object.</remarks>
     public class PgpPublicKeyEncryptedData
         : PgpEncryptedData
     {
@@ -139,10 +140,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     truncStream = new TruncatedStream(encStream);
 
-					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
-					IDigest digest = DigestUtilities.GetDigest(digestName);
+                    IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 
-					encStream = new DigestStream(truncStream, digest, null);
+                    encStream = new DigestStream(truncStream, digest, null);
                 }
 
 				if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
@@ -189,76 +189,114 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		{
             byte[][] secKeyData = keyData.GetEncSessionKey();
 
-            if (keyData.Algorithm == PublicKeyAlgorithmTag.ECDH)
+            if (keyData.Algorithm != PublicKeyAlgorithmTag.ECDH)
             {
-                ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
-                X9ECParameters x9Params = ECKeyPairGenerator.FindECCurveByOid(ecKey.CurveOid);
+                IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
+
+                try
+			    {
+                    cipher.Init(false, privKey.Key);
+			    }
+			    catch (InvalidKeyException e)
+			    {
+				    throw new PgpException("error setting asymmetric cipher", e);
+			    }
+
+                if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
+				    || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
+			    {
+                    byte[] bi = secKeyData[0];
+
+                    cipher.ProcessBytes(bi, 2, bi.Length - 2);
+			    }
+			    else
+			    {
+				    ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
+				    int size = (k.Parameters.P.BitLength + 7) / 8;
+
+                    ProcessEncodedMpi(cipher, size, secKeyData[0]);
+                    ProcessEncodedMpi(cipher, size, secKeyData[1]);
+			    }
+
+                try
+			    {
+                    return cipher.DoFinal();
+			    }
+			    catch (Exception e)
+			    {
+				    throw new PgpException("exception decrypting secret key", e);
+			    }
+            }
 
-                byte[] enc = secKeyData[0];
+            ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
+            byte[] enc = secKeyData[0];
 
-                int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
-                if ((2 + pLen + 1) > enc.Length) 
-                    throw new PgpException("encoded length out of range");
+            int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+            if ((2 + pLen + 1) > enc.Length) 
+                throw new PgpException("encoded length out of range");
 
-                byte[] pEnc = new byte[pLen];
-                Array.Copy(enc, 2, pEnc, 0, pLen);
+            byte[] pEnc = new byte[pLen];
+            Array.Copy(enc, 2, pEnc, 0, pLen);
 
-                int keyLen = enc[pLen + 2];
-                if ((2 + pLen + 1 + keyLen) > enc.Length)
-                    throw new PgpException("encoded length out of range");
+            int keyLen = enc[pLen + 2];
+            if ((2 + pLen + 1 + keyLen) > enc.Length)
+                throw new PgpException("encoded length out of range");
 
-                byte[] keyEnc = new byte[keyLen];
-                Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
+            byte[] keyEnc = new byte[keyLen];
+            Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
 
-                ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc);
+            var curveOid = ecPubKey.CurveOid;
+            byte[] secret;
 
-                ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key;
-                ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize();
+            if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+            {
+                // skip the 0x40 header byte.
+                if (pEnc.Length != (1 + X25519PublicKeyParameters.KeySize) || 0x40 != pEnc[0])
+                    throw new ArgumentException("Invalid X25519 public key");
 
-                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S));
+                X25519PublicKeyParameters ephPub = new X25519PublicKeyParameters(pEnc, 1);
 
-                IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
-                w.Init(false, key);
+                X25519Agreement agreement = new X25519Agreement();
+                agreement.Init(privKey.Key);
 
-                return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length));
+                secret = new byte[agreement.AgreementSize];
+                agreement.CalculateAgreement(ephPub, secret, 0);
             }
+            else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+            {
+                // skip the 0x40 header byte.
+                if (pEnc.Length != (1 + X448PublicKeyParameters.KeySize) || 0x40 != pEnc[0])
+                    throw new ArgumentException("Invalid X448 public key");
 
-            IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
+                X448PublicKeyParameters ephPub = new X448PublicKeyParameters(pEnc, 1);
 
-            try
-			{
-                cipher.Init(false, privKey.Key);
-			}
-			catch (InvalidKeyException e)
-			{
-				throw new PgpException("error setting asymmetric cipher", e);
-			}
+                X448Agreement agreement = new X448Agreement();
+                agreement.Init(privKey.Key);
 
-            if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
-				|| keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
-			{
-                byte[] bi = secKeyData[0];
+                secret = new byte[agreement.AgreementSize];
+                agreement.CalculateAgreement(ephPub, secret, 0);
+            }
+            else
+            {
+                ECDomainParameters ecParameters = ((ECPrivateKeyParameters)privKey.Key).Parameters;
 
-                cipher.ProcessBytes(bi, 2, bi.Length - 2);
-			}
-			else
-			{
-				ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
-				int size = (k.Parameters.P.BitLength + 7) / 8;
+                ECPublicKeyParameters ephPub = new ECPublicKeyParameters(ecParameters.Curve.DecodePoint(pEnc),
+                    ecParameters);
 
-                ProcessEncodedMpi(cipher, size, secKeyData[0]);
-                ProcessEncodedMpi(cipher, size, secKeyData[1]);
-			}
+                ECDHBasicAgreement agreement = new ECDHBasicAgreement();
+                agreement.Init(privKey.Key);
+                BigInteger S = agreement.CalculateAgreement(ephPub);
+                secret = BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), S);
+            }
 
-            try
-			{
-                return cipher.DoFinal();
-			}
-			catch (Exception e)
-			{
-				throw new PgpException("exception decrypting secret key", e);
-			}
-		}
+            KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, secret));
+
+            IWrapper w = PgpUtilities.CreateWrapper(ecPubKey.SymmetricKeyAlgorithm);
+            w.Init(false, key);
+
+            return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length));
+        }
 
         private static void ProcessEncodedMpi(IBufferedCipher cipher, int size, byte[] mpiEnc)
         {
diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs
index 4aa15384c..ebbb95634 100644
--- a/crypto/src/openpgp/PgpPublicKeyRing.cs
+++ b/crypto/src/openpgp/PgpPublicKeyRing.cs
@@ -68,19 +68,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// <summary>Return the first public key in the ring.</summary>
         public virtual PgpPublicKey GetPublicKey()
         {
-            return (PgpPublicKey) keys[0];
+            return keys[0];
         }
 
         /// <summary>Return the public key referred to by the passed in key ID if it is present.</summary>
-        public virtual PgpPublicKey GetPublicKey(
-            long keyId)
+        public virtual PgpPublicKey GetPublicKey(long keyId)
         {
             foreach (PgpPublicKey k in keys)
             {
                 if (keyId == k.KeyId)
-                {
                     return k;
-                }
             }
 
             return null;
@@ -168,23 +165,24 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// <returns>A new <c>PgpPublicKeyRing</c>, or null if pubKey is not found.</returns>
         public static PgpPublicKeyRing RemovePublicKey(PgpPublicKeyRing pubRing, PgpPublicKey pubKey)
         {
-            var keys = new List<PgpPublicKey>(pubRing.keys);
+            int count = pubRing.keys.Count;
+            long keyID = pubKey.KeyId;
+
+            var result = new List<PgpPublicKey>(count);
             bool found = false;
 
-            // TODO Is there supposed to be at most a single match?
-            int pos = keys.Count;
-            while (--pos >= 0)
+            foreach (var key in pubRing.keys)
             {
-                PgpPublicKey key = keys[pos];
-
-                if (key.KeyId == pubKey.KeyId)
+                if (key.KeyId == keyID)
                 {
                     found = true;
-                    keys.RemoveAt(pos);
+                    continue;
                 }
+
+                result.Add(key);
             }
 
-            return found ? new PgpPublicKeyRing(keys) : null;
+            return found ? new PgpPublicKeyRing(result) : null;
         }
 
         internal static PublicKeyPacket ReadPublicKeyPacket(BcpgInputStream bcpgInput)
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 51a45703a..0103cc187 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -2,10 +2,17 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
+using Org.BouncyCastle.Asn1.EdEC;
+using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
@@ -13,6 +20,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
     /// <remarks>General class to handle a PGP secret key object.</remarks>
     public class PgpSecretKey
+        : PgpObject
     {
         private readonly SecretKeyPacket	secret;
         private readonly PgpPublicKey		pub;
@@ -52,10 +60,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     secKey = new DsaSecretBcpgKey(dsK.X);
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
+                {
+                    if (privKey.Key is ECPrivateKeyParameters ecdhK)
+                    {
+                        secKey = new ECSecretBcpgKey(ecdhK.D);
+                    }
+                    else
+                    {
+                        // 'reverse' because the native format for X25519 private keys is little-endian
+                        X25519PrivateKeyParameters xK = (X25519PrivateKeyParameters)privKey.Key;
+                        secKey = new ECSecretBcpgKey(new BigInteger(1, Arrays.ReverseInPlace(xK.GetEncoded())));
+                    }
+                    break;
+                }
                 case PublicKeyAlgorithmTag.ECDsa:
                     ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
                     secKey = new ECSecretBcpgKey(ecK.D);
                     break;
+                case PublicKeyAlgorithmTag.EdDsa:
+                {
+                    if (privKey.Key is Ed25519PrivateKeyParameters ed25519K)
+                    {
+                        secKey = new EdSecretBcpgKey(new BigInteger(1, ed25519K.GetEncoded()));
+                    }
+                    else if (privKey.Key is Ed448PrivateKeyParameters ed448K)
+                    {
+                        secKey = new EdSecretBcpgKey(new BigInteger(1, ed448K.GetEncoded()));
+                    }
+                    else
+                    {
+                        throw new PgpException("unknown EdDSA key class");
+                    }
+                    break;
+                }
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
@@ -625,6 +662,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 return null;
 
             PublicKeyPacket pubPk = secret.PublicKeyPacket;
+
             try
             {
                 byte[] data = ExtractKeyData(rawPassPhrase, clearPassPhrase);
@@ -655,11 +693,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
-                    privateKey = GetECKey("ECDH", bcpgIn);
+                {
+                    ECDHPublicBcpgKey ecdhPub = (ECDHPublicBcpgKey)pubPk.Key;
+                    ECSecretBcpgKey ecdhPriv = new ECSecretBcpgKey(bcpgIn);
+                    var curveOid = ecdhPub.CurveOid;
+
+                    if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                        CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+                    {
+                        // 'reverse' because the native format for X25519 private keys is little-endian
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(Arrays.ReverseInPlace(BigIntegers.AsUnsignedByteArray(ecdhPriv.X)))));
+                    }
+                    else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                    {
+                        // 'reverse' because the native format for X448 private keys is little-endian
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(Arrays.ReverseInPlace(BigIntegers.AsUnsignedByteArray(ecdhPriv.X)))));
+                    }
+                    else
+                    {
+                        privateKey = new ECPrivateKeyParameters("ECDH", ecdhPriv.X, ecdhPub.CurveOid);
+                    }
                     break;
+                }
                 case PublicKeyAlgorithmTag.ECDsa:
-                    privateKey = GetECKey("ECDSA", bcpgIn);
+                {
+                    ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)pubPk.Key;
+                    ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn);
+
+                    privateKey = new ECPrivateKeyParameters("ECDSA", ecdsaPriv.X, ecdsaPub.CurveOid);
                     break;
+                }
+                case PublicKeyAlgorithmTag.EdDsa:
+                {
+                    EdDsaPublicBcpgKey eddsaPub = (EdDsaPublicBcpgKey)pubPk.Key;
+                    EdSecretBcpgKey ecdsaPriv = new EdSecretBcpgKey(bcpgIn);
+
+                    var curveOid = eddsaPub.CurveOid;
+                    if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) ||
+                        GnuObjectIdentifiers.Ed25519.Equals(curveOid))
+                    {
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(BigIntegers.AsUnsignedByteArray(Ed25519.SecretKeySize, ecdsaPriv.X))));
+                    }
+                    else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid))
+                    {
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(BigIntegers.AsUnsignedByteArray(Ed448.SecretKeySize, ecdsaPriv.X))));
+                    }
+                    else 
+                    {
+                        throw new InvalidOperationException();
+                    }
+                    break;
+                }
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
@@ -683,13 +775,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        private ECPrivateKeyParameters GetECKey(string algorithm, BcpgInputStream bcpgIn)
-        {
-            ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)secret.PublicKeyPacket.Key;
-            ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn);
-            return new ECPrivateKeyParameters(algorithm, ecdsaPriv.X, ecdsaPub.CurveOid);
-        }
-
         private static byte[] Checksum(
             bool	useSha1,
             byte[]	bytes,
@@ -699,7 +784,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             {
                 try
                 {
-                    IDigest dig = DigestUtilities.GetDigest("SHA1");
+                    IDigest dig = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
                     dig.BlockUpdate(bytes, 0, length);
                     return DigestUtilities.DoFinal(dig);
                 }
diff --git a/crypto/src/openpgp/PgpSecretKeyRing.cs b/crypto/src/openpgp/PgpSecretKeyRing.cs
index 637cb45f8..a070aa132 100644
--- a/crypto/src/openpgp/PgpSecretKeyRing.cs
+++ b/crypto/src/openpgp/PgpSecretKeyRing.cs
@@ -71,11 +71,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             // revocation and direct signatures
             var keySigs = ReadSignaturesAndTrust(bcpgInput);
 
-            IList<object> ids;
-            IList<TrustPacket> idTrusts;
-            IList<IList<PgpSignature>> idSigs;
-
-            ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+            ReadUserIDs(bcpgInput, out var ids, out var idTrusts, out var idSigs);
 
             keys.Add(new PgpSecretKey(secret,
                 new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs)));
@@ -119,6 +115,43 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return keys[0].PublicKey;
         }
 
+        /**
+         * Return any keys carrying a signature issued by the key represented by keyID.
+         *
+         * @param keyID the key id to be matched against.
+         * @return an iterator (possibly empty) of PGPPublicKey objects carrying signatures from keyID.
+         */
+        public IEnumerable<PgpPublicKey> GetKeysWithSignaturesBy(long keyID)
+        {
+            var keysWithSigs = new List<PgpPublicKey>();
+
+            foreach (var k in GetPublicKeys())
+            {
+                var sigIt = k.GetSignaturesForKeyID(keyID).GetEnumerator();
+
+                if (sigIt.MoveNext())
+                {
+                    keysWithSigs.Add(k);
+                }
+            }
+
+            return CollectionUtilities.Proxy(keysWithSigs);
+        }
+
+        public IEnumerable<PgpPublicKey> GetPublicKeys()
+        {
+            var pubKeys = new List<PgpPublicKey>();
+
+            foreach (var secretKey in keys)
+            {
+                pubKeys.Add(secretKey.PublicKey);
+            }
+
+            pubKeys.AddRange(extraPubKeys);
+
+            return CollectionUtilities.Proxy(pubKeys);
+        }
+
         /// <summary>Return the master private key.</summary>
         public PgpSecretKey GetSecretKey()
         {
@@ -156,7 +189,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
         public byte[] GetEncoded()
         {
-            MemoryStream bOut = new MemoryStream();
+            var bOut = new MemoryStream();
             Encode(bOut);
             return bOut.ToArray();
         }
@@ -165,7 +198,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             Stream outStr)
         {
             if (outStr == null)
-                throw new ArgumentNullException("outStr");
+                throw new ArgumentNullException(nameof(outStr));
 
             foreach (PgpSecretKey key in keys)
             {
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index aabe964b1..9b596f279 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -1,8 +1,10 @@
 using System;
 using System.IO;
-using Org.BouncyCastle.Asn1;
 
+using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Date;
@@ -14,10 +16,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
     {
         private static SignaturePacket Cast(Packet packet)
         {
-            if (!(packet is SignaturePacket))
-                throw new IOException("unexpected packet in stream: " + packet);
+			if (packet is SignaturePacket signaturePacket)
+				return signaturePacket;
 
-            return (SignaturePacket)packet;
+            throw new IOException("unexpected packet in stream: " + packet);
         }
 
         public const int BinaryDocument = 0x00;
@@ -56,24 +58,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
         }
 
-        internal PgpSignature(
-            SignaturePacket	sigPacket,
-            TrustPacket		trustPacket)
+        internal PgpSignature(SignaturePacket sigPacket, TrustPacket trustPacket)
         {
-			if (sigPacket == null)
-				throw new ArgumentNullException("sigPacket");
-
-			this.sigPck = sigPacket;
+			this.sigPck = sigPacket ?? throw new ArgumentNullException(nameof(sigPacket));
 			this.signatureType = sigPck.SignatureType;
 			this.trustPck = trustPacket;
         }
 
-		private void GetSig()
-        {
-            this.sig = SignerUtilities.GetSigner(
-				PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
-        }
-
 		/// <summary>The OpenPGP version number for this signature.</summary>
 		public int Version
 		{
@@ -98,17 +89,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return IsCertification(SignatureType);
         }
 
-		public void InitVerify(
-            PgpPublicKey pubKey)
+		public void InitVerify(PgpPublicKey pubKey)
         {
 			lastb = 0;
+			AsymmetricKeyParameter key = pubKey.GetKey();
+
             if (sig == null)
-            {
-                GetSig();
+			{
+                this.sig = PgpUtilities.CreateSigner(sigPck.KeyAlgorithm, sigPck.HashAlgorithm, key);
             }
+
             try
             {
-                sig.Init(false, pubKey.GetKey());
+                sig.Init(false, key);
             }
             catch (InvalidKeyException e)
             {
@@ -116,12 +109,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        public void Update(
-            byte b)
+        public void Update(byte b)
         {
             if (signatureType == CanonicalTextDocument)
             {
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
             }
             else
             {
@@ -129,18 +121,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
@@ -151,39 +142,56 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
 			sig.Update((byte)'\r');
 			sig.Update((byte)'\n');
 		}
 
-		public void Update(
-            params byte[] bytes)
+		public void Update(params byte[] bytes)
         {
 			Update(bytes, 0, bytes.Length);
         }
 
-		public void Update(
-            byte[]	bytes,
-            int		off,
-            int		length)
+		public void Update(byte[] bytes, int off, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(bytes.AsSpan(off, length));
+#else
             if (signatureType == CanonicalTextDocument)
             {
                 int finish = off + length;
 
 				for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(bytes[i]);
+                    DoCanonicalUpdateByte(bytes[i]);
                 }
             }
             else
             {
                 sig.BlockUpdate(bytes, off, length);
             }
+#endif
         }
 
-		public bool Verify()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            if (signatureType == CanonicalTextDocument)
+            {
+                for (int i = 0; i < input.Length; ++i)
+                {
+                    DoCanonicalUpdateByte(input[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(input);
+            }
+        }
+#endif
+
+        public bool Verify()
         {
             byte[] trailer = GetSignatureTrailer();
             sig.BlockUpdate(trailer, 0, trailer.Length);
@@ -234,7 +242,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			//
 			try
 			{
-				MemoryStream bOut = new MemoryStream();
+				var bOut = new MemoryStream();
 				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
 				{
 					packet.Encode(bOut);
@@ -248,7 +256,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			this.Update(sigPck.GetSignatureTrailer());
 
-			return sig.VerifySignature(this.GetSignature());
+			return sig.VerifySignature(GetSignature());
 		}
 
 		/// <summary>
@@ -345,15 +353,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 		public PgpSignatureSubpacketVector GetHashedSubPackets()
         {
-            return createSubpacketVector(sigPck.GetHashedSubPackets());
+            return CreateSubpacketVector(sigPck.GetHashedSubPackets());
         }
 
 		public PgpSignatureSubpacketVector GetUnhashedSubPackets()
         {
-            return createSubpacketVector(sigPck.GetUnhashedSubPackets());
+            return CreateSubpacketVector(sigPck.GetUnhashedSubPackets());
         }
 
-		private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
+		private static PgpSignatureSubpacketVector CreateSubpacketVector(SignatureSubpacket[] pcks)
 		{
 			return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
 		}
@@ -369,10 +377,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				{
 					signature = sigValues[0].Value.ToByteArrayUnsigned();
 				}
-				else
-				{
-					try
+                else if (KeyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
+                {
+					if (sigValues.Length != 2)
+						throw new InvalidOperationException();
+
+					BigInteger v0 = sigValues[0].Value;
+                    BigInteger v1 = sigValues[1].Value;
+
+					if (v0.BitLength == 918 &&
+                        v1.Equals(BigInteger.Zero) &&
+						v0.ShiftRight(912).Equals(BigInteger.ValueOf(0x40)))
+					{
+						signature = new byte[Ed448.SignatureSize];
+						BigIntegers.AsUnsignedByteArray(v0.ClearBit(918), signature, 0, signature.Length);
+					}
+					else if (v0.BitLength <= 256 && v1.BitLength <= 256)
 					{
+                        signature = new byte[Ed25519.SignatureSize];
+                        BigIntegers.AsUnsignedByteArray(sigValues[0].Value, signature,  0, 32);
+                        BigIntegers.AsUnsignedByteArray(sigValues[1].Value, signature, 32, 32);
+                    }
+                    else
+					{
+                        throw new InvalidOperationException();
+                    }
+                }
+                else
+                {
+                    if (sigValues.Length != 2)
+                        throw new InvalidOperationException();
+
+                    try
+                    {
 						signature = new DerSequence(
 							new DerInteger(sigValues[0].Value),
 							new DerInteger(sigValues[1].Value)).GetEncoded();
@@ -394,17 +431,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		// TODO Handle the encoding stuff by subclassing BcpgObject?
 		public byte[] GetEncoded()
         {
-            MemoryStream bOut = new MemoryStream();
+            var bOut = new MemoryStream();
 
 			Encode(bOut);
 
 			return bOut.ToArray();
         }
 
-		public void Encode(
-            Stream outStream)
+		public void Encode(Stream outStream)
         {
-            BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);
+            var bcpgOut = BcpgOutputStream.Wrap(outStream);
 
 			bcpgOut.WritePacket(sigPck);
 
@@ -414,8 +450,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		private byte[] GetEncodedPublicKey(
-			PgpPublicKey pubKey) 
+		private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey) 
 		{
 			try
 			{
@@ -436,13 +471,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
             switch (signatureType)
             {
-                case DefaultCertification:
-                case NoCertification:
-                case CasualCertification:
-                case PositiveCertification:
-                    return true;
-                default:
-                    return false;
+            case DefaultCertification:
+            case NoCertification:
+            case CasualCertification:
+            case PositiveCertification:
+                return true;
+            default:
+                return false;
             }
         }
     }
diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs
index c5309689f..12edf9f89 100644
--- a/crypto/src/openpgp/PgpSignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -5,19 +5,20 @@ using Org.BouncyCastle.Bcpg.Sig;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
 	/// <remarks>Generator for PGP signatures.</remarks>
-	// TODO Should be able to implement ISigner?
     public class PgpSignatureGenerator
     {
 		private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
 
-		private PublicKeyAlgorithmTag	keyAlgorithm;
-        private HashAlgorithmTag		hashAlgorithm;
+		private readonly PublicKeyAlgorithmTag keyAlgorithm;
+        private readonly HashAlgorithmTag hashAlgorithm;
+
         private PgpPrivateKey			privKey;
         private ISigner					sig;
         private IDigest					dig;
@@ -35,33 +36,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             this.keyAlgorithm = keyAlgorithm;
             this.hashAlgorithm = hashAlgorithm;
 
-			dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
-            sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+			dig = PgpUtilities.CreateDigest(hashAlgorithm);
         }
 
 		/// <summary>Initialise the generator for signing.</summary>
         public void InitSign(
             int				sigType,
-            PgpPrivateKey	key)
+            PgpPrivateKey	privKey)
         {
-			InitSign(sigType, key, null);
+			InitSign(sigType, privKey, null);
         }
 
 		/// <summary>Initialise the generator for signing.</summary>
 		public void InitSign(
 			int				sigType,
-			PgpPrivateKey	key,
+			PgpPrivateKey privKey,
 			SecureRandom	random)
 		{
-			this.privKey = key;
+			this.privKey = privKey;
 			this.signatureType = sigType;
 
-			try
+			AsymmetricKeyParameter key = privKey.Key;
+
+			if (sig == null)
 			{
-				ICipherParameters cp = key.Key;
+                this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key);
+            }
+
+            try
+			{
+				ICipherParameters cp = key;
 				if (random != null)
 				{
-					cp = new ParametersWithRandom(key.Key, random);
+					cp = new ParametersWithRandom(cp, random);
 				}
 
 				sig.Init(true, cp);
@@ -75,72 +82,68 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			lastb = 0;
 		}
 
-		public void Update(
-            byte b)
+		public void Update(byte b)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
             }
             else
             {
-				doUpdateByte(b);
+				DoUpdateByte(b);
             }
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
 			{
-				doUpdateByte(b);
+				DoUpdateByte(b);
 			}
 
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
-			doUpdateByte((byte)'\r');
-			doUpdateByte((byte)'\n');
+			DoUpdateByte((byte)'\r');
+			DoUpdateByte((byte)'\n');
 		}
 
-		private void doUpdateByte(
-			byte b)
+		private void DoUpdateByte(byte b)
 		{
 			sig.Update(b);
 			dig.Update(b);
 		}
 
-		public void Update(
-            params byte[] b)
+		public void Update(params byte[] b)
         {
 			Update(b, 0, b.Length);
         }
 
-		public void Update(
-            byte[]	b,
-            int		off,
-            int		len)
+		public void Update(byte[] b, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(b.AsSpan(off, len));
+#else
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
                 int finish = off + len;
 
 				for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(b[i]);
+                    DoCanonicalUpdateByte(b[i]);
                 }
             }
             else
@@ -148,9 +151,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 sig.BlockUpdate(b, off, len);
                 dig.BlockUpdate(b, off, len);
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                for (int i = 0; i < input.Length; ++i)
+                {
+                    DoCanonicalUpdateByte(input[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(input);
+                dig.BlockUpdate(input);
+            }
         }
+#endif
 
-		public void SetHashedSubpackets(
+        public void SetHashedSubpackets(
             PgpSignatureSubpacketVector hashedPackets)
         {
 			hashed = hashedPackets == null
@@ -180,15 +202,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
 			SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed;
 
-			if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime))
+			if (!IsPacketPresent(hashed, SignatureSubpacketTag.CreationTime))
 			{
-				hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
+				hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
 			}
 
-			if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
-				&& !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
+			if (!IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
+				&& !IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
 			{
-				unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
+				unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
 			}
 
 			int version = 4;
@@ -239,17 +261,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			byte[] sigBytes = sig.GenerateSignature();
 			byte[] digest = DigestUtilities.DoFinal(dig);
-			byte[] fingerPrint = new byte[] { digest[0], digest[1] };
+			byte[] fingerPrint = new byte[2]{ digest[0], digest[1] };
 
-			// an RSA signature
-			bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign
-				|| keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral;
-
-			MPInteger[] sigValues = isRsa
-				?	PgpUtilities.RsaSigToMpi(sigBytes)
-				:	PgpUtilities.DsaSigToMpi(sigBytes);
+			MPInteger[] sigValues;
+            if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
+            {
+                int sigLen = sigBytes.Length;
+                if (sigLen == Ed25519.SignatureSize)
+				{
+					sigValues = new MPInteger[2]{
+						new MPInteger(new BigInteger(1, sigBytes,  0, 32)),
+						new MPInteger(new BigInteger(1, sigBytes, 32, 32))
+					};
+				}
+                else if (sigLen == Ed448.SignatureSize)
+                {
+                    sigValues = new MPInteger[2]{
+                        new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
+                        new MPInteger(BigInteger.Zero)
+                    };
+                }
+                else
+				{
+					throw new InvalidOperationException();
+				}
+            }
+			else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
+			{
+                sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
+            }
+			else
+			{
+                sigValues = PgpUtilities.DsaSigToMpi(sigBytes);
+            }
 
-			return new PgpSignature(
+            return new PgpSignature(
 				new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm,
 					hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
         }
@@ -258,9 +304,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="id">The ID we are certifying against the public key.</param>
 		/// <param name="pubKey">The key we are certifying against the ID.</param>
 		/// <returns>The certification.</returns>
-        public PgpSignature GenerateCertification(
-            string			id,
-            PgpPublicKey	pubKey)
+        public PgpSignature GenerateCertification(string id, PgpPublicKey pubKey)
         {
 			UpdateWithPublicKey(pubKey);
 
@@ -276,9 +320,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="userAttributes">The ID we are certifying against the public key.</param>
 		/// <param name="pubKey">The key we are certifying against the ID.</param>
 		/// <returns>The certification.</returns>
-		public PgpSignature GenerateCertification(
-			PgpUserAttributeSubpacketVector	userAttributes,
-			PgpPublicKey					pubKey)
+		public PgpSignature GenerateCertification(PgpUserAttributeSubpacketVector userAttributes, PgpPublicKey pubKey)
 		{
 			UpdateWithPublicKey(pubKey);
 
@@ -287,7 +329,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			//
 			try
 			{
-				MemoryStream bOut = new MemoryStream();
+				var bOut = new MemoryStream();
 				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
 				{
 					packet.Encode(bOut);
@@ -299,16 +341,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				throw new PgpException("cannot encode subpacket array", e);
 			}
 
-			return this.Generate();
+			return Generate();
 		}
 
 		/// <summary>Generate a certification for the passed in key against the passed in master key.</summary>
 		/// <param name="masterKey">The key we are certifying against.</param>
 		/// <param name="pubKey">The key we are certifying.</param>
 		/// <returns>The certification.</returns>
-        public PgpSignature GenerateCertification(
-            PgpPublicKey	masterKey,
-            PgpPublicKey	pubKey)
+        public PgpSignature GenerateCertification(PgpPublicKey masterKey, PgpPublicKey pubKey)
         {
 			UpdateWithPublicKey(masterKey);
 			UpdateWithPublicKey(pubKey);
@@ -319,16 +359,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <summary>Generate a certification, such as a revocation, for the passed in key.</summary>
 		/// <param name="pubKey">The key we are certifying.</param>
 		/// <returns>The certification.</returns>
-        public PgpSignature GenerateCertification(
-            PgpPublicKey pubKey)
+        public PgpSignature GenerateCertification(PgpPublicKey pubKey)
         {
 			UpdateWithPublicKey(pubKey);
 
 			return Generate();
         }
 
-		private byte[] GetEncodedPublicKey(
-			PgpPublicKey pubKey) 
+		private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey) 
 		{
 			try
 			{
@@ -340,42 +378,31 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			}
 		}
 
-		private bool packetPresent(
-			SignatureSubpacket[]	packets,
-			SignatureSubpacketTag	type)
+		private static bool IsPacketPresent(SignatureSubpacket[] packets, SignatureSubpacketTag type)
 		{
 			for (int i = 0; i != packets.Length; i++)
 			{
 				if (packets[i].SubpacketType == type)
-				{
 					return true;
-				}
 			}
 
 			return false;
 		}
 
-		private SignatureSubpacket[] insertSubpacket(
-			SignatureSubpacket[]	packets,
-			SignatureSubpacket		subpacket)
+		private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
 		{
-			SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1];
-			tmp[0] = subpacket;
-			packets.CopyTo(tmp, 1);
-			return tmp;
+			return Arrays.Prepend(packets, subpacket);
 		}
 
-		private void UpdateWithIdData(
-			int		header,
-			byte[]	idBytes)
+		private void UpdateWithIdData(int header, byte[] idBytes)
 		{
-			this.Update(
+			Update(
 				(byte) header,
 				(byte)(idBytes.Length >> 24),
 				(byte)(idBytes.Length >> 16),
 				(byte)(idBytes.Length >> 8),
 				(byte)(idBytes.Length));
-			this.Update(idBytes);
+			Update(idBytes);
 		}
 
 		private void UpdateWithPublicKey(
@@ -383,11 +410,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		{
 			byte[] keyBytes = GetEncodedPublicKey(key);
 
-			this.Update(
-				(byte) 0x99,
+			Update(
+				0x99,
 				(byte)(keyBytes.Length >> 8),
 				(byte)(keyBytes.Length));
-			this.Update(keyBytes);
+			Update(keyBytes);
 		}
 	}
 }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index f33969ea8..2642f3497 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -9,7 +9,9 @@ using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Pqc.Crypto.SphincsPlus;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
@@ -114,7 +116,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             if (NameToHashID.TryGetValue(name, out var hashAlgorithmTag))
                 return (int)hashAlgorithmTag;
 
-            throw new ArgumentException("unable to map " + name + " to a hash id", "name");
+            throw new ArgumentException("unable to map " + name + " to a hash id", nameof(name));
         }
 
         /**
@@ -152,6 +154,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 case PublicKeyAlgorithmTag.ECDsa:
                     encAlg = "ECDSA";
                     break;
+                case PublicKeyAlgorithmTag.EdDsa:
+                    encAlg = "EdDSA";
+                    break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
 				case PublicKeyAlgorithmTag.ElGamalGeneral:
 					encAlg = "ElGamal";
@@ -163,7 +168,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return GetDigestName(hashAlgorithm) + "with" + encAlg;
         }
 
-	public static string GetSymmetricCipherName(
+	    public static string GetSymmetricCipherName(
             SymmetricKeyAlgorithmTag algorithm)
         {
             switch (algorithm)
@@ -301,11 +306,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				IDigest digest;
 				if (s2k != null)
                 {
-					string digestName = GetDigestName(s2k.HashAlgorithm);
-
                     try
                     {
-						digest = DigestUtilities.GetDigest(digestName);
+                        digest = CreateDigest(s2k.HashAlgorithm);
                     }
                     catch (Exception e)
                     {
@@ -368,7 +371,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     try
                     {
-                        digest = DigestUtilities.GetDigest("MD5");
+                        digest = CreateDigest(HashAlgorithmTag.MD5);
 
 						for (int i = 0; i != loopCount; i++)
                         {
@@ -407,7 +410,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return MakeKey(algorithm, keyBytes);
         }
 
-#if !PORTABLE || DOTNET
         /// <summary>Write out the passed in file as a literal data packet.</summary>
         public static void WriteFileToLiteralData(
             Stream		output,
@@ -452,12 +454,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 Platform.Dispose(inputStream);
             }
         }
-#endif
 
 		private const int ReadAhead = 60;
 
-		private static bool IsPossiblyBase64(
-            int ch)
+		private static bool IsPossiblyBase64(int ch)
         {
             return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
                     || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
@@ -473,7 +473,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
 			// TODO Remove this restriction?
 			if (!inputStream.CanSeek)
-				throw new ArgumentException("inputStream must be seek-able", "inputStream");
+				throw new ArgumentException("inputStream must be seek-able", nameof(inputStream));
 
 			long markedPos = inputStream.Position;
 
@@ -552,6 +552,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        internal static IDigest CreateDigest(HashAlgorithmTag hashAlgorithm)
+        {
+            return DigestUtilities.GetDigest(GetDigestName(hashAlgorithm));
+        }
+
+        internal static ISigner CreateSigner(PublicKeyAlgorithmTag publicKeyAlgorithm, HashAlgorithmTag hashAlgorithm,
+            AsymmetricKeyParameter key)
+        {
+            switch (publicKeyAlgorithm)
+            {
+            case PublicKeyAlgorithmTag.EdDsa:
+            {
+                ISigner signer;
+                if (key is Ed25519PrivateKeyParameters || key is Ed25519PublicKeyParameters)
+                {
+                    signer = new Ed25519Signer();
+                }
+                else if (key is Ed448PrivateKeyParameters || key is Ed448PublicKeyParameters)
+                {
+                    signer = new Ed448Signer(Arrays.EmptyBytes);
+                }
+                else
+                {
+                    throw new InvalidOperationException();
+                }
+
+                return new EdDsaSigner(signer, CreateDigest(hashAlgorithm));
+            }
+            default:
+            {
+                return SignerUtilities.GetSigner(GetSignatureName(publicKeyAlgorithm, hashAlgorithm));
+            }
+            }
+        }
+
         internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm)
         {
             switch (encAlgorithm)
diff --git a/crypto/src/openpgp/PgpV3SignatureGenerator.cs b/crypto/src/openpgp/PgpV3SignatureGenerator.cs
index fc8b42df2..324dbd768 100644
--- a/crypto/src/openpgp/PgpV3SignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpV3SignatureGenerator.cs
@@ -2,18 +2,17 @@ using System;
 
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.Date;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
 	/// <remarks>Generator for old style PGP V3 Signatures.</remarks>
-	// TODO Should be able to implement ISigner?
 	public class PgpV3SignatureGenerator
     {
-        private PublicKeyAlgorithmTag keyAlgorithm;
-        private HashAlgorithmTag hashAlgorithm;
+        private readonly PublicKeyAlgorithmTag keyAlgorithm;
+        private readonly HashAlgorithmTag hashAlgorithm;
+
         private PgpPrivateKey privKey;
         private ISigner sig;
         private IDigest    dig;
@@ -25,36 +24,40 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             PublicKeyAlgorithmTag	keyAlgorithm,
             HashAlgorithmTag		hashAlgorithm)
         {
+            if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
+                throw new ArgumentException("Invalid algorithm for V3 signature", nameof(keyAlgorithm));
+
             this.keyAlgorithm = keyAlgorithm;
             this.hashAlgorithm = hashAlgorithm;
 
-            dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
-            sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+            dig = PgpUtilities.CreateDigest(hashAlgorithm);
         }
 
 		/// <summary>Initialise the generator for signing.</summary>
-		public void InitSign(
-			int				sigType,
-			PgpPrivateKey	key)
+		public void InitSign(int sigType, PgpPrivateKey privKey)
 		{
-			InitSign(sigType, key, null);
+			InitSign(sigType, privKey, null);
 		}
 
 		/// <summary>Initialise the generator for signing.</summary>
-        public void InitSign(
-            int				sigType,
-            PgpPrivateKey	key,
-			SecureRandom	random)
+        public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
         {
-            this.privKey = key;
+            this.privKey = privKey;
             this.signatureType = sigType;
 
-			try
+            AsymmetricKeyParameter key = privKey.Key;
+
+            if (sig == null)
+            {
+                this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key);
+            }
+
+            try
             {
-				ICipherParameters cp = key.Key;
+				ICipherParameters cp = key;
 				if (random != null)
 				{
-					cp = new ParametersWithRandom(key.Key, random);
+					cp = new ParametersWithRandom(cp, random);
 				}
 
 				sig.Init(true, cp);
@@ -68,93 +71,98 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             lastb = 0;
         }
 
-		public void Update(
-            byte b)
+		public void Update(byte b)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
             }
             else
             {
-				doUpdateByte(b);
+				DoUpdateByte(b);
             }
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
 			{
-				doUpdateByte(b);
+				DoUpdateByte(b);
 			}
 
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
-			doUpdateByte((byte)'\r');
-			doUpdateByte((byte)'\n');
+			DoUpdateByte((byte)'\r');
+			DoUpdateByte((byte)'\n');
 		}
 
-		private void doUpdateByte(
+		private void DoUpdateByte(
 			byte b)
 		{
 			sig.Update(b);
 			dig.Update(b);
 		}
 
-		public void Update(
-            byte[] b)
+		public void Update(params byte[] b)
+        {
+            Update(b, 0, b.Length);
+        }
+
+		public void Update(byte[] b, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(b.AsSpan(off, len));
+#else
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                for (int i = 0; i != b.Length; i++)
+                int finish = off + len;
+
+				for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(b[i]);
+                    DoCanonicalUpdateByte(b[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(b, 0, b.Length);
-                dig.BlockUpdate(b, 0, b.Length);
+                sig.BlockUpdate(b, off, len);
+                dig.BlockUpdate(b, off, len);
             }
+#endif
         }
 
-		public void Update(
-            byte[]	b,
-            int		off,
-            int		len)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                int finish = off + len;
-
-				for (int i = off; i != finish; i++)
+                for (int i = 0; i < input.Length; ++i)
                 {
-                    doCanonicalUpdateByte(b[i]);
+                    DoCanonicalUpdateByte(input[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(b, off, len);
-                dig.BlockUpdate(b, off, len);
+                sig.BlockUpdate(input);
+                dig.BlockUpdate(input);
             }
         }
+#endif
 
-		/// <summary>Return the one pass header associated with the current signature.</summary>
+        /// <summary>Return the one pass header associated with the current signature.</summary>
         public PgpOnePassSignature GenerateOnePassVersion(
             bool isNested)
         {
diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs
index 5d992ec51..e1405f481 100644
--- a/crypto/src/openpgp/Rfc6637Utilities.cs
+++ b/crypto/src/openpgp/Rfc6637Utilities.cs
@@ -69,11 +69,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
         public static byte[] CreateKey(PublicKeyPacket pubKeyData, ECPoint s)
         {
+            return CreateKey(pubKeyData, s.AffineXCoord.GetEncoded());
+        }
+
+        public static byte[] CreateKey(PublicKeyPacket pubKeyData, byte[] secret)
+        {
             byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData);
 
             ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
 
-            return Kdf(ecKey.HashAlgorithm, s, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial);
+            return Kdf(ecKey.HashAlgorithm, secret, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial);
         }
 
         // RFC 6637 - Section 8
@@ -116,12 +121,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         //         ZB = x;
         //         MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param );
         //   return oBits leftmost bits of MB.
-        private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters)
+        private static byte[] Kdf(HashAlgorithmTag digestAlg, byte[] ZB, int keyLen, byte[] parameters)
         {
-            byte[] ZB = s.XCoord.GetEncoded();
-
-            string digestName = PgpUtilities.GetDigestName(digestAlg);
-			IDigest digest = DigestUtilities.GetDigest(digestName);
+            IDigest digest = PgpUtilities.CreateDigest(digestAlg);
 
             digest.Update(0x00);
             digest.Update(0x00);
diff --git a/crypto/src/openpgp/WrappedGeneratorStream.cs b/crypto/src/openpgp/WrappedGeneratorStream.cs
index df31d71f0..6f96dc9b8 100644
--- a/crypto/src/openpgp/WrappedGeneratorStream.cs
+++ b/crypto/src/openpgp/WrappedGeneratorStream.cs
@@ -13,10 +13,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		internal WrappedGeneratorStream(IStreamGenerator generator, Stream s)
 			: base(s)
 		{
-			if (generator == null)
-				throw new ArgumentNullException(nameof(generator));
-
-			m_generator = generator;
+			m_generator = generator ?? throw new ArgumentNullException(nameof(generator));
 		}
 
         protected override void Dispose(bool disposing)
@@ -31,11 +28,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				m_generator = null;
 			}
 
-			// Don't dispose the wrapped Stream
-			if (!disposing)
-            {
-				base.Dispose(disposing);
-			}
+			Detach(disposing);
 		}
 	}
 }
diff --git a/crypto/src/openssl/PEMUtilities.cs b/crypto/src/openssl/PEMUtilities.cs
index b58e5e765..332768083 100644
--- a/crypto/src/openssl/PEMUtilities.cs
+++ b/crypto/src/openssl/PEMUtilities.cs
@@ -16,8 +16,8 @@ namespace Org.BouncyCastle.OpenSsl
 		static PemUtilities()
 		{
 			// Signal to obfuscation tools not to change enum constants
-			((PemBaseAlg)Enums.GetArbitraryValue(typeof(PemBaseAlg))).ToString();
-			((PemMode)Enums.GetArbitraryValue(typeof(PemMode))).ToString();
+			Enums.GetArbitraryValue<PemBaseAlg>().ToString();
+            Enums.GetArbitraryValue<PemMode>().ToString();
 		}
 
 		private static void ParseDekAlgName(
@@ -31,16 +31,16 @@ namespace Org.BouncyCastle.OpenSsl
 
 				if (dekAlgName == "DES-EDE" || dekAlgName == "DES-EDE3")
 				{
-					baseAlg = (PemBaseAlg)Enums.GetEnumValue(typeof(PemBaseAlg), dekAlgName);
+					baseAlg = Enums.GetEnumValue<PemBaseAlg>(dekAlgName);
 					return;
 				}
 
 				int pos = dekAlgName.LastIndexOf('-');
 				if (pos >= 0)
 				{
-					baseAlg = (PemBaseAlg)Enums.GetEnumValue(typeof(PemBaseAlg), dekAlgName.Substring(0, pos));
-					mode = (PemMode)Enums.GetEnumValue(typeof(PemMode), dekAlgName.Substring(pos + 1));
-					return;
+					baseAlg = Enums.GetEnumValue<PemBaseAlg>(dekAlgName.Substring(0, pos));
+                    mode = Enums.GetEnumValue<PemMode>(dekAlgName.Substring(pos + 1));
+                    return;
 				}
 			}
 			catch (ArgumentException)
diff --git a/crypto/src/openssl/Pkcs8Generator.cs b/crypto/src/openssl/Pkcs8Generator.cs
index 0674cce15..242c966d0 100644
--- a/crypto/src/openssl/Pkcs8Generator.cs
+++ b/crypto/src/openssl/Pkcs8Generator.cs
@@ -83,10 +83,7 @@ namespace Org.BouncyCastle.OpenSsl
 
 			// TODO Theoretically, the amount of salt needed depends on the algorithm
 			byte[] salt = new byte[20];
-			if (random == null)
-			{
-				random = new SecureRandom();
-			}
+			random = CryptoServicesRegistrar.GetSecureRandom(random);
 			random.NextBytes(salt);
 
 			try
diff --git a/crypto/src/pkcs/Pkcs10CertificationRequest.cs b/crypto/src/pkcs/Pkcs10CertificationRequest.cs
index be4cbb570..59b5b51ed 100644
--- a/crypto/src/pkcs/Pkcs10CertificationRequest.cs
+++ b/crypto/src/pkcs/Pkcs10CertificationRequest.cs
@@ -281,14 +281,14 @@ namespace Org.BouncyCastle.Pkcs
 
             this.reqInfo = new CertificationRequestInfo(subject, pubInfo, attributes);
 
-            IStreamCalculator streamCalculator = signatureFactory.CreateCalculator();
+            IStreamCalculator<IBlockResult> streamCalculator = signatureFactory.CreateCalculator();
             using (Stream sigStream = streamCalculator.Stream)
             {
                 reqInfo.EncodeTo(sigStream, Der);
             }
 
             // Generate Signature.
-            sigBits = new DerBitString(((IBlockResult)streamCalculator.GetResult()).Collect());
+            sigBits = new DerBitString(streamCalculator.GetResult().Collect());
         }
 
         //        internal Pkcs10CertificationRequest(
@@ -344,13 +344,13 @@ namespace Org.BouncyCastle.Pkcs
             {
                 byte[] b = reqInfo.GetDerEncoded();
 
-                IStreamCalculator streamCalculator = verifier.CreateCalculator();
+                IStreamCalculator<IVerifier> streamCalculator = verifier.CreateCalculator();
 
                 streamCalculator.Stream.Write(b, 0, b.Length);
 
                 Platform.Dispose(streamCalculator.Stream);
 
-                return ((IVerifier)streamCalculator.GetResult()).IsVerified(sigBits.GetOctets());
+                return streamCalculator.GetResult().IsVerified(sigBits.GetOctets());
             }
             catch (Exception e)
             {
diff --git a/crypto/src/pkix/CertStatus.cs b/crypto/src/pkix/CertStatus.cs
index 4f40b7bc6..aff1b1857 100644
--- a/crypto/src/pkix/CertStatus.cs
+++ b/crypto/src/pkix/CertStatus.cs
@@ -1,7 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Utilities.Date;
-
 namespace Org.BouncyCastle.Pkix
 {
     public class CertStatus
@@ -12,12 +10,12 @@ namespace Org.BouncyCastle.Pkix
 
         private int status = Unrevoked;
 
-        DateTimeObject revocationDate = null;
+        DateTime? revocationDate = null;
 
         /// <summary>
         /// Returns the revocationDate.
         /// </summary>
-         public DateTimeObject RevocationDate
+         public DateTime? RevocationDate
         {
             get { return revocationDate; }
             set { this.revocationDate = value; }
diff --git a/crypto/src/pkix/PkixCertPath.cs b/crypto/src/pkix/PkixCertPath.cs
index 54a3c8f6a..95280173a 100644
--- a/crypto/src/pkix/PkixCertPath.cs
+++ b/crypto/src/pkix/PkixCertPath.cs
@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.X509;
diff --git a/crypto/src/pkix/PkixCertPathValidatorResult.cs b/crypto/src/pkix/PkixCertPathValidatorResult.cs
index 693ded89c..07cb350c1 100644
--- a/crypto/src/pkix/PkixCertPathValidatorResult.cs
+++ b/crypto/src/pkix/PkixCertPathValidatorResult.cs
@@ -31,20 +31,14 @@ namespace Org.BouncyCastle.Pkix
 			get { return this.subjectPublicKey; }
 		}
 
-		public PkixCertPathValidatorResult(
-			TrustAnchor				trustAnchor,
-			PkixPolicyNode			policyTree,
-			AsymmetricKeyParameter	subjectPublicKey)
+		public PkixCertPathValidatorResult(TrustAnchor trustAnchor, PkixPolicyNode policyTree,
+			AsymmetricKeyParameter subjectPublicKey)
 		{
-			if (subjectPublicKey == null)
-			{
-				throw new NullReferenceException("subjectPublicKey must be non-null");
-			}
-			if (trustAnchor == null)
-			{
-				throw new NullReferenceException("trustAnchor must be non-null");
-			}
-			
+            if (trustAnchor == null)
+                throw new ArgumentNullException(nameof(trustAnchor));
+            if (subjectPublicKey == null)
+				throw new ArgumentNullException(nameof(subjectPublicKey));
+
 			this.trustAnchor = trustAnchor;
 			this.policyTree = policyTree;
 			this.subjectPublicKey = subjectPublicKey;
diff --git a/crypto/src/pkix/PkixCertPathValidatorUtilities.cs b/crypto/src/pkix/PkixCertPathValidatorUtilities.cs
index 2514f1df2..88affe53d 100644
--- a/crypto/src/pkix/PkixCertPathValidatorUtilities.cs
+++ b/crypto/src/pkix/PkixCertPathValidatorUtilities.cs
@@ -11,7 +11,6 @@ using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509;
 using Org.BouncyCastle.X509.Extension;
 using Org.BouncyCastle.X509.Store;
@@ -173,7 +172,7 @@ namespace Org.BouncyCastle.Pkix
 
 		internal static DateTime GetValidDate(PkixParameters paramsPKIX)
 		{
-			DateTimeObject validDate = paramsPKIX.Date;
+			DateTime? validDate = paramsPKIX.Date;
 
 			if (validDate == null)
 				return DateTime.UtcNow;
@@ -541,7 +540,7 @@ namespace Org.BouncyCastle.Pkix
 
             // (i) or (j)
             certStatus.Status = reasonCodeValue;
-            certStatus.RevocationDate = new DateTimeObject(revocationDate);
+            certStatus.RevocationDate = revocationDate;
         }
 
 		/**
@@ -631,11 +630,11 @@ namespace Org.BouncyCastle.Pkix
 
 			if (index - 1 == 0)
 			{
-				DerGeneralizedTime dateOfCertgen;
+                Asn1GeneralizedTime dateOfCertgen;
 				try
 				{
 					Asn1OctetString extVal = cert.GetExtensionValue(IsisMttObjectIdentifiers.IdIsisMttATDateOfCertGen);
-					dateOfCertgen = DerGeneralizedTime.GetInstance(extVal);
+					dateOfCertgen = Asn1GeneralizedTime.GetInstance(extVal);
 				}
 				catch (ArgumentException)
 				{
diff --git a/crypto/src/pkix/PkixCrlUtilities.cs b/crypto/src/pkix/PkixCrlUtilities.cs
index 8740cc780..facbf56c2 100644
--- a/crypto/src/pkix/PkixCrlUtilities.cs
+++ b/crypto/src/pkix/PkixCrlUtilities.cs
@@ -2,7 +2,6 @@ using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Utilities.Collections;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509;
 using Org.BouncyCastle.X509.Store;
 
@@ -36,7 +35,7 @@ namespace Org.BouncyCastle.Pkix
 			// based on RFC 5280 6.3.3
 			foreach (X509Crl crl in initialSet)
 			{
-                DateTimeObject nextUpdate = crl.NextUpdate;
+                DateTime? nextUpdate = crl.NextUpdate;
 
                 if (null == nextUpdate || nextUpdate.Value.CompareTo(validityDate) > 0)
 				{
diff --git a/crypto/src/pkix/PkixParameters.cs b/crypto/src/pkix/PkixParameters.cs
index 8e4c609ed..89124f2e9 100644
--- a/crypto/src/pkix/PkixParameters.cs
+++ b/crypto/src/pkix/PkixParameters.cs
@@ -2,7 +2,6 @@ using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Utilities.Collections;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509;
 
 namespace Org.BouncyCastle.Pkix
@@ -38,7 +37,7 @@ namespace Org.BouncyCastle.Pkix
 		public const int ChainValidityModel = 1;
 
 		private HashSet<TrustAnchor> trustAnchors;
-		private DateTimeObject date;
+		private DateTime? date;
 		private List<PkixCertPathChecker> m_checkers;
 		private bool revocationEnabled = true;
 		private HashSet<string> initialPolicies;
@@ -174,7 +173,7 @@ namespace Org.BouncyCastle.Pkix
 		//	set { this.checkOnlyEECertificateCrl = value; }
 		//}
 
-		public virtual DateTimeObject Date
+		public virtual DateTime? Date
 		{
 			get { return this.date; }
 			set { this.date = value; }
diff --git a/crypto/src/pqc/crypto/bike/BikeEngine.cs b/crypto/src/pqc/crypto/bike/BikeEngine.cs
new file mode 100644
index 000000000..f7c126c66
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeEngine.cs
@@ -0,0 +1,504 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    internal sealed class BikeEngine
+    {
+        // degree of R
+        private readonly int r;
+
+        // the row weight
+        private readonly int w;
+
+        // Hamming weight of h0, h1
+        private readonly int hw;
+
+        // the error weight
+        private readonly int t;
+
+        //the shared secret size
+        //private readonly int l;
+
+        // number of iterations in BGF decoder
+        private readonly int nbIter;
+
+        // tau
+        private readonly int tau;
+
+        private readonly BikeRing bikeRing;
+        private readonly int L_BYTE;
+        private readonly int R_BYTE;
+
+        internal BikeEngine(int r, int w, int t, int l, int nbIter, int tau)
+        {
+            this.r = r;
+            this.w = w;
+            this.t = t;
+            //this.l = l;
+            this.nbIter = nbIter;
+            this.tau = tau;
+            this.hw = this.w / 2;
+            this.L_BYTE = l / 8;
+            this.R_BYTE = (r + 7) / 8;
+            this.bikeRing = new BikeRing(r);
+        }
+
+        internal int SessionKeySize => L_BYTE;
+
+        private byte[] FunctionH(byte[] seed)
+        {
+            IXof digest = new ShakeDigest(256);
+            digest.BlockUpdate(seed, 0, seed.Length);
+            return BikeUtilities.GenerateRandomByteArray(r * 2, 2 * R_BYTE, t, digest);
+        }
+
+        private void FunctionL(byte[] e0, byte[] e1, byte[] result)
+        {
+            byte[] hashRes = new byte[48];
+
+            Sha3Digest digest = new Sha3Digest(384);
+            digest.BlockUpdate(e0, 0, e0.Length);
+            digest.BlockUpdate(e1, 0, e1.Length);
+            digest.DoFinal(hashRes, 0);
+
+            Array.Copy(hashRes, 0, result, 0, L_BYTE);
+        }
+
+        private void FunctionK(byte[] m, byte[] c0, byte[] c1, byte[] result)
+        {
+            byte[] hashRes = new byte[48];
+
+            Sha3Digest digest = new Sha3Digest(384);
+            digest.BlockUpdate(m, 0, m.Length);
+            digest.BlockUpdate(c0, 0, c0.Length);
+            digest.BlockUpdate(c1, 0, c1.Length);
+            digest.DoFinal(hashRes, 0);
+
+            Array.Copy(hashRes, 0, result, 0, L_BYTE);
+        }
+
+        /**
+         Generate key pairs
+         - Secret key : (h0, h1, sigma)
+         - Public key: h
+         * @param h0            h0
+         * @param h1            h1
+         * @param sigma         sigma
+         * @param h             h
+         * @param random        Secure Random
+         **/
+        internal void GenKeyPair(byte[] h0, byte[] h1, byte[] sigma, byte[] h, SecureRandom random)
+        {
+            // Randomly generate seeds
+            byte[] seeds = new byte[64];
+            random.NextBytes(seeds);
+
+            byte[] seed1 = new byte[L_BYTE];
+            byte[] seed2 = new byte[L_BYTE];
+            Array.Copy(seeds, 0, seed1, 0, seed1.Length);
+            Array.Copy(seeds, seed1.Length, seed2, 0, seed2.Length);
+
+            IXof digest = new ShakeDigest(256);
+            digest.BlockUpdate(seed1, 0, seed1.Length);
+
+            // 1. Randomly generate h0, h1
+            ulong[] h0Element = bikeRing.GenerateRandom(hw, digest);
+            ulong[] h1Element = bikeRing.GenerateRandom(hw, digest);
+
+            bikeRing.EncodeBytes(h0Element, h0);
+            bikeRing.EncodeBytes(h1Element, h1);
+
+            // 2. Compute h
+            ulong[] hElement = bikeRing.Create();
+            bikeRing.Inv(h0Element, hElement);
+            bikeRing.Multiply(hElement, h1Element, hElement);
+            bikeRing.EncodeBytes(hElement, h);
+
+            //3. Parse seed2 as sigma
+            Array.Copy(seed2, 0, sigma, 0, sigma.Length);
+        }
+
+        /**
+         KEM Encapsulation
+         - Input: h
+         - Output: (c0,c1,k)
+         * @param c0            ciphertext
+         * @param c1            ciphertext
+         * @param k             session key
+         * @param h             public key
+         * @param random        Secure Random
+         **/
+        internal void Encaps(byte[] c0, byte[] c1, byte[] k, byte[] h, SecureRandom random)
+        {
+            byte[] seeds = new byte[64];
+            random.NextBytes(seeds);
+
+            // 1. Randomly generate m by using seed1
+            byte[] m = new byte[L_BYTE];
+            Array.Copy(seeds, 0, m, 0, m.Length);
+
+            // 2. Calculate e0, e1
+            byte[] eBytes = FunctionH(m);
+
+            byte[] eBits = new byte[2 * r];
+            BikeUtilities.FromByteArrayToBitArray(eBits, eBytes);
+
+            byte[] e0Bits = Arrays.CopyOfRange(eBits, 0, r);
+            byte[] e0Bytes = new byte[R_BYTE];
+            BikeUtilities.FromBitArrayToByteArray(e0Bytes, e0Bits);
+
+            byte[] e1Bits = Arrays.CopyOfRange(eBits, r, eBits.Length);
+            byte[] e1Bytes = new byte[R_BYTE];
+            BikeUtilities.FromBitArrayToByteArray(e1Bytes, e1Bits);
+
+            ulong[] e0Element = bikeRing.Create();
+            ulong[] e1Element = bikeRing.Create();
+
+            bikeRing.DecodeBytes(e0Bytes, e0Element);
+            bikeRing.DecodeBytes(e1Bytes, e1Element);
+
+            ulong[] hElement = bikeRing.Create();
+            bikeRing.DecodeBytes(h, hElement);
+
+            // 3. Calculate c
+            // calculate c0
+            ulong[] c0Element = bikeRing.Create();
+            bikeRing.Multiply(e1Element, hElement, c0Element);
+            bikeRing.Add(c0Element, e0Element, c0Element);
+            bikeRing.EncodeBytes(c0Element, c0);
+
+            //calculate c1
+            FunctionL(e0Bytes, e1Bytes, c1);
+            BikeUtilities.XorTo(m, c1, L_BYTE);
+
+            // 4. Calculate K
+            FunctionK(m, c0, c1, k);
+        }
+
+        /**
+         KEM Decapsulation
+         - Input: (h0, h1, sigma), (c0, c1)
+         - Output: k
+         * @param h0            private key
+         * @param h1            private key
+         * @param sigma         private key
+         * @param c0            ciphertext
+         * @param c1            ciphertext
+         * @param k             session key
+         **/
+        internal void Decaps(byte[] k, byte[] h0, byte[] h1, byte[] sigma, byte[] c0, byte[] c1)
+        {
+            // Get compact version of h0, h1
+            int[] h0Compact = new int[hw];
+            int[] h1Compact = new int[hw];
+            ConvertToCompact(h0Compact, h0);
+            ConvertToCompact(h1Compact, h1);
+
+            // Compute syndrome
+            byte[] syndromeBits = ComputeSyndrome(c0, h0);
+
+            // 1. Compute e'
+            byte[] ePrimeBits = BGFDecoder(syndromeBits, h0Compact, h1Compact);
+            byte[] ePrimeBytes = new byte[2 * R_BYTE];
+            BikeUtilities.FromBitArrayToByteArray(ePrimeBytes, ePrimeBits);
+
+            byte[] e0Bits = Arrays.CopyOfRange(ePrimeBits, 0, r);
+            byte[] e1Bits = Arrays.CopyOfRange(ePrimeBits, r, ePrimeBits.Length);
+
+            byte[] e0Bytes = new byte[R_BYTE];
+            BikeUtilities.FromBitArrayToByteArray(e0Bytes, e0Bits);
+            byte[] e1Bytes = new byte[R_BYTE];
+            BikeUtilities.FromBitArrayToByteArray(e1Bytes, e1Bits);
+
+            // 2. Compute m'
+            byte[] mPrime = new byte[L_BYTE];
+            FunctionL(e0Bytes, e1Bytes, mPrime);
+            BikeUtilities.XorTo(c1, mPrime, L_BYTE);
+
+            // 3. Compute K
+            byte[] wlist = FunctionH(mPrime);
+            if (Arrays.AreEqual(ePrimeBytes, wlist))
+            {
+                FunctionK(mPrime, c0, c1, k);
+            }
+            else
+            {
+                FunctionK(sigma, c0, c1, k);
+            }
+        }
+
+        private byte[] ComputeSyndrome(byte[] c0, byte[] h0)
+        {
+            ulong[] c0Element = bikeRing.Create();
+            ulong[] h0Element = bikeRing.Create();
+            bikeRing.DecodeBytes(c0, c0Element);
+            bikeRing.DecodeBytes(h0, h0Element);
+            ulong[] sElement = bikeRing.Create();
+            bikeRing.Multiply(c0Element, h0Element, sElement);
+            return Transpose(bikeRing.EncodeBits(sElement));
+        }
+
+        private byte[] BGFDecoder(byte[] s, int[] h0Compact, int[] h1Compact)
+        {
+            byte[] e = new byte[2 * r];
+
+            // Get compact column version
+            int[] h0CompactCol = GetColumnFromCompactVersion(h0Compact);
+            int[] h1CompactCol = GetColumnFromCompactVersion(h1Compact);
+
+            for (int i = 1; i <= nbIter; i++)
+            {
+                byte[] black = new byte[2 * r];
+                byte[] gray = new byte[2 * r];
+
+                int T = Threshold(BikeUtilities.GetHammingWeight(s), r);
+
+                BFIter(s, e, T, h0Compact, h1Compact, h0CompactCol, h1CompactCol, black, gray);
+
+                if (i == 1)
+                {
+                    BFMaskedIter(s, e, black, (hw + 1) / 2 + 1, h0Compact, h1Compact, h0CompactCol, h1CompactCol);
+                    BFMaskedIter(s, e, gray, (hw + 1) / 2 + 1, h0Compact, h1Compact, h0CompactCol, h1CompactCol);
+                }
+            }
+
+            if (BikeUtilities.GetHammingWeight(s) == 0)
+                return e;
+
+            return null;
+        }
+
+        private byte[] Transpose(byte[] input)
+        {
+            byte[] output = new byte[r];
+            output[0] = input[0];
+            for (int i = 1; i < r; i++)
+            {
+                output[i] = input[r - i];
+            }
+            return output;
+        }
+
+        private void BFIter(byte[] s, byte[] e, int T, int[] h0Compact, int[] h1Compact, int[] h0CompactCol,
+            int[] h1CompactCol, byte[] black, byte[] gray)
+        {
+            int[] updatedIndices = new int[2 * r];
+
+            // calculate for h0compact
+            for (int j = 0; j < r; j++)
+            {
+                int ctr = Ctr(h0CompactCol, s, j);
+                if (ctr >= T)
+                {
+                    UpdateNewErrorIndex(e, j);
+                    updatedIndices[j] = 1;
+                    black[j] = 1;
+                }
+                else if (ctr >= T - tau)
+                {
+                    gray[j] = 1;
+                }
+            }
+
+            // calculate for h1Compact
+            for (int j = 0; j < r; j++)
+            {
+                int ctr = Ctr(h1CompactCol, s, j);
+                if (ctr >= T)
+                {
+                    UpdateNewErrorIndex(e, r + j);
+                    updatedIndices[r + j] = 1;
+                    black[r + j] = 1;
+                }
+                else if (ctr >= T - tau)
+                {
+                    gray[r + j] = 1;
+                }
+            }
+
+            // recompute syndrome
+            for (int i = 0; i < 2 * r; i++)
+            {
+                if (updatedIndices[i] == 1)
+                {
+                    RecomputeSyndrome(s, i, h0Compact, h1Compact);
+                }
+            }
+        }
+
+        private void BFMaskedIter(byte[] s, byte[] e, byte[] mask, int T, int[] h0Compact, int[] h1Compact,
+            int[] h0CompactCol, int[] h1CompactCol)
+        {
+            int[] updatedIndices = new int[2 * r];
+
+            for (int j = 0; j < r; j++)
+            {
+                if (mask[j] == 1 && Ctr(h0CompactCol, s, j) >= T)
+                {
+                    UpdateNewErrorIndex(e, j);
+                    updatedIndices[j] = 1;
+                }
+            }
+
+            for (int j = 0; j < r; j++)
+            {
+                if (mask[r + j] == 1 && Ctr(h1CompactCol, s, j) >= T)
+                {
+                    UpdateNewErrorIndex(e, r + j);
+                    updatedIndices[r + j] = 1;
+                }
+            }
+
+            // recompute syndrome
+            for (int i = 0; i < 2 * r; i++)
+            {
+                if (updatedIndices[i] == 1)
+                {
+                    RecomputeSyndrome(s, i, h0Compact, h1Compact);
+                }
+            }
+        }
+
+        private int Threshold(int hammingWeight, int r)
+        {
+            double d;
+            int floorD;
+            int res = 0;
+            switch (r)
+            {
+            case 12323:
+                d = 0.0069722 * hammingWeight + 13.530;
+                floorD = (int) System.Math.Floor(d);
+                res = floorD > 36 ? floorD : 36;
+                break;
+            case 24659:
+                d = 0.005265 * hammingWeight + 15.2588;
+                floorD = (int) System.Math.Floor(d);
+                res = floorD > 52 ? floorD : 52;
+                break;
+            case 40973:
+                d = 0.00402312 * hammingWeight + 17.8785;
+                floorD = (int) System.Math.Floor(d);
+                res = floorD > 69 ? floorD : 69;
+                break;
+            }
+            return res;
+        }
+
+        private int Ctr(int[] hCompactCol, byte[] s, int j)
+        {
+            Debug.Assert(0 <= j && j < r);
+
+            int count = 0;
+            for (int i = 0; i < hw; i++)
+            {
+                //int sPos = (hCompactCol[i] + j) % r;
+                int sPos = hCompactCol[i] + j - r;
+                sPos += (sPos >> 31) & r;
+
+                //if (s[sPos] == 1)
+                //{
+                //    count += 1;
+                //}
+                count += s[sPos];
+            }
+            return count;
+        }
+
+        // Convert a polynomial in GF2 to an array of positions of which the coefficients of the polynomial are equals to 1
+        private void ConvertToCompact(int[] compactVersion, byte[] h)
+        {
+            // maximum size of this array is the Hamming weight of the polynomial
+            int count = 0;
+            for (int i = 0; i < R_BYTE; i++)
+            {
+                for (int j = 0; j < 8; j++)
+                {
+                    if ((i * 8 + j) == this.r)
+                        break;
+
+                    if (((h[i] >> j) & 1) == 1)
+                    {
+                        compactVersion[count++] = i * 8 + j;
+                    }
+                }
+            }
+        }
+
+        private int[] GetColumnFromCompactVersion(int[] hCompact)
+        {
+            int[] hCompactColumn = new int[hw];
+            if (hCompact[0] == 0)
+            {
+                hCompactColumn[0] = 0;
+                for (int i = 1; i < hw; i++)
+                {
+                    hCompactColumn[i] = r - hCompact[hw - i];
+                }
+            }
+            else
+            {
+                for (int i = 0; i < hw; i++)
+                {
+                    hCompactColumn[i] = r - hCompact[hw - 1 - i];
+                }
+            }
+            return hCompactColumn;
+        }
+
+        private void RecomputeSyndrome(byte[] syndrome, int index, int[] h0Compact, int[] h1Compact)
+        {
+            if (index < r)
+            {
+                for (int i = 0; i < hw; i++)
+                {
+                    if (h0Compact[i] <= index)
+                    {
+                        syndrome[index - h0Compact[i]] ^= 1;
+                    }
+                    else
+                    {
+                        syndrome[r + index - h0Compact[i]] ^= 1;
+                    }
+                }
+            }
+            else
+            {
+                for (int i = 0; i < hw; i++)
+                {
+                    if (h1Compact[i] <= (index - r))
+                    {
+                        syndrome[(index - r) - h1Compact[i]] ^= 1;
+                    }
+                    else
+                    {
+                        syndrome[r - h1Compact[i] + (index - r)] ^= 1;
+                    }
+                }
+            }
+        }
+
+        private void UpdateNewErrorIndex(byte[] e, int index)
+        {
+            int newIndex = index;
+            if (index != 0 && index != r)
+            {
+                if (index > r)
+                {
+                    newIndex = 2 * r - index + r;
+                }
+                else
+                {
+                    newIndex = r - index;
+                }
+            }
+            e[newIndex] ^= 1;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeKemExtractor.cs b/crypto/src/pqc/crypto/bike/BikeKemExtractor.cs
new file mode 100644
index 000000000..b6358e3d2
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeKemExtractor.cs
@@ -0,0 +1,43 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Pqc.Crypto.Sike;
+using Org.BouncyCastle.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public sealed class BikeKemExtractor
+        : IEncapsulatedSecretExtractor
+    {
+        private readonly BikeKeyParameters key;
+
+        public BikeKemExtractor(BikePrivateKeyParameters privParams)
+        {
+            this.key = privParams;
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            BikeParameters parameters = key.Parameters;
+            BikeEngine engine = parameters.BikeEngine;
+            int defaultKeySize = parameters.DefaultKeySize;
+
+            byte[] session_key = new byte[engine.SessionKeySize];
+            BikePrivateKeyParameters secretKey = (BikePrivateKeyParameters)key;
+
+            // Extract c0, c1 from encapsulation c
+            byte[] c0 = Arrays.CopyOfRange(encapsulation, 0, secretKey.Parameters.RByte);
+            byte[] c1 = Arrays.CopyOfRange(encapsulation, secretKey.Parameters.RByte, encapsulation.Length);
+
+            byte[] h0 = secretKey.GetH0();
+            byte[] h1 = secretKey.GetH1();
+            byte[] sigma = secretKey.GetSigma();
+
+            engine.Decaps(session_key, h0, h1, sigma, c0, c1);
+            return Arrays.CopyOfRange(session_key, 0, defaultKeySize / 8);
+        }
+
+        public int EncapsulationLength => key.Parameters.RByte + key.Parameters.LByte;
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeKemGenerator.cs b/crypto/src/pqc/crypto/bike/BikeKemGenerator.cs
new file mode 100644
index 000000000..da4221967
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeKemGenerator.cs
@@ -0,0 +1,86 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public sealed class BikeKemGenerator
+        : IEncapsulatedSecretGenerator
+    {
+        private readonly SecureRandom sr;
+
+        public BikeKemGenerator(SecureRandom random)
+        {
+            this.sr = random;
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            BikePublicKeyParameters key = (BikePublicKeyParameters)recipientKey;
+            BikeParameters parameters = key.Parameters;
+            BikeEngine engine = parameters.BikeEngine;
+
+            byte[] K = new byte[parameters.LByte];
+            byte[] c0 = new byte[parameters.RByte];
+            byte[] c1 = new byte[parameters.LByte];
+            byte[] h = key.PublicKey;
+
+            engine.Encaps(c0, c1, K, h, sr);
+
+            byte[] cipherText = Arrays.Concatenate(c0, c1);
+
+            return new SecretWithEncapsulationImpl(Arrays.CopyOfRange(K, 0, parameters.DefaultKeySize / 8), cipherText);
+        }
+
+        private class SecretWithEncapsulationImpl
+            : ISecretWithEncapsulation
+        {
+            private volatile bool hasBeenDestroyed = false;
+
+            private byte[] sessionKey;
+            private byte[] cipher_text;
+
+            public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
+            {
+                this.sessionKey = sessionKey;
+                this.cipher_text = cipher_text;
+            }
+
+            public byte[] GetSecret()
+            {
+                CheckDestroyed();
+                return Arrays.Clone(sessionKey);
+            }
+
+            public byte[] GetEncapsulation()
+            {
+                CheckDestroyed();
+
+                return Arrays.Clone(cipher_text);
+            }
+
+            public void Dispose()
+            {
+                if (!hasBeenDestroyed)
+                {
+                    hasBeenDestroyed = true;
+                    Arrays.Clear(sessionKey);
+                    Arrays.Clear(cipher_text);
+                }
+            }
+
+            public bool IsDestroyed()
+            {
+                return hasBeenDestroyed;
+            }
+
+            void CheckDestroyed()
+            {
+                if (IsDestroyed())
+                    throw new Exception("data has been destroyed");
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeKeyGenerationParameters.cs b/crypto/src/pqc/crypto/bike/BikeKeyGenerationParameters.cs
new file mode 100644
index 000000000..397c7bcdb
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeKeyGenerationParameters.cs
@@ -0,0 +1,19 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public sealed class BikeKeyGenerationParameters
+        : KeyGenerationParameters
+    {
+        private readonly BikeParameters m_parameters;
+
+        public BikeKeyGenerationParameters(SecureRandom random, BikeParameters parameters)
+            : base(random, 256)
+        {
+            m_parameters = parameters;
+        }
+
+        public BikeParameters Parameters => m_parameters;
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeKeyPairGenerator.cs b/crypto/src/pqc/crypto/bike/BikeKeyPairGenerator.cs
new file mode 100644
index 000000000..f621306bc
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeKeyPairGenerator.cs
@@ -0,0 +1,71 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public sealed class BikeKeyPairGenerator
+        : IAsymmetricCipherKeyPairGenerator
+    {
+        private SecureRandom random;
+
+        // block length
+        private int r;
+
+        // the row weight
+        //private int w;
+
+        // Hamming weight of h0, h1
+        //private int hw;
+
+        // the error weight
+        //private int t;
+
+        //the shared secret size
+        private int l;
+
+        // number of iterations in BGF decoder
+        //private int nbIter;
+
+        // tau
+        //private int tau;
+        private int L_BYTE;
+        private int R_BYTE;
+
+        private BikeKeyGenerationParameters bikeKeyGenerationParameters;
+
+        public void Init(KeyGenerationParameters param)
+        {
+            this.bikeKeyGenerationParameters = (BikeKeyGenerationParameters)param;
+            this.random = param.Random;
+
+            // get parameters
+            this.r = this.bikeKeyGenerationParameters.Parameters.R;
+            //this.w = this.bikeKeyGenerationParameters.Parameters.W;
+            this.l = this.bikeKeyGenerationParameters.Parameters.L;
+            //this.t = this.bikeKeyGenerationParameters.Parameters.T;
+            //this.nbIter = this.bikeKeyGenerationParameters.Parameters.NbIter;
+            //this.tau = this.bikeKeyGenerationParameters.Parameters.Tau;
+            //this.hw = w / 2;
+            this.L_BYTE = l / 8;
+            this.R_BYTE = (r + 7) / 8;
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            BikeParameters parameters = bikeKeyGenerationParameters.Parameters;
+            BikeEngine engine = parameters.BikeEngine;
+            byte[] h0 = new byte[R_BYTE];
+            byte[] h1 = new byte[R_BYTE];
+            byte[] h = new byte[R_BYTE];
+            byte[] sigma = new byte[L_BYTE];
+
+            engine.GenKeyPair(h0, h1, sigma, h, random);
+
+            // form keys
+            BikePublicKeyParameters publicKey = new BikePublicKeyParameters(parameters, h);
+            BikePrivateKeyParameters privateKey = new BikePrivateKeyParameters(parameters, h0, h1, sigma);
+
+            return new AsymmetricCipherKeyPair(publicKey, privateKey);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeKeyParameters.cs b/crypto/src/pqc/crypto/bike/BikeKeyParameters.cs
new file mode 100644
index 000000000..fd15138e4
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeKeyParameters.cs
@@ -0,0 +1,18 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public abstract class BikeKeyParameters
+        : AsymmetricKeyParameter
+    {
+        private readonly BikeParameters m_parameters;
+
+        public BikeKeyParameters(bool isPrivate, BikeParameters parameters)
+            : base(isPrivate)
+        {
+            this.m_parameters = parameters;
+        }
+
+        public BikeParameters Parameters => m_parameters;
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeParameters.cs b/crypto/src/pqc/crypto/bike/BikeParameters.cs
new file mode 100644
index 000000000..9fd658e0c
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeParameters.cs
@@ -0,0 +1,52 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public sealed class BikeParameters
+        : ICipherParameters
+    {
+        // 128 bits security
+        public static BikeParameters bike128 = new BikeParameters("bike128", 12323, 142, 134, 256, 5, 3, 128);
+
+        // 192 bits security
+        public static BikeParameters bike192 = new BikeParameters("bike192", 24659, 206, 199, 256, 5, 3, 192);
+
+        // 256 bits security
+        public static BikeParameters bike256 = new BikeParameters("bike256", 40973, 274, 264, 256, 5, 3, 256);
+
+        private readonly string name;
+        private readonly int r;
+        private readonly int w;
+        private readonly int t;
+        private readonly int l;
+        private readonly int nbIter;
+        private readonly int tau;
+        private readonly int defaultKeySize;
+        private readonly BikeEngine bikeEngine;
+
+        internal BikeParameters(string name, int r, int w, int t, int l, int nbIter, int tau, int defaultKeySize)
+        {
+            this.name = name;
+            this.r = r;
+            this.w = w;
+            this.t = t;
+            this.l = l;
+            this.nbIter = nbIter;
+            this.tau = tau;
+            this.defaultKeySize = defaultKeySize;
+            this.bikeEngine = new BikeEngine(r, w, t, l, nbIter, tau);
+        }
+
+        public int R => r;
+        public int RByte => (r + 7) / 8;
+        public int LByte => l / 8; 
+        public int W => w;
+        public int T => t;
+        public int L => l;
+        public int NbIter => nbIter;
+        public int Tau => tau;
+        public string Name => name;
+        public int DefaultKeySize => defaultKeySize;
+        internal BikeEngine BikeEngine => bikeEngine;
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikePrivateKeyParameters.cs b/crypto/src/pqc/crypto/bike/BikePrivateKeyParameters.cs
new file mode 100644
index 000000000..dce003a7b
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikePrivateKeyParameters.cs
@@ -0,0 +1,54 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public class BikePrivateKeyParameters
+        : BikeKeyParameters
+    {
+        // h0
+        private byte[] h0;
+
+        // h1
+        private byte[] h1;
+
+        // sigma
+        private byte[] sigma;
+
+        /**
+         * Constructor.
+         *
+         * @param h0    h0
+         * @param h1    h1
+         * @param sigma random bytes sigma
+         */
+        public BikePrivateKeyParameters(BikeParameters bikeParameters, byte[] h0, byte[] h1, byte[] sigma)
+            : base(true, bikeParameters)
+        {
+            this.h0 = Arrays.Clone(h0);
+            this.h1 = Arrays.Clone(h1);
+            this.sigma = Arrays.Clone(sigma);
+        }
+
+        public byte[] GetH0()
+        {
+            return h0;
+        }
+
+        public byte[] GetH1()
+        {
+            return h1;
+        }
+
+        public byte[] GetSigma()
+        {
+            return sigma;
+        }
+
+        internal byte[] PrivateKey => Arrays.Concatenate(Arrays.Concatenate(h0, h1), sigma);
+
+        public byte[] GetEncoded()
+        {
+            return PrivateKey;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikePublicKeyParameters.cs b/crypto/src/pqc/crypto/bike/BikePublicKeyParameters.cs
new file mode 100644
index 000000000..dbbe2480c
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikePublicKeyParameters.cs
@@ -0,0 +1,28 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    public class BikePublicKeyParameters
+        : BikeKeyParameters
+    {
+        private readonly byte[] publicKey;
+
+        /**
+         * Constructor.
+         *
+         * @param publicKey      byte
+         */
+        public BikePublicKeyParameters(BikeParameters param, byte[] publicKey)
+            : base(false, param)
+        {
+            this.publicKey = Arrays.Clone(publicKey);
+        }
+
+        internal byte[] PublicKey => publicKey;
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.Clone(publicKey);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeRing.cs b/crypto/src/pqc/crypto/bike/BikeRing.cs
new file mode 100644
index 000000000..a519595af
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeRing.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Diagnostics;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math.Raw;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    internal sealed class BikeRing
+    {
+        private readonly int m_bits;
+        private readonly int m_size;
+        private readonly int m_sizeExt;
+
+        internal BikeRing(int r)
+        {
+            if ((r & 0x80000001) != 1)
+                throw new ArgumentException();
+
+            m_bits = r;
+            m_size = (r + 63) >> 6;
+            m_sizeExt = m_size * 2;
+        }
+
+        internal void Add(ulong[] x, ulong[] y, ulong[] z)
+        {
+            Nat.Xor64(Size, x, y, z);
+        }
+
+        internal void AddTo(ulong[] x, ulong[] z)
+        {
+            Nat.XorTo64(Size, x, z);
+        }
+
+        internal void Copy(ulong[] x, ulong[] z)
+        {
+            for (int i = 0; i < Size; ++i)
+            {
+                z[i] = x[i];
+            }
+        }
+
+        internal ulong[] Create()
+        {
+            return new ulong[Size];
+        }
+
+        internal ulong[] CreateExt()
+        {
+            return new ulong[SizeExt];
+        }
+
+        internal void DecodeBytes(byte[] bs, ulong[] z)
+        {
+            int partialBits = m_bits & 63;
+            Pack.LE_To_UInt64(bs, 0, z, 0, Size - 1);
+            byte[] last = new byte[8];
+            Array.Copy(bs, (Size - 1) << 3, last, 0, (partialBits + 7) >> 3);
+            z[Size - 1] = Pack.LE_To_UInt64(last);
+            Debug.Assert((z[Size - 1] >> partialBits) == 0UL);
+        }
+
+        internal byte[] EncodeBits(ulong[] x)
+        {
+            byte[] bs = new byte[m_bits];
+            for (int i = 0; i < m_bits; ++i)
+            {
+                bs[i] = (byte)((x[i >> 6] >> (i & 63)) & 1UL);
+            }
+            return bs;
+        }
+
+        internal void EncodeBytes(ulong[] x, byte[] bs)
+        {
+            int partialBits = m_bits & 63;
+            Debug.Assert((x[Size - 1] >> partialBits) == 0UL);
+            Pack.UInt64_To_LE(x, 0, Size - 1, bs, 0);
+            byte[] last = new byte[8];
+            Pack.UInt64_To_LE(x[Size - 1], last);
+            Array.Copy(last, 0, bs, (Size - 1) << 3, (partialBits + 7) >> 3);
+        }
+
+        internal ulong[] GenerateRandom(int weight, IXof digest)
+        {
+            byte[] buf = new byte[4];
+            int highest = Integers.HighestOneBit(m_bits);
+            int mask = highest | (highest - 1);
+
+            ulong[] z = Create();
+            int count = 0;
+            while (count < weight)
+            {
+                digest.Output(buf, 0, 4);
+                int candidate = (int)Pack.LE_To_UInt32(buf) & mask;
+                if (candidate < m_bits)
+                {
+                    int pos = candidate >> 6;
+                    ulong bit = 1UL << (candidate & 63);
+                    if ((z[pos] & bit) == 0UL)
+                    {
+                        z[pos] |= bit;
+                        ++count;
+                    }
+                }
+            }
+            return z;
+        }
+
+        internal void Inv(ulong[] a, ulong[] z)
+        {
+            ulong[] f = Create();
+            ulong[] g = Create();
+            ulong[] t = Create();
+
+            Copy(a, f);
+            Copy(a, t);
+
+            int rSub2 = m_bits - 2;
+            int bits = 32 - Integers.NumberOfLeadingZeros(rSub2);
+
+            for (int i = 1; i < bits; ++i)
+            {
+                SquareN(f, 1 << (i - 1), g);
+                Multiply(f, g, f);
+
+                if ((rSub2 & (1 << i)) != 0)
+                {
+                    int n = rSub2 & ((1 << i) - 1);
+                    SquareN(f, n, g);
+                    Multiply(t, g, t);
+                }
+            }
+
+            Square(t, z);
+        }
+
+        internal void Multiply(ulong[] x, ulong[] y, ulong[] z)
+        {
+            ulong[] tt = CreateExt();
+            ImplMultiplyAcc(x, y, tt);
+            Reduce(tt, z);
+        }
+
+        internal void Reduce(ulong[] tt, ulong[] z)
+        {
+            int partialBits = m_bits & 63;
+            int excessBits = 64 - partialBits;
+            ulong partialMask = ulong.MaxValue >> excessBits;
+
+            ulong c = Nat.ShiftUpBits64(Size, tt, Size, excessBits, tt[Size - 1], z, 0);
+            Debug.Assert(c == 0UL);
+            AddTo(tt, z);
+            z[Size - 1] &= partialMask;
+        }
+
+        internal int Size => m_size;
+
+        internal int SizeExt => m_sizeExt;
+
+        internal void Square(ulong[] x, ulong[] z)
+        {
+            ulong[] tt = CreateExt();
+            ImplSquare(x, tt);
+            Reduce(tt, z);
+        }
+
+        internal void SquareN(ulong[] x, int n, ulong[] z)
+        {
+            /*
+             * TODO In these polynomial rings, 'squareN' for some 'n' is equivalent to a fixed permutation of the
+             * coefficients. For 'squareN' with 'n' above some cutoff value, this permutation could be precomputed
+             * and then applied in place of explicit squaring for that 'n'. This is particularly relevant to the
+             * calls generated by 'inv'.
+             */
+
+            Debug.Assert(n > 0);
+
+            ulong[] tt = CreateExt();
+            ImplSquare(x, tt);
+            Reduce(tt, z);
+
+            while (--n > 0)
+            {
+                ImplSquare(z, tt);
+                Reduce(tt, z);
+            }
+        }
+
+        private void ImplMultiplyAcc(ulong[] x, ulong[] y, ulong[] zz)
+        {
+            ulong[] u = new ulong[16];
+
+            // Schoolbook
+
+            //for (int i = 0; i < Size; ++i)
+            //{
+            //    ulong x_i = x[i];
+
+            //    for (int j = 0; j < Size; ++j)
+            //    {
+            //        ulong y_j = y[j];
+
+            //        ImplMulwAcc(u, x_i, y_j, zz, i + j);
+            //    }
+            //}
+
+            // Arbitrary-degree Karatsuba
+
+            for (int i = 0; i < Size; ++i)
+            {
+                ImplMulwAcc(u, x[i], y[i], zz, i << 1);
+            }
+
+            ulong v0 = zz[0], v1 = zz[1];
+            for (int i = 1; i < Size; ++i)
+            {
+                v0 ^= zz[i << 1]; zz[i] = v0 ^ v1; v1 ^= zz[(i << 1) + 1];
+            }
+
+            ulong w = v0 ^ v1;
+            for (int i = 0; i < Size; ++i)
+            {
+                zz[Size + i] = zz[i] ^ w;
+            }
+
+            int last = Size - 1;
+            for (int zPos = 1; zPos < (last * 2); ++zPos)
+            {
+                int hi = System.Math.Min(last, zPos);
+                int lo = zPos - hi;
+
+                while (lo < hi)
+                {
+                    ImplMulwAcc(u, x[lo] ^ x[hi], y[lo] ^ y[hi], zz, zPos);
+
+                    ++lo;
+                    --hi;
+                }
+            }
+        }
+
+        private static void ImplMulwAcc(ulong[] u, ulong x, ulong y, ulong[] z, int zOff)
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (Pclmulqdq.IsSupported)
+            {
+                var X = Vector128.CreateScalar(x);
+                var Y = Vector128.CreateScalar(y);
+                var Z = Pclmulqdq.CarrylessMultiply(X, Y, 0x00);
+                z[zOff    ] ^= Z.GetElement(0);
+                z[zOff + 1] ^= Z.GetElement(1);
+                return;
+            }
+#endif
+
+            //u[0] = 0;
+            u[1] = y;
+            for (int i = 2; i < 16; i += 2)
+            {
+                u[i    ] = u[i >> 1] << 1;
+                u[i + 1] = u[i     ] ^  y;
+            }
+
+            uint j = (uint)x;
+            ulong g, h = 0, l = u[j & 15]
+                              ^ u[(j >> 4) & 15] << 4;
+            int k = 56;
+            do
+            {
+                j  = (uint)(x >> k);
+                g  = u[j & 15]
+                   ^ u[(j >> 4) & 15] << 4;
+                l ^= (g << k);
+                h ^= (g >> -k);
+            }
+            while ((k -= 8) > 0);
+
+            for (int p = 0; p < 7; ++p)
+            {
+                x = (x & 0xFEFEFEFEFEFEFEFEUL) >> 1;
+                h ^= x & (ulong)((long)(y << p) >> 63);
+            }
+
+            Debug.Assert(h >> 63 == 0);
+
+            z[zOff    ] ^= l;
+            z[zOff + 1] ^= h;
+        }
+
+        private void ImplSquare(ulong[] x, ulong[] zz)
+        {
+            Interleave.Expand64To128(x, 0, Size, zz, 0);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/bike/BikeUtilities.cs b/crypto/src/pqc/crypto/bike/BikeUtilities.cs
new file mode 100644
index 000000000..09143aea0
--- /dev/null
+++ b/crypto/src/pqc/crypto/bike/BikeUtilities.cs
@@ -0,0 +1,111 @@
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Bike
+{
+    internal class BikeUtilities
+    {
+        internal static void XorTo(byte[] x, byte[] z, int zLen)
+        {
+            for (int i = 0; i < zLen; ++i)
+            {
+                z[i] ^= x[i];
+            }
+        }
+
+        internal static int GetHammingWeight(byte[] bytes)
+        {
+            int hammingWeight = 0;
+            for (int i = 0; i < bytes.Length; i++)
+            {
+                hammingWeight += bytes[i];
+            }
+            return hammingWeight;
+        }
+
+        internal static void FromByteArrayToBitArray(byte[] output, byte[] input)
+        {
+            int max = (output.Length / 8);
+            for (int i = 0; i < max; i++)
+            {
+                for (int j = 0; j != 8; j++)
+                {
+                    output[i * 8 + j] = (byte)((input[i] >> j) & 1);
+                }
+            }
+            if (output.Length % 8 != 0)
+            {
+                int off = max * 8;
+                int count = 0;
+                while (off < output.Length)
+                {
+                    output[off++] = (byte)((input[max] >> count) & 1);
+                    count++;
+                }
+            }
+        }
+
+        internal static void FromBitArrayToByteArray(byte[] output, byte[] input)
+        {
+            int count = 0;
+            int pos = 0;
+            long len = input.Length;
+            while (count < len)
+            {
+                if (count + 8 >= input.Length)
+                {// last set of bits cannot have enough 8 bits
+                    int b = input[count];
+                    for (int j = input.Length - count - 1; j >= 1; j--)
+                    { //bin in reversed order
+                        b |= input[count + j] << j;
+                    }
+                    output[pos] = (byte)b;
+                }
+                else
+                {
+                    int b = input[count];
+                    for (int j = 7; j >= 1; j--)
+                    { //bin in reversed order
+                        b |= input[count + j] << j;
+                    }
+                    output[pos] = (byte)b;
+                }
+
+                count += 8;
+                pos++;
+            }
+        }
+
+        internal static byte[] GenerateRandomByteArray(int mod, int size, int weight, IXof digest)
+        {
+            byte[] buf = new byte[4];
+            int highest = Integers.HighestOneBit(mod);
+            int mask = highest | (highest - 1);
+
+            byte[] res = new byte[size];
+            int count = 0;
+            while (count < weight)
+            {
+                digest.Output(buf, 0, 4);
+                int tmp = (int)Pack.LE_To_UInt32(buf) & mask;
+
+                if (tmp < mod && SetBit(res, tmp))
+                {
+                    ++count;
+                }
+            }
+            return res;
+        }
+
+        private static bool SetBit(byte[] a, int position)
+        {
+            int index = position / 8;
+            int pos = position % 8;
+            int selector = 1 << pos;
+            bool result = (a[index] & selector) == 0;
+            a[index] |= (byte)selector;
+            return result;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/cmce/CmceEngine.cs b/crypto/src/pqc/crypto/cmce/CmceEngine.cs
index 41194e8a6..96595ecc0 100644
--- a/crypto/src/pqc/crypto/cmce/CmceEngine.cs
+++ b/crypto/src/pqc/crypto/cmce/CmceEngine.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Runtime.InteropServices;
+#endif
 
 using Org.BouncyCastle.Asn1.Nist;
 using Org.BouncyCastle.Crypto;
@@ -96,7 +99,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             IDigest digest = DigestUtilities.GetDigest(NistObjectIdentifiers.IdShake256);
             digest.Update(64);
             digest.BlockUpdate(sk, 0, 32);
-            ((IXof)digest).DoFinal(hash, 0, hash.Length);
+            ((IXof)digest).OutputFinal(hash, 0, hash.Length);
 
             for (int i = 0; i < (1 << GFBITS); i++)
             {
@@ -123,7 +126,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             IDigest digest = DigestUtilities.GetDigest(NistObjectIdentifiers.IdShake256);
             digest.Update((byte)64);
             digest.BlockUpdate(sk, 0, 32); // input
-            ((IXof)digest).DoFinal(hash, 0, hash.Length);
+            ((IXof)digest).OutputFinal(hash, 0, hash.Length);
 
 
             // generate g
@@ -170,7 +173,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
                     {
                         buf[i] = perm[i];
                         buf[i] <<= 31;
-                        buf[i] |= i;
+                        buf[i] |= (uint)i;
                         buf[i] &= 0x7fffffffffffffffL; // getting rid of signed longs
                     }
                     Sort64(buf, 0, buf.Length);
@@ -213,7 +216,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
                 // SeededKeyGen - 1. Compute E = G(δ), a string of n + σ2q + σ1t + l bits. (3488 + 32*4096 + 16*64 + 256)
                 digest.BlockUpdate(seed_a, 0, seed_a.Length);
                 digest.BlockUpdate(seed_b, 0, seed_b.Length);
-                ((IXof)digest).DoFinal(E, 0, E.Length);
+                ((IXof)digest).OutputFinal(E, 0, E.Length);
                 // Store the seeds generated
 
                 // SeededKeyGen - 2. Define δ′ as the last l bits of E.
@@ -532,7 +535,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             IDigest digest = DigestUtilities.GetDigest(NistObjectIdentifiers.IdShake256);
             digest.Update((byte)0x02);
             digest.BlockUpdate(error_vector, 0, error_vector.Length); // input
-            ((IXof)digest).DoFinal(cipher_text, SYND_BYTES, cipher_text.Length - SYND_BYTES);     // output
+            ((IXof)digest).OutputFinal(cipher_text, SYND_BYTES, cipher_text.Length - SYND_BYTES);     // output
 
             /*
             2.4.5 Encapsulation
@@ -543,7 +546,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             digest.Update((byte)0x01);
             digest.BlockUpdate(error_vector, 0, error_vector.Length);
             digest.BlockUpdate(cipher_text, 0, cipher_text.Length); // input
-            ((IXof)digest).DoFinal(key, 0, key.Length);     // output
+            ((IXof)digest).OutputFinal(key, 0, key.Length);     // output
 
             if (usePadding)
             {
@@ -598,7 +601,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             IDigest digest = DigestUtilities.GetDigest(NistObjectIdentifiers.IdShake256);
             digest.Update((byte)0x02);
             digest.BlockUpdate(error_vector, 0, error_vector.Length); // input
-            ((IXof)digest).DoFinal(conf, 0, conf.Length);     // output
+            ((IXof)digest).OutputFinal(conf, 0, conf.Length);     // output
 
             /*
             2.3.3 Decapsulation
@@ -640,7 +643,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             //  = SHAKE256(preimage, 32)
             digest = DigestUtilities.GetDigest(NistObjectIdentifiers.IdShake256);
             digest.BlockUpdate(preimage, 0, preimage.Length); // input
-            ((IXof)digest).DoFinal(key, 0, key.Length);     // output
+            ((IXof)digest).OutputFinal(key, 0, key.Length);     // output
 
 
             // clear outputs (set to all 1's) if padding bits are not all zero
@@ -1159,7 +1162,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             {
                 for (x = 0; x < n; ++x)
                 {
-                    temp[(int)x] = ((GetQShort(temp, (int)(qIndex + x)) ^ 1) << 16) | GetQShort(temp, (int)((qIndex) + (x ^ 1)));
+                    ushort t0 = (ushort)GetQShort(temp, (int)(qIndex + x));
+                    ushort t1 = (ushort)GetQShort(temp, (int)(qIndex + (x ^ 1)));
+                    temp[(int)x] = ((t0 ^ 1) << 16) | t1;
                 }
             }
             Sort32(temp, 0, (int)n); /* A = (id<<16)+pibar */
@@ -1178,7 +1183,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
 
             for (x = 0; x < n; ++x)
             {
-                temp[(int)x] = (int)((temp[(int)x] << 16) | x); /* A = (pibar<<16)+id */
+                temp[(int)x] = (int)((uint)(temp[(int)x] << 16) | x); /* A = (pibar<<16)+id */
             }
             Sort32(temp, 0, (int)n); /* A = (id<<16)+pibar^-1 */
 
@@ -1201,7 +1206,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
 
                     for (x = 0; x < n; ++x)
                     {
-                        temp[(int)x] = (int)(((temp[(int)(n + x)] & ~0x3ff) << 6) | x); /* A = (p<<16)+id */
+                        temp[(int)x] = (int)(((uint)(temp[(int)(n + x)] & ~0x3ff) << 6) | x); /* A = (p<<16)+id */
                     }
                     Sort32(temp, 0, (int)n); /* A = (id<<16)+p^{-1} */
 
@@ -1238,7 +1243,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
                     /* B = (p<<16)+c */
                     for (x = 0; x < n; ++x)
                     {
-                        temp[(int)x] = (int)((temp[(int)(n + x)] & ~0xffff) | x);
+                        temp[(int)x] = (int)((uint)(temp[(int)(n + x)] & ~0xffff) | x);
                     }
                     Sort32(temp, 0, (int)n); /* A = (id<<16)+p^(-1) */
                     for (x = 0; x < n; ++x)
@@ -1372,7 +1377,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             {
                 buf[i] = perm[i];
                 buf[i] <<= 31;
-                buf[i] |= i;
+                buf[i] |= (uint)i;
                 // buf[i] &= 0x7fffffffffffffffL; // getting rid of signed longs
             }
             // Sort32 the buffer
@@ -1460,9 +1465,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
                     row = i * 8 + j;
 
                     if (row >= PK_NROWS)
-                    {
                         break;
-                    }
+
+                    byte[] mat_row = mat[row];
 
                     if (usePivots)
                     {
@@ -1470,7 +1475,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
                         {
                             if (MovColumns(mat, pi, pivots) != 0)
                             {
-                                //                            System.out.println("failed mov column!");
+                                //System.out.println("failed mov column!");
                                 return -1;
                             }
                         }
@@ -1478,21 +1483,37 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
 
                     for (k = row + 1; k < PK_NROWS; k++)
                     {
-                        mask = (byte)(mat[row][i] ^ mat[k][i]);
+                        byte[] mat_k = mat[k];
+                        mask = (byte)(mat_row[i] ^ mat_k[i]);
                         mask >>= j;
                         mask &= 1;
-                        mask = (byte)-mask;
 
-                        for (c = 0; c < SYS_N / 8; c++)
+                        c = 0;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                        ulong mask64 = 0UL - mask;
+                        int limit64 = (SYS_N / 8) - 8;
+                        while (c <= limit64)
                         {
-                            mat[row][c] ^= (byte)(mat[k][c] & mask);
+                            ulong t0 = MemoryMarshal.Read<ulong>(mat_k.AsSpan(c)) & mask64;
+                            ulong t1 = MemoryMarshal.Read<ulong>(mat_row.AsSpan(c)) ^ t0;
+                            MemoryMarshal.Write(mat_row.AsSpan(c), ref t1);
+                            c += 8;
+                        }
+#endif
+                        byte maskByte = (byte)-mask;
+                        while (c < SYS_N / 8)
+                        {
+                            mat_row[c] ^= (byte)(mat_k[c] & maskByte);
+                            ++c;
                         }
                     }
+
                     // 7. Compute (T,cn−k−μ+1,...,cn−k,Γ′) ← MatGen(Γ). If this fails, set δ ← δ′ and
                     // restart the algorithm.
-                    if (((mat[row][i] >> j) & 1) == 0) // return if not systematic
+                    if (((mat_row[i] >> j) & 1) == 0) // return if not systematic
                     {
-                        //                    System.out.println("FAIL 2\n");
+                        //System.out.println("FAIL 2\n");
                         return -1;
                     }
 
@@ -1500,14 +1521,35 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
                     {
                         if (k != row)
                         {
-                            mask = (byte)(mat[k][i] >> j);
+                            byte[] mat_k = mat[k];
+                            mask = (byte)(mat_k[i] >> j);
                             mask &= 1;
-                            mask = (byte)-mask;
 
-                            for (c = 0; c < SYS_N / 8; c++)
-                            {
-                                mat[k][c] ^= (byte)(mat[row][c] & mask);
+                            //mask = (byte)-mask;
+
+                            //for (c = 0; c < SYS_N / 8; c++)
+                            //{
+                            //    mat_k[c] ^= (byte)(mat_row[c] & mask);
+                            //}
 
+                            c = 0;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                            ulong mask64 = 0UL - mask;
+                            int limit64 = (SYS_N / 8) - 8;
+                            while (c <= limit64)
+                            {
+                                ulong t0 = MemoryMarshal.Read<ulong>(mat_row.AsSpan(c)) & mask64;
+                                ulong t1 = MemoryMarshal.Read<ulong>(mat_k.AsSpan(c)) ^ t0;
+                                MemoryMarshal.Write(mat_k.AsSpan(c), ref t1);
+                                c += 8;
+                            }
+#endif
+                            byte maskByte = (byte)-mask;
+                            while (c < SYS_N / 8)
+                            {
+                                mat_k[c] ^= (byte)(mat_row[c] & maskByte);
+                                ++c;
                             }
                         }
                     }
@@ -1546,7 +1588,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             return 0;
         }
 
-
         private ushort Eval(ushort[] f, ushort a)
         {
             ushort r;
diff --git a/crypto/src/pqc/crypto/cmce/CmceKemExtractor.cs b/crypto/src/pqc/crypto/cmce/CmceKemExtractor.cs
index 9c4e84456..805dce7b6 100644
--- a/crypto/src/pqc/crypto/cmce/CmceKemExtractor.cs
+++ b/crypto/src/pqc/crypto/cmce/CmceKemExtractor.cs
@@ -32,16 +32,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             return ExtractSecret(encapsulation, engine.DefaultSessionKeySize);
         }
 
-        public byte[] ExtractSecret(byte[] encapsulation, int sessionKeySizeInBits)
+        private byte[] ExtractSecret(byte[] encapsulation, int sessionKeySizeInBits)
         {
             byte[] session_key = new byte[sessionKeySizeInBits / 8];
             engine.kem_dec(session_key, encapsulation, ((CmcePrivateKeyParameters)key).PrivateKey);
             return session_key;
         }
 
-        public int GetInputSize()
-        {
-            return engine.CipherTextSize;
-        }
+        public int EncapsulationLength => engine.CipherTextSize;
     }
 }
diff --git a/crypto/src/pqc/crypto/cmce/CmceKemGenerator.cs b/crypto/src/pqc/crypto/cmce/CmceKemGenerator.cs
index fe258ce74..ac97fb594 100644
--- a/crypto/src/pqc/crypto/cmce/CmceKemGenerator.cs
+++ b/crypto/src/pqc/crypto/cmce/CmceKemGenerator.cs
@@ -26,7 +26,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Cmce
             return GenerateEncapsulated(recipientKey, engine.DefaultSessionKeySize);
         }
 
-        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey, int sessionKeySizeInBits)
+        private ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey, int sessionKeySizeInBits)
         {
             CmcePublicKeyParameters key = (CmcePublicKeyParameters)recipientKey;
             CmceEngine engine = key.Parameters.Engine;
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumEngine.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumEngine.cs
new file mode 100644
index 000000000..4ba769984
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumEngine.cs
@@ -0,0 +1,361 @@
+using System;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class DilithiumEngine
+    {
+        private SecureRandom _random;
+
+        public const int N = 256;
+        public const int Q = 8380417;
+        public const int QInv = 58728449; // Q ^ (-1) mod 2 ^32
+        public const int D = 13;
+        public const int RootOfUnity = 1753;
+        public const int SeedBytes = 32;
+        public const int CrhBytes = 64;
+
+        public const int PolyT1PackedBytes = 320;
+        public const int PolyT0PackedBytes = 416;
+
+        public int Mode { get; private set; }
+
+        public int K { get; private set; }
+        public int L { get; private set; }
+        public int Eta { get; private set; }
+        public int Tau { get; private set; }
+        public int Beta { get; private set; }
+        public int Gamma1 { get; private set; }
+        public int Gamma2 { get; private set; }
+        public int Omega { get; private set; }
+
+        public int PolyVecHPackedBytes { get; private set; }
+
+        public int PolyZPackedBytes { get; private set; }
+        public int PolyW1PackedBytes { get; private set; }
+        public int PolyEtaPackedBytes { get; private set; }
+        
+        public int CryptoPublicKeyBytes { get; private set; }
+        public int CryptoSecretKeyBytes { get; private set; }
+        public int CryptoBytes { get; private set; }
+        public int PolyUniformGamma1NBytes { get; private set; }
+        public Symmetric Symmetric { get; private set; }
+        
+        public DilithiumEngine(int mode, SecureRandom random, bool usingAes)
+        {
+            Mode = mode;
+            switch (Mode)
+            {
+                case 2:
+                    K = 4;
+                    L = 4;
+                    Eta = 2;
+                    Tau = 39;
+                    Beta = 78;
+                    Gamma1 = (1 << 17);
+                    Gamma2 = ((Q - 1) / 88);
+                    Omega = 80;
+                    PolyZPackedBytes = 576;
+                    PolyW1PackedBytes = 192;
+                    PolyEtaPackedBytes = 96;
+                    break;
+                case 3:
+                    K = 6;
+                    L = 5;
+                    Eta = 4;
+                    Tau = 49;
+                    Beta = 196;
+                    Gamma1 = (1 << 19);
+                    Gamma2 = ((Q - 1) / 32);
+                    Omega = 55;
+                    PolyZPackedBytes = 640;
+                    PolyW1PackedBytes = 128;
+                    PolyEtaPackedBytes = 128;
+                    break;
+                case 5:
+                    K = 8;
+                    L = 7;
+                    Eta = 2;
+                    Tau = 60;
+                    Beta = 120;
+                    Gamma1 = (1 << 19);
+                    Gamma2 = ((Q - 1) / 32);
+                    Omega = 75;
+                    PolyZPackedBytes = 640;
+                    PolyW1PackedBytes = 128;
+                    PolyEtaPackedBytes = 96;
+                    break;
+                default:
+                    throw new ArgumentException("The mode " + mode + "is not supported by Crystals Dilithium!");
+            }
+            if(usingAes)
+            {
+                Symmetric = new Symmetric.AesSymmetric();
+            }
+            else
+            {
+                Symmetric = new Symmetric.ShakeSymmetric();
+            }
+
+            _random = random;
+            PolyVecHPackedBytes = Omega + K;
+            CryptoPublicKeyBytes = SeedBytes + K * PolyT1PackedBytes;
+            CryptoSecretKeyBytes = 3 * SeedBytes + L * PolyEtaPackedBytes + K * PolyEtaPackedBytes + K * PolyT0PackedBytes;
+            CryptoBytes = SeedBytes + L * PolyZPackedBytes + PolyVecHPackedBytes;
+
+            if (Gamma1 == (1 << 17))
+            {
+                PolyUniformGamma1NBytes = ((576 + Symmetric.Stream256BlockBytes - 1) / Symmetric.Stream256BlockBytes);
+            }
+            else if (Gamma1 == (1 << 19))
+            {
+                PolyUniformGamma1NBytes = ((640 + Symmetric.Stream256BlockBytes - 1) / Symmetric.Stream256BlockBytes);
+            }
+            else
+            {
+                throw new ArgumentException("Wrong Dilithium Gamma1!");
+            }
+        }
+        
+        public void GenerateKeyPair(out byte[] rho, out byte[] key, out byte[] tr, out byte[] s1_, out byte[] s2_, out byte[] t0_, out byte[] encT1)
+        {
+            byte[] SeedBuf = new byte[SeedBytes];
+            byte[] buf = new byte[2 * SeedBytes + CrhBytes];
+            byte[] rhoPrime = new byte[CrhBytes];
+
+            tr = new byte[SeedBytes];
+            rho = new byte[SeedBytes];
+            key = new byte[SeedBytes];
+            s1_ = new byte[L * PolyEtaPackedBytes];
+            s2_ = new byte[K * PolyEtaPackedBytes];
+            t0_ = new byte[K * PolyT0PackedBytes];
+            PolyVecMatrix Matrix = new PolyVecMatrix(this);
+
+            PolyVecL s1 = new PolyVecL(this), s1Hat;
+            PolyVecK s2 = new PolyVecK(this), t1 = new PolyVecK(this), t0 = new PolyVecK(this);
+
+            _random.NextBytes(SeedBuf);
+            
+            ShakeDigest Shake256Digest = new ShakeDigest(256);
+            Shake256Digest.BlockUpdate(SeedBuf, 0, SeedBytes);
+            Shake256Digest.OutputFinal(buf, 0, 2 * SeedBytes + CrhBytes);
+
+            rho = Arrays.CopyOfRange(buf, 0, SeedBytes);
+            rhoPrime = Arrays.CopyOfRange(buf, SeedBytes, SeedBytes + CrhBytes);
+            key = Arrays.CopyOfRange(buf, SeedBytes + CrhBytes, 2 * SeedBytes + CrhBytes);
+
+            Matrix.ExpandMatrix(rho);
+
+            s1.UniformEta(rhoPrime, (ushort)0);
+
+            s2.UniformEta(rhoPrime, (ushort)L);
+
+            s1Hat = new PolyVecL(this);
+
+            s1.CopyPolyVecL(s1Hat);
+            s1Hat.Ntt();
+
+            Matrix.PointwiseMontgomery(t1, s1Hat);
+
+            t1.Reduce();
+            t1.InverseNttToMont();
+
+            t1.AddPolyVecK(s2);
+            t1.ConditionalAddQ();
+            t1.Power2Round(t0);
+
+            encT1 = Packing.PackPublicKey(t1, this);
+
+            Shake256Digest.BlockUpdate(rho, 0, rho.Length);
+            Shake256Digest.BlockUpdate(encT1, 0, encT1.Length);
+            Shake256Digest.OutputFinal(tr, 0, SeedBytes);
+
+            Packing.PackSecretKey(t0_, s1_, s2_, t0, s1, s2, this);
+        }
+
+        public void SignSignature(byte[] sig, int siglen, byte[] msg, int msglen, byte[] rho, byte[] key, byte[] tr, byte[] t0Enc, byte[] s1Enc, byte[] s2Enc)
+        {
+            int n;
+            byte[] SeedBuf = new byte[3 * SeedBytes + 2 * CrhBytes];
+            byte[] mu = new byte[CrhBytes], rhoPrime = new byte[CrhBytes];
+            ushort nonce = 0;
+            PolyVecMatrix Matrix = new PolyVecMatrix(this);
+            PolyVecL s1 = new PolyVecL(this), y = new PolyVecL(this), z = new PolyVecL(this);
+            PolyVecK t0 = new PolyVecK(this), s2 = new PolyVecK(this), w1 = new PolyVecK(this), w0 = new PolyVecK(this), h = new PolyVecK(this);
+            Poly cp = new Poly(this);
+
+            Packing.UnpackSecretKey(t0, s1, s2, t0Enc, s1Enc, s2Enc, this);
+
+            ShakeDigest ShakeDigest256 = new ShakeDigest(256);
+            ShakeDigest256.BlockUpdate(tr, 0, SeedBytes);
+            ShakeDigest256.BlockUpdate(msg, 0, msglen);
+            ShakeDigest256.OutputFinal(mu, 0, CrhBytes);
+
+            if (_random != null)
+            {
+                _random.NextBytes(rhoPrime);
+            }
+            else
+            {
+                byte[] KeyMu = Arrays.CopyOf(key, SeedBytes + CrhBytes);
+                Array.Copy(mu, 0, KeyMu, SeedBytes, CrhBytes);
+                ShakeDigest256.BlockUpdate(KeyMu, 0, SeedBytes + CrhBytes);
+                ShakeDigest256.OutputFinal(rhoPrime, 0, CrhBytes);
+            }
+
+            Matrix.ExpandMatrix(rho);
+            
+            s1.Ntt();
+            s2.Ntt();
+            t0.Ntt();
+
+        rej:
+            y.UniformGamma1(rhoPrime, nonce++);
+            y.CopyPolyVecL(z);
+            z.Ntt();
+
+            Matrix.PointwiseMontgomery(w1, z);
+            
+            w1.Reduce();
+            w1.InverseNttToMont();
+
+            w1.ConditionalAddQ();
+            w1.Decompose(w0);
+            
+            w1.PackW1(sig);
+
+            ShakeDigest256.BlockUpdate(mu, 0, CrhBytes);
+            ShakeDigest256.BlockUpdate(sig, 0, K * PolyW1PackedBytes);
+            ShakeDigest256.OutputFinal(sig, 0, SeedBytes);
+
+            cp.Challenge(sig);
+
+            cp.PolyNtt();
+
+            z.PointwisePolyMontgomery(cp, s1);
+            z.InverseNttToMont();
+            z.AddPolyVecL(y);
+            z.Reduce();
+            if (z.CheckNorm(Gamma1 - Beta))
+            {
+                goto rej;
+            }
+            
+            h.PointwisePolyMontgomery(cp, s2);
+            h.InverseNttToMont();
+
+            w0.Subtract(h);
+            w0.Reduce();
+            if (w0.CheckNorm(Gamma2 - Beta))
+            {
+                goto rej;
+            }
+
+            h.PointwisePolyMontgomery(cp, t0);
+            h.InverseNttToMont();
+            h.Reduce();
+            if (h.CheckNorm(Gamma2))
+            {
+                goto rej;
+            }
+
+            w0.AddPolyVecK(h);
+
+            w0.ConditionalAddQ();
+
+            n = h.MakeHint(w0, w1);
+            if (n > Omega)
+            {
+                goto rej;
+            }
+
+            Packing.PackSignature(sig, sig, z, h, this);
+        }
+
+        public void Sign(byte[] sig, int siglen, byte[] msg, int mlen, byte[] rho, byte[] key, byte[] tr, byte[] t0, byte[] s1, byte[] s2)
+        {
+            SignSignature(sig, siglen, msg,  mlen, rho, key, tr, t0, s1, s2);
+        }
+
+        public bool SignVerify(byte[] sig, int siglen, byte[] msg, int msglen, byte[] rho, byte[] encT1)
+        {
+            byte[] buf = new byte[K * PolyW1PackedBytes], mu = new byte[CrhBytes], c = new byte[SeedBytes], c2 = new byte[SeedBytes];
+            Poly cp = new Poly(this);
+            PolyVecMatrix Matrix = new PolyVecMatrix(this);
+            PolyVecL z = new PolyVecL(this);
+            PolyVecK t1 = new PolyVecK(this), w1 = new PolyVecK(this), h = new PolyVecK(this);
+
+            if (siglen != CryptoBytes)
+            {
+                return false;
+            }
+
+            t1 = Packing.UnpackPublicKey(t1, encT1, this);
+            
+
+
+            if (!Packing.UnpackSignature(z, h, sig, this))
+            {
+                return false;
+            }
+            c = Arrays.CopyOfRange(sig, 0, SeedBytes);
+
+            if (z.CheckNorm(Gamma1 - Beta))
+            {
+                return false;
+            }
+            
+            ShakeDigest Shake256Digest = new ShakeDigest(256);
+            Shake256Digest.BlockUpdate(rho, 0, rho.Length);
+            Shake256Digest.BlockUpdate(encT1, 0, encT1.Length);
+            Shake256Digest.OutputFinal(mu, 0, SeedBytes);
+
+            Shake256Digest.BlockUpdate(mu, 0, SeedBytes);
+            Shake256Digest.BlockUpdate(msg, 0, msglen);
+            Shake256Digest.DoFinal(mu, 0);
+
+            cp.Challenge(c);
+
+            Matrix.ExpandMatrix(rho);
+
+            z.Ntt();
+            Matrix.PointwiseMontgomery(w1, z);
+
+            cp.PolyNtt();
+
+            t1.ShiftLeft();
+            t1.Ntt();
+            t1.PointwisePolyMontgomery(cp, t1);
+
+            w1.Subtract(t1);
+            w1.Reduce();
+            w1.InverseNttToMont();
+
+            w1.ConditionalAddQ();
+            w1.UseHint(w1, h);
+
+            w1.PackW1(buf);
+
+            Shake256Digest.BlockUpdate(mu, 0, CrhBytes);
+            Shake256Digest.BlockUpdate(buf, 0, K * PolyW1PackedBytes);
+            Shake256Digest.OutputFinal(c2, 0, SeedBytes);
+
+            for (int i = 0; i < SeedBytes; ++i)
+            {
+                if (c[i] != c2[i])
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+        
+        public bool SignOpen(byte[] msg, byte[] sig, int siglen, byte[] rho, byte[] t1)
+        {
+            return SignVerify(sig, siglen, msg, msg.Length, rho, t1);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyGenerationParameters.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyGenerationParameters.cs
new file mode 100644
index 000000000..e79129c59
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyGenerationParameters.cs
@@ -0,0 +1,18 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumKeyGenerationParameters
+        : KeyGenerationParameters
+    {
+        private DilithiumParameters parameters;
+
+        public DilithiumKeyGenerationParameters(SecureRandom random, DilithiumParameters parameters) : base(random, 255)
+        {
+            this.parameters = parameters;
+        }
+
+        public DilithiumParameters Parameters => parameters;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyPairGenerator.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyPairGenerator.cs
new file mode 100644
index 000000000..98bf32b2b
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyPairGenerator.cs
@@ -0,0 +1,34 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumKeyPairGenerator
+        : IAsymmetricCipherKeyPairGenerator
+
+    {
+        private SecureRandom random;
+        private DilithiumParameters parameters;
+
+        public void Init(KeyGenerationParameters param)
+        {
+            random = param.Random;
+            parameters = ((DilithiumKeyGenerationParameters)param).Parameters;
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            DilithiumEngine engine = parameters.GetEngine(random);
+            byte[] rho, key, tr, s1, s2, t0, encT1;
+            engine.GenerateKeyPair(out rho, out key, out tr, out s1, out s2, out t0, out encT1);
+            
+            //unpack sk
+
+            DilithiumPublicKeyParameters pubKey = new DilithiumPublicKeyParameters(parameters, rho, encT1);
+            DilithiumPrivateKeyParameters privKey = new DilithiumPrivateKeyParameters(parameters, rho, key, tr, s1, s2, t0, encT1);
+
+
+            return new AsymmetricCipherKeyPair(pubKey, privKey);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyParameters.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyParameters.cs
new file mode 100644
index 000000000..789f3f5ef
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumKeyParameters.cs
@@ -0,0 +1,17 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumKeyParameters
+        : AsymmetricKeyParameter
+    {
+        DilithiumParameters parameters;
+
+        public DilithiumKeyParameters(bool isPrivate, DilithiumParameters parameters) : base(isPrivate)
+        {
+            this.parameters = parameters;
+        }
+
+        public DilithiumParameters Parameters => parameters;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumParameters.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumParameters.cs
new file mode 100644
index 000000000..fb726473a
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumParameters.cs
@@ -0,0 +1,32 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumParameters
+        : ICipherParameters
+    {
+        public static DilithiumParameters Dilithium2 = new DilithiumParameters(2, false);
+        public static DilithiumParameters Dilithium2Aes = new DilithiumParameters(2, true);
+        
+        public static DilithiumParameters Dilithium3 = new DilithiumParameters(3, false);
+        public static DilithiumParameters Dilithium3Aes = new DilithiumParameters(3, true);
+        
+        public static DilithiumParameters Dilithium5 = new DilithiumParameters(5, false);
+        public static DilithiumParameters Dilithium5Aes = new DilithiumParameters(5, true);
+
+        private int k;
+        private bool usingAes;
+
+        private DilithiumParameters(int param, bool usingAes)
+        {
+            k = param;
+            this.usingAes = usingAes;
+        }
+
+        internal DilithiumEngine GetEngine(SecureRandom Random)
+        {
+            return new DilithiumEngine(k, Random, usingAes);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumPrivateKeyParameters.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumPrivateKeyParameters.cs
new file mode 100644
index 000000000..bb1b95ff6
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumPrivateKeyParameters.cs
@@ -0,0 +1,49 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumPrivateKeyParameters
+        : DilithiumKeyParameters
+    {
+        internal byte[] rho;
+        internal byte[] k;
+        internal byte[] tr;
+        internal byte[] s1;
+        internal byte[] s2;
+        internal byte[] t0;
+        
+        private byte[] t1;
+
+        public DilithiumPrivateKeyParameters(DilithiumParameters parameters,  byte[] rho, byte[] K, byte[] tr, byte[] s1, byte[] s2, byte[] t0, byte[] t1)
+            : base(true, parameters)
+        {
+            this.rho = Arrays.Clone(rho);
+            this.k = Arrays.Clone(K);
+            this.tr = Arrays.Clone(tr);
+            this.s1 = Arrays.Clone(s1);
+            this.s2 = Arrays.Clone(s2);
+            this.t0 = Arrays.Clone(t0);
+            this.t1 = Arrays.Clone(t1);
+        }
+        
+        public byte[] Rho => Arrays.Clone(rho);
+
+        public byte[] K => Arrays.Clone(k);
+
+        public byte[] Tr => Arrays.Clone(tr);
+
+        public byte[] S1 => Arrays.Clone(s1);
+
+        public byte[] S2 => Arrays.Clone(s2);
+        
+
+        public byte[] T0 => Arrays.Clone(t0);
+
+        public byte[] T1 => t1;
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.ConcatenateAll(rho, k, tr, s1, s2, t0);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumPublicKeyParameters.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumPublicKeyParameters.cs
new file mode 100644
index 000000000..04ac7e423
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumPublicKeyParameters.cs
@@ -0,0 +1,35 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumPublicKeyParameters
+        : DilithiumKeyParameters
+    {
+        internal byte[] rho;
+        internal byte[] t1;
+
+        public DilithiumPublicKeyParameters(DilithiumParameters parameters, byte[] pkEncoded)
+            : base(false, parameters)
+        {
+            this.rho = Arrays.CopyOfRange(pkEncoded, 0, DilithiumEngine.SeedBytes);
+            this.t1 = Arrays.CopyOfRange(pkEncoded, DilithiumEngine.SeedBytes, pkEncoded.Length);
+        }
+
+        public DilithiumPublicKeyParameters(DilithiumParameters parameters, byte[] rho, byte[] t1)
+            : base(false, parameters)
+        {
+            this.rho = Arrays.Clone(rho);
+            this.t1 = Arrays.Clone(t1);
+        }
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.Concatenate(rho, t1);
+        }
+
+        internal byte[] Rho => rho;
+
+        internal byte[] T1 => t1;
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/DilithiumSigner.cs b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumSigner.cs
new file mode 100644
index 000000000..89519f134
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/DilithiumSigner.cs
@@ -0,0 +1,55 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public class DilithiumSigner 
+        : IMessageSigner
+    {
+        private DilithiumPrivateKeyParameters privKey;
+        private DilithiumPublicKeyParameters pubKey;
+
+        private SecureRandom random;
+
+        public DilithiumSigner()
+        {
+        }
+
+        public void Init(bool forSigning, ICipherParameters param)
+        {
+            if (forSigning)
+            {
+                if (param is ParametersWithRandom)
+                {
+                    privKey = (DilithiumPrivateKeyParameters)((ParametersWithRandom)param).Parameters;
+                    random = ((ParametersWithRandom)param).Random;
+                }
+                else
+                {
+                    privKey = (DilithiumPrivateKeyParameters)param;
+                    random = null;
+                }
+            }
+            else
+            {
+                pubKey = (DilithiumPublicKeyParameters) param;
+            }
+
+        }
+
+        public byte[] GenerateSignature(byte[] message)
+        {
+            DilithiumEngine engine = privKey.Parameters.GetEngine(random);
+            byte[] sig = new byte[engine.CryptoBytes];
+            engine.Sign(sig, sig.Length, message, message.Length, privKey.rho, privKey.k, privKey.tr, privKey.t0, privKey.s1, privKey.s2);
+            return sig;
+        }
+
+        public bool VerifySignature(byte[] message, byte[] signature)
+        {
+            DilithiumEngine engine = pubKey.Parameters.GetEngine(random);
+            return engine.SignOpen(message,signature, signature.Length, pubKey.rho, pubKey.t1 );
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/Ntt.cs b/crypto/src/pqc/crypto/crystals/dilithium/Ntt.cs
new file mode 100644
index 000000000..292719058
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/Ntt.cs
@@ -0,0 +1,90 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class Ntt
+    {
+        private static int[] Zetas = {
+        0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468,
+        1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103,
+        2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549,
+        -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005,
+        2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439,
+        -3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299,
+        -1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596,
+        811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779,
+        -3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221,
+        -1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922,
+        3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047,
+        -671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430,
+        -3343383, 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618,
+        -3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856,
+        189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330,
+        1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961,
+        2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462,
+        266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, -3193378,
+        900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500,
+        -655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838,
+        342297, 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044,
+        2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974,
+        -3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970,
+        -1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, -1962642,
+        -1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031,
+        -542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993,
+        -2013608, 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385,
+        -3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107,
+        -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078,
+        -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893,
+        -2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687,
+        -554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782
+        };
+        public static void NTT(int[] r)
+        {
+            uint len, start, j, k;
+            int zeta, t;
+
+            k = 0;
+            for (len = 128; len > 0; len >>= 1)
+            {
+                for (start = 0; start < DilithiumEngine.N; start = j + len)
+                {
+                    zeta = Ntt.Zetas[++k];
+                    for (j = start; j < start + len; ++j)
+                    {
+                        t = Reduce.MontgomeryReduce((long)((long)zeta * (long)r[j + len]));
+                        r[j + len] = r[j] - t;
+                        r[j] = r[j] + t;
+                    }
+                }
+            }
+        }
+
+        public static void InverseNttToMont(int[] a)
+        {
+            uint start, len, j, k;
+            int t, zeta;
+            const int f = 41978; // (mont^2)/256
+
+
+            k = 256;
+            for (len = 1; len < DilithiumEngine.N; len <<= 1)
+            {
+                for (start = 0; start < DilithiumEngine.N; start = j + len)
+                {
+                    zeta = (-1) * Ntt.Zetas[--k];
+                    for (j = start; j < start + len; ++j)
+                    {
+                        t = a[j];
+                        a[j] = t + a[j + len];
+                        a[j + len] = t - a[j + len];
+                        a[j + len] = Reduce.MontgomeryReduce((long)((long)zeta * (long)a[j + len]));
+                    }
+
+                }
+            }
+
+            for (j = 0; j < DilithiumEngine.N; ++j)
+            {
+                a[j] = Reduce.MontgomeryReduce((long)((long)f * (long)a[j]));
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/Packing.cs b/crypto/src/pqc/crypto/crystals/dilithium/Packing.cs
new file mode 100644
index 000000000..0f1fec1d2
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/Packing.cs
@@ -0,0 +1,149 @@
+using Org.BouncyCastle.Utilities;
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class Packing
+    {
+
+        public static byte[] PackPublicKey(PolyVecK t1, DilithiumEngine Engine)
+        {
+            byte[] output = new byte[Engine.CryptoPublicKeyBytes - DilithiumEngine.SeedBytes];
+
+            for (int i = 0; i < Engine.K; i++)
+            {
+                Array.Copy(t1.Vec[i].PolyT1Pack(), 0, output, i * DilithiumEngine.PolyT1PackedBytes, DilithiumEngine.PolyT1PackedBytes );
+            }
+            return output;
+        }
+
+        public static PolyVecK UnpackPublicKey(PolyVecK t1, byte[] pk, DilithiumEngine Engine)
+        {
+            int i;
+            for (i = 0; i < Engine.K; ++i)
+            {
+                t1.Vec[i].PolyT1Unpack(Arrays.CopyOfRange(pk, i * DilithiumEngine.PolyT1PackedBytes, DilithiumEngine.SeedBytes + (i + 1) * DilithiumEngine.PolyT1PackedBytes));
+            }
+
+            return t1;
+        }
+
+        public static void PackSecretKey(byte[] t0_, byte[] s1_, byte[] s2_, PolyVecK t0, PolyVecL s1, PolyVecK s2, DilithiumEngine Engine)
+        {
+            int i;
+            
+
+            for (i = 0; i < Engine.L; ++i)
+            {
+                s1.Vec[i].PolyEtaPack(s1_, i * Engine.PolyEtaPackedBytes);
+            }
+
+            for (i = 0; i < Engine.K; ++i)
+            {
+                s2.Vec[i].PolyEtaPack(s2_, i * Engine.PolyEtaPackedBytes);
+            }
+
+            for (i = 0; i < Engine.K; ++i)
+            {
+                t0.Vec[i].PolyT0Pack(t0_,i * DilithiumEngine.PolyT0PackedBytes);
+            }
+        }
+
+        public static void UnpackSecretKey(PolyVecK t0, PolyVecL s1, PolyVecK s2, byte[] t0Enc, byte[] s1Enc, byte[] s2Enc, DilithiumEngine Engine)
+        {
+            int i;
+            for (i = 0; i < Engine.L; ++i)
+            {
+                s1.Vec[i].PolyEtaUnpack(s1Enc,i * Engine.PolyEtaPackedBytes);
+            }
+            for (i = 0; i < Engine.K; ++i)
+            {
+                s2.Vec[i].PolyEtaUnpack(s2Enc,i * Engine.PolyEtaPackedBytes);
+            }
+            for (i = 0; i < Engine.K; ++i)
+            {
+                t0.Vec[i].PolyT0Unpack(t0Enc,i * DilithiumEngine.PolyT0PackedBytes);
+            }
+        }
+
+        public static void PackSignature(byte[] sig, byte[] c, PolyVecL z, PolyVecK h, DilithiumEngine engine)
+        {
+            int i, j, k, end = 0;
+
+            Array.Copy(c, 0, sig, 0, DilithiumEngine.SeedBytes);
+            end += DilithiumEngine.SeedBytes;
+
+            for (i = 0; i < engine.L; ++i)
+            {
+                z.Vec[i].PackZ(sig, end + i * engine.PolyZPackedBytes);
+            }
+            end += engine.L * engine.PolyZPackedBytes;
+
+            for (i = 0; i < engine.Omega + engine.K; ++i)
+            {
+                sig[end + i] = 0;
+            }
+
+
+            k = 0;
+            for (i = 0; i < engine.K; ++i)
+            {
+                for (j = 0; j < DilithiumEngine.N; ++j)
+                {
+                    if (h.Vec[i].Coeffs[j] != 0)
+                    {
+                        sig[end + k++] = (byte)j;
+                    }
+                }
+                sig[end + engine.Omega + i] = (byte)k;
+            }
+            //Console.WriteLine("sig = " + Convert.ToHexString(sig));
+
+        }
+
+        public static bool UnpackSignature(PolyVecL z, PolyVecK h, byte[] sig, DilithiumEngine Engine)
+        {
+            int i, j, k;
+            
+            int end = DilithiumEngine.SeedBytes;
+            for (i = 0; i < Engine.L; ++i)
+            {
+                z.Vec[i].UnpackZ(Arrays.CopyOfRange(sig, end + i * Engine.PolyZPackedBytes, end + (i + 1) * Engine.PolyZPackedBytes));
+            }
+            end += Engine.L * Engine.PolyZPackedBytes;
+
+            k = 0;
+            for (i = 0; i < Engine.K; ++i)
+            {
+                for (j = 0; j < DilithiumEngine.N; ++j)
+                {
+                    h.Vec[i].Coeffs[j] = 0;
+                }
+
+                if ((sig[end + Engine.Omega + i] & 0xFF) < k || (sig[end + Engine.Omega + i] & 0xFF) > Engine.Omega)
+                {
+                    return false;
+                }
+
+                for (j = k; j < (sig[end + Engine.Omega + i] & 0xFF); ++j)
+                {
+                    if (j > k && (sig[end + j] & 0xFF) <= (sig[end + j - 1] & 0xFF))
+                    {
+                        return false;
+                    }
+                    h.Vec[i].Coeffs[sig[end + j] & 0xFF] = 1;
+                }
+
+                k = (int)(sig[end + Engine.Omega + i]);
+            }
+            for (j = k; j < Engine.Omega; ++j)
+            {
+                if ((sig[end + j] & 0xFF) != 0)
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/Poly.cs b/crypto/src/pqc/crypto/crystals/dilithium/Poly.cs
new file mode 100644
index 000000000..eb209f8a2
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/Poly.cs
@@ -0,0 +1,678 @@
+using Org.BouncyCastle.Crypto.Digests;
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class Poly
+    {
+        public int[] Coeffs { get; set; }
+
+        private int N;
+        private DilithiumEngine Engine;
+        private int PolyUniformNBlocks;
+        private Symmetric Symmetric;
+
+        public Poly(DilithiumEngine engine)
+        {
+            N = DilithiumEngine.N;
+            Coeffs = new int[N];
+            Engine = engine;
+            Symmetric = engine.Symmetric;
+            PolyUniformNBlocks = (768 + Symmetric.Stream128BlockBytes - 1) / Symmetric.Stream128BlockBytes;
+        }
+
+        public void UniformBlocks(byte[] seed, ushort nonce)
+        {
+            int i, ctr, off,
+            buflen = PolyUniformNBlocks * Symmetric.Stream128BlockBytes;
+            byte[] buf = new byte[buflen + 2];
+            
+            Symmetric.Stream128Init(seed, nonce);
+
+            Symmetric.Stream128SqueezeBlocks(buf, 0, buflen);
+
+            ctr = RejectUniform(Coeffs, 0, N, buf, buflen);
+
+            while (ctr < N)
+            {
+                off = buflen % 3;
+                for (i = 0; i < off; ++i)
+                {
+                    buf[i] = buf[buflen - off + i];
+                }
+                Symmetric.Stream128SqueezeBlocks(buf, off, Symmetric.Stream128BlockBytes);
+                buflen = Symmetric.Stream128BlockBytes + off;
+                ctr += RejectUniform(Coeffs, ctr, N - ctr, buf, buflen);
+            }
+
+
+        }
+
+        private static int RejectUniform(int[] coeffs, int off, int len, byte[] buf, int buflen)
+        {
+            int ctr, pos;
+            uint t;
+
+
+            ctr = pos = 0;
+            while (ctr < len && pos + 3 <= buflen)
+            {
+                t = (uint)(buf[pos++] & 0xFF);
+                t |= (uint)(buf[pos++] & 0xFF) << 8;
+                t |= (uint)(buf[pos++] & 0xFF) << 16;
+                t &= 0x7FFFFF;
+
+                if (t < DilithiumEngine.Q)
+                {
+                    coeffs[off + ctr++] = (int)t;
+                }
+            }
+            return ctr;
+
+        }
+
+        public void UniformEta(byte[] seed, ushort nonce)
+        {
+            int ctr, PolyUniformEtaNBlocks, eta = Engine.Eta;
+
+
+            if (Engine.Eta == 2)
+            {
+                PolyUniformEtaNBlocks = ((136 + Symmetric.Stream256BlockBytes - 1) / Symmetric.Stream256BlockBytes);
+            }
+            else if (Engine.Eta == 4)
+            {
+                PolyUniformEtaNBlocks = ((227 + Symmetric.Stream256BlockBytes - 1) / Symmetric.Stream256BlockBytes);
+            }
+            else
+            {
+                throw new ArgumentException("Wrong Dilithium Eta!");
+            }
+
+            int buflen = PolyUniformEtaNBlocks * Symmetric.Stream256BlockBytes;
+
+            byte[] buf = new byte[buflen];
+
+            Symmetric.Stream256Init(seed, nonce);
+            Symmetric.Stream256SqueezeBlocks(buf, 0, buflen);
+            ctr = RejectEta(Coeffs, 0, N, buf, buflen, eta);
+
+            while (ctr < DilithiumEngine.N)
+            {
+                Symmetric.Stream256SqueezeBlocks(buf, 0, Symmetric.Stream256BlockBytes);
+                ctr += RejectEta(Coeffs, ctr, N - ctr, buf, Symmetric.Stream256BlockBytes, eta);
+            }
+        }
+
+        private static int RejectEta(int[] coeffs, int off, int len, byte[] buf, int buflen, int eta)
+        {
+            int ctr, pos;
+            uint t0, t1;
+
+            ctr = pos = 0;
+
+            while (ctr < len && pos < buflen)
+            {
+                t0 = (uint)(buf[pos] & 0xFF) & 0x0F;
+                t1 = (uint)(buf[pos++] & 0xFF) >> 4;
+                if (eta == 2)
+                {
+                    if (t0 < 15)
+                    {
+                        t0 = t0 - (205 * t0 >> 10) * 5;
+                        coeffs[off + ctr++] = (int)(2 - t0);
+                    }
+                    if (t1 < 15 && ctr < len)
+                    {
+                        t1 = t1 - (205 * t1 >> 10) * 5;
+                        coeffs[off + ctr++] = (int)(2 - t1);
+                    }
+                }
+                else if (eta == 4)
+                {
+                    if (t0 < 9)
+                    {
+                        coeffs[off + ctr++] = (int)(4 - t0);
+                    }
+                    if (t1 < 9 && ctr < len)
+                    {
+                        coeffs[off + ctr++] = (int)(4 - t1);
+                    }
+                }
+            }
+            return ctr;
+        }
+
+        public void PointwiseMontgomery(Poly v, Poly w)
+        {
+            int i;
+            for (i = 0; i < N; ++i)
+            {
+                Coeffs[i] = Reduce.MontgomeryReduce((long)((long)v.Coeffs[i] * (long)w.Coeffs[i]));
+            }
+        }
+
+        public void PointwiseAccountMontgomery(PolyVecL u, PolyVecL v)
+        {
+            int i;
+            Poly t = new Poly(Engine);
+
+            PointwiseMontgomery(u.Vec[0], v.Vec[0]);
+
+            for (i = 1; i < Engine.L; ++i)
+            {
+                t.PointwiseMontgomery(u.Vec[i], v.Vec[i]);
+                AddPoly(t);
+            }
+        }
+
+        public void AddPoly(Poly a)
+        {
+            int i;
+            for (i = 0; i < N; i++)
+            {
+                Coeffs[i] += a.Coeffs[i];
+            }
+        }
+
+        public void Subtract(Poly b)
+        {
+            for (int i = 0; i < N; ++i)
+            {
+                Coeffs[i] -= b.Coeffs[i];
+            }
+        }
+
+        public void ReducePoly()
+        {
+            for (int i = 0; i < N; ++i)
+            {
+                Coeffs[i] = Reduce.Reduce32(Coeffs[i]);
+            }
+        }
+
+        public void PolyNtt()
+        {
+            Ntt.NTT(Coeffs);
+        }
+
+        public void InverseNttToMont()
+        {
+            Ntt.InverseNttToMont(Coeffs);
+        }
+
+        public void ConditionalAddQ()
+        {
+            for (int i = 0; i < N; ++i)
+            {
+                Coeffs[i] = Reduce.ConditionalAddQ(Coeffs[i]);
+            }
+        }
+
+        public void Power2Round(Poly a)
+        {
+            for (int i = 0; i < N; ++i)
+            {
+                int[] Power2Round = Rounding.Power2Round(Coeffs[i]);
+                Coeffs[i] = Power2Round[0];
+                a.Coeffs[i] = Power2Round[1];
+            }
+        }
+
+        public void PolyT0Pack(byte[] r, int off)
+        {
+            int i;
+            int[] t = new int[8];
+            for (i = 0; i < N / 8; ++i)
+            {
+                t[0] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 0];
+                t[1] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 1];
+                t[2] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 2];
+                t[3] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 3];
+                t[4] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 4];
+                t[5] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 5];
+                t[6] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 6];
+                t[7] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 7];
+
+                r[off + 13 * i + 0] = (byte)(t[0]);
+
+                r[off + 13 * i + 1] = (byte)(t[0] >> 8);
+                r[off + 13 * i + 1] = (byte)(r[off + 13 * i + 1] | (byte)(t[1] << 5));
+                r[off + 13 * i + 2] = (byte)(t[1] >> 3);
+                r[off + 13 * i + 3] = (byte)(t[1] >> 11);
+                r[off + 13 * i + 3] = (byte)(r[off + 13 * i + 3] | (byte)(t[2] << 2));
+                r[off + 13 * i + 4] = (byte)(t[2] >> 6);
+                r[off + 13 * i + 4] = (byte)(r[off + 13 * i + 4] | (byte)(t[3] << 7));
+                r[off + 13 * i + 5] = (byte)(t[3] >> 1);
+                r[off + 13 * i + 6] = (byte)(t[3] >> 9);
+                r[off + 13 * i + 6] = (byte)(r[off + 13 * i + 6] | (byte)(t[4] << 4));
+                r[off + 13 * i + 7] = (byte)(t[4] >> 4);
+                r[off + 13 * i + 8] = (byte)(t[4] >> 12);
+                r[off + 13 * i + 8] = (byte)(r[off + 13 * i + 8] | (byte)(t[5] << 1));
+                r[off + 13 * i + 9] = (byte)(t[5] >> 7);
+                r[off + 13 * i + 9] = (byte)(r[off + 13 * i + 9] | (byte)(t[6] << 6));
+                r[off + 13 * i + 10] = (byte)(t[6] >> 2);
+                r[off + 13 * i + 11] = (byte)(t[6] >> 10);
+                r[off + 13 * i + 11] = (byte)(r[off + 13 * i + 11] | (byte)(t[7] << 3));
+                r[off + 13 * i + 12] = (byte)(t[7] >> 5);
+            }
+        }
+
+        public void PolyT0Unpack(byte[] a, int off)
+        {
+            int i;
+            for (i = 0; i < N / 8; ++i)
+            {
+                Coeffs[8 * i + 0] =
+                    (
+                        (a[off + 13 * i + 0] & 0xFF) |
+                            ((a[off + 13 * i + 1] & 0xFF) << 8)
+                    ) & 0x1FFF;
+                Coeffs[8 * i + 1] =
+                    (
+                        (((a[off + 13 * i + 1] & 0xFF) >> 5) |
+                            ((a[off + 13 * i + 2] & 0xFF) << 3)) |
+                            ((a[off + 13 * i + 3] & 0xFF) << 11)
+                    ) & 0x1FFF;
+
+                Coeffs[8 * i + 2] =
+                    (
+                        (((a[off + 13 * i + 3] & 0xFF) >> 2) |
+                            ((a[off + 13 * i + 4] & 0xFF) << 6))
+                    ) & 0x1FFF;
+
+                Coeffs[8 * i + 3] =
+                    (
+                        (((a[off + 13 * i + 4] & 0xFF) >> 7) |
+                            ((a[off + 13 * i + 5] & 0xFF) << 1)) |
+                            ((a[off + 13 * i + 6] & 0xFF) << 9)
+                    ) & 0x1FFF;
+
+                Coeffs[8 * i + 4] =
+                    (
+                        (((a[off + 13 * i + 6] & 0xFF) >> 4) |
+                            ((a[off + 13 * i + 7] & 0xFF) << 4)) |
+                            ((a[off + 13 * i + 8] & 0xFF) << 12)
+                    ) & 0x1FFF;
+
+                Coeffs[8 * i + 5] =
+                    (
+                        (((a[off + 13 * i + 8] & 0xFF) >> 1) |
+                            ((a[off + 13 * i + 9] & 0xFF) << 7))
+                    ) & 0x1FFF;
+
+                Coeffs[8 * i + 6] =
+                    (
+                        (((a[off + 13 * i + 9] & 0xFF) >> 6) |
+                            ((a[off + 13 * i + 10] & 0xFF) << 2)) |
+                            ((a[off + 13 * i + 11] & 0xFF) << 10)
+                    ) & 0x1FFF;
+
+                Coeffs[8 * i + 7] =
+                    (
+                        ((a[off + 13 * i + 11] & 0xFF) >> 3 |
+                            ((a[off + 13 * i + 12] & 0xFF) << 5))
+                    ) & 0x1FFF;
+
+
+                Coeffs[8 * i + 0] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 0];
+                Coeffs[8 * i + 1] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 1];
+                Coeffs[8 * i + 2] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 2];
+                Coeffs[8 * i + 3] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 3];
+                Coeffs[8 * i + 4] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 4];
+                Coeffs[8 * i + 5] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 5];
+                Coeffs[8 * i + 6] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 6];
+                Coeffs[8 * i + 7] = (1 << (DilithiumEngine.D - 1)) - Coeffs[8 * i + 7];
+            }
+        }
+
+        public byte[] PolyT1Pack()
+        {
+            byte[] output = new byte[DilithiumEngine.PolyT1PackedBytes];
+            for (int i = 0; i < N / 4; ++i)
+            {
+                output[5 * i + 0] = (byte)(Coeffs[4 * i + 0] >> 0);
+                output[5 * i + 1] = (byte)((Coeffs[4 * i + 0] >> 8) | (Coeffs[4 * i + 1] << 2));
+                output[5 * i + 2] = (byte)((Coeffs[4 * i + 1] >> 6) | (Coeffs[4 * i + 2] << 4));
+                output[5 * i + 3] = (byte)((Coeffs[4 * i + 2] >> 4) | (Coeffs[4 * i + 3] << 6));
+                output[5 * i + 4] = (byte)(Coeffs[4 * i + 3] >> 2);
+            }
+            return output;
+        }
+
+        public void PolyT1Unpack(byte[] a)
+        {
+            int i;
+
+            for (i = 0; i < N / 4; ++i)
+            {
+                Coeffs[4 * i + 0] = (((a[5 * i + 0] & 0xFF) >> 0) | ((int)(a[5 * i + 1] & 0xFF) << 8)) & 0x3FF;
+                Coeffs[4 * i + 1] = (((a[5 * i + 1] & 0xFF) >> 2) | ((int)(a[5 * i + 2] & 0xFF) << 6)) & 0x3FF;
+                Coeffs[4 * i + 2] = (((a[5 * i + 2] & 0xFF) >> 4) | ((int)(a[5 * i + 3] & 0xFF) << 4)) & 0x3FF;
+                Coeffs[4 * i + 3] = (((a[5 * i + 3] & 0xFF) >> 6) | ((int)(a[5 * i + 4] & 0xFF) << 2)) & 0x3FF;
+            }
+        }
+
+        public void PolyEtaPack(byte[] r, int off)
+        {
+            int i;
+            byte[] t = new byte[8];
+
+            if (Engine.Eta == 2)
+            {
+                for (i = 0; i < N / 8; ++i)
+                {
+                    t[0] = (byte)(Engine.Eta - Coeffs[8 * i + 0]);
+                    t[1] = (byte)(Engine.Eta - Coeffs[8 * i + 1]);
+                    t[2] = (byte)(Engine.Eta - Coeffs[8 * i + 2]);
+                    t[3] = (byte)(Engine.Eta - Coeffs[8 * i + 3]);
+                    t[4] = (byte)(Engine.Eta - Coeffs[8 * i + 4]);
+                    t[5] = (byte)(Engine.Eta - Coeffs[8 * i + 5]);
+                    t[6] = (byte)(Engine.Eta - Coeffs[8 * i + 6]);
+                    t[7] = (byte)(Engine.Eta - Coeffs[8 * i + 7]);
+
+                    r[off + 3 * i + 0] = (byte)((t[0] >> 0) | (t[1] << 3) | (t[2] << 6));
+                    r[off + 3 * i + 1] = (byte)((t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7));
+                    r[off + 3 * i + 2] = (byte)((t[5] >> 1) | (t[6] << 2) | (t[7] << 5));
+                }
+            }
+            else if (Engine.Eta == 4)
+            {
+                for (i = 0; i < N / 2; ++i)
+                {
+                    t[0] = (byte)(Engine.Eta - Coeffs[2 * i + 0]);
+                    t[1] = (byte)(Engine.Eta - Coeffs[2 * i + 1]);
+                    r[off + i] = (byte)(t[0] | t[1] << 4);
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Eta needs to be 2 or 4!");
+            }
+        }
+
+        public void PolyEtaUnpack(byte[] a, int off)
+        {
+            int i, eta = Engine.Eta;
+
+            if (eta == 2)
+            {
+                for (i = 0; i < N / 8; ++i)
+                {
+                    Coeffs[8 * i + 0] = (((a[off + 3 * i + 0] & 0xFF) >> 0) & 7);
+                    Coeffs[8 * i + 1] = ((((a[off + 3 * i + 0] & 0xFF) >> 3)) & 7);
+                    Coeffs[8 * i + 2] = (((a[off + 3 * i + 0] & 0xFF) >> 6) | ((a[off + 3 * i + 1] & 0xFF) << 2) & 7);
+                    Coeffs[8 * i + 3] = ((((a[off + 3 * i + 1] & 0xFF) >> 1)) & 7);
+                    Coeffs[8 * i + 4] = ((((a[off + 3 * i + 1] & 0xFF) >> 4)) & 7);
+                    Coeffs[8 * i + 5] = (((a[off + 3 * i + 1] & 0xFF) >> 7) | ((a[off + 3 * i + 2] & 0xFF) << 1) & 7);
+                    Coeffs[8 * i + 6] = ((((a[off + 3 * i + 2] & 0xFF) >> 2)) & 7);
+                    Coeffs[8 * i + 7] = ((((a[off + 3 * i + 2] & 0xFF) >> 5)) & 7);
+
+                    Coeffs[8 * i + 0] = eta - Coeffs[8 * i + 0];
+                    Coeffs[8 * i + 1] = eta - Coeffs[8 * i + 1];
+                    Coeffs[8 * i + 2] = eta - Coeffs[8 * i + 2];
+                    Coeffs[8 * i + 3] = eta - Coeffs[8 * i + 3];
+                    Coeffs[8 * i + 4] = eta - Coeffs[8 * i + 4];
+                    Coeffs[8 * i + 5] = eta - Coeffs[8 * i + 5];
+                    Coeffs[8 * i + 6] = eta - Coeffs[8 * i + 6];
+                    Coeffs[8 * i + 7] = eta - Coeffs[8 * i + 7];
+                }
+            }
+            else if (eta == 4)
+            {
+                for (i = 0; i < N / 2; ++i)
+                {
+                    Coeffs[2 * i + 0] = ((a[off + i] & 0xFF) & 0x0F);
+                    Coeffs[2 * i + 1] = ((a[off + i] & 0xFF) >> 4);
+                    Coeffs[2 * i + 0] = eta - Coeffs[2 * i + 0];
+                    Coeffs[2 * i + 1] = eta - Coeffs[2 * i + 1];
+                }
+            }
+        }
+
+        public void UniformGamma1(byte[] seed, ushort nonce)
+        {
+            byte[] buf = new byte[Engine.PolyUniformGamma1NBytes * Symmetric.Stream256BlockBytes];
+            Symmetric.Stream256Init(seed, nonce);
+            Symmetric.Stream256SqueezeBlocks(buf, 0, buf.Length);
+            UnpackZ(buf);
+
+        }
+
+        public void PackZ(byte[] r, int offset)
+        {
+            int i;
+            uint[] t = new uint[4];
+            if (Engine.Gamma1 == (1 << 17))
+            {
+                for (i = 0; i < N / 4; ++i)
+                {
+                    t[0] = (uint)(Engine.Gamma1 - Coeffs[4 * i + 0]);
+                    t[1] = (uint)(Engine.Gamma1 - Coeffs[4 * i + 1]);
+                    t[2] = (uint)(Engine.Gamma1 - Coeffs[4 * i + 2]);
+                    t[3] = (uint)(Engine.Gamma1 - Coeffs[4 * i + 3]);
+
+                    r[offset + 9 * i + 0] = (byte)t[0];
+                    r[offset + 9 * i + 1] = (byte)(t[0] >> 8);
+                    r[offset + 9 * i + 2] = (byte)((byte)(t[0] >> 16) | (t[1] << 2));
+                    r[offset + 9 * i + 3] = (byte)(t[1] >> 6);
+                    r[offset + 9 * i + 4] = (byte)((byte)(t[1] >> 14) | (t[2] << 4));
+                    r[offset + 9 * i + 5] = (byte)(t[2] >> 4);
+                    r[offset + 9 * i + 6] = (byte)((byte)(t[2] >> 12) | (t[3] << 6));
+                    r[offset + 9 * i + 7] = (byte)(t[3] >> 2);
+                    r[offset + 9 * i + 8] = (byte)(t[3] >> 10);
+                }
+            }
+            else if (Engine.Gamma1 == (1 << 19))
+            {
+                for (i = 0; i < N / 2; ++i)
+                {
+                    t[0] = (uint)(Engine.Gamma1 - Coeffs[2 * i + 0]);
+                    t[1] = (uint)(Engine.Gamma1 - Coeffs[2 * i + 1]);
+
+                    r[offset + 5 * i + 0] = (byte)t[0];
+                    r[offset + 5 * i + 1] = (byte)(t[0] >> 8);
+                    r[offset + 5 * i + 2] = (byte)((byte)(t[0] >> 16) | (t[1] << 4));
+                    r[offset + 5 * i + 3] = (byte)(t[1] >> 4);
+                    r[offset + 5 * i + 4] = (byte)(t[1] >> 12);
+
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Wrong Dilithium Gamma1!");
+            }
+        }
+
+        public void UnpackZ(byte[] a)
+        {
+            int i;
+            if (Engine.Gamma1 == (1 << 17))
+            {
+                for (i = 0; i < N / 4; ++i)
+                {
+                    Coeffs[4 * i + 0] =
+                        (
+                            (((a[9 * i + 0] & 0xFF)) |
+                                ((a[9 * i + 1] & 0xFF) << 8)) |
+                                ((a[9 * i + 2] & 0xFF) << 16)
+                        ) & 0x3FFFF;
+                    Coeffs[4 * i + 1] =
+                        (
+                            (((a[9 * i + 2] & 0xFF) >> 2) |
+                                ((a[9 * i + 3] & 0xFF) << 6)) |
+                                ((a[9 * i + 4] & 0xFF) << 14)
+                        ) & 0x3FFFF;
+                    Coeffs[4 * i + 2] =
+                        (
+                            (((a[9 * i + 4] & 0xFF) >> 4) |
+                                ((a[9 * i + 5] & 0xFF) << 4)) |
+                                ((a[9 * i + 6] & 0xFF) << 12)
+                        ) & 0x3FFFF;
+                    Coeffs[4 * i + 3] =
+                        (
+                            (((a[9 * i + 6] & 0xFF) >> 6) |
+                                ((a[9 * i + 7] & 0xFF) << 2)) |
+                                ((a[9 * i + 8] & 0xFF) << 10)
+                        ) & 0x3FFFF;
+
+
+                    Coeffs[4 * i + 0] = Engine.Gamma1 - Coeffs[4 * i + 0];
+                    Coeffs[4 * i + 1] = Engine.Gamma1 - Coeffs[4 * i + 1];
+                    Coeffs[4 * i + 2] = Engine.Gamma1 - Coeffs[4 * i + 2];
+                    Coeffs[4 * i + 3] = Engine.Gamma1 - Coeffs[4 * i + 3];
+                }
+            }
+            else if (Engine.Gamma1 == (1 << 19))
+            {
+                for (i = 0; i < N / 2; ++i)
+                {
+                    Coeffs[2 * i + 0] =
+                        (
+                            (((a[5 * i + 0] & 0xFF)) |
+                                ((a[5 * i + 1] & 0xFF) << 8)) |
+                                ((a[5 * i + 2] & 0xFF) << 16)
+                        ) & 0xFFFFF;
+                    Coeffs[2 * i + 1] =
+                        (
+                            (((a[5 * i + 2] & 0xFF) >> 4) |
+                                ((a[5 * i + 3] & 0xFF) << 4)) |
+                                ((a[5 * i + 4] & 0xFF) << 12)
+                        ) & 0xFFFFF;
+
+                    Coeffs[2 * i + 0] = Engine.Gamma1 - Coeffs[2 * i + 0];
+                    Coeffs[2 * i + 1] = Engine.Gamma1 - Coeffs[2 * i + 1];
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Wrong Dilithiumn Gamma1!");
+            }
+        }
+
+        public void Decompose(Poly a)
+        {
+            int i;
+            for (i = 0; i < N; ++i)
+            {
+                int[] decomp = Rounding.Decompose(Coeffs[i], Engine.Gamma2);
+                a.Coeffs[i] = decomp[0];
+                Coeffs[i] = decomp[1];
+            }
+        }
+
+        public void PackW1(byte[] r, int off)
+        {
+            int i;
+
+            if (Engine.Gamma2 == (DilithiumEngine.Q - 1) / 88)
+            {
+                for (i = 0; i < N / 4; ++i)
+                {
+                    r[off + 3 * i + 0] = (byte)(((byte)Coeffs[4 * i + 0]) | (Coeffs[4 * i + 1] << 6));
+                    r[off + 3 * i + 1] = (byte)((byte)(Coeffs[4 * i + 1] >> 2) | (Coeffs[4 * i + 2] << 4));
+                    r[off + 3 * i + 2] = (byte)((byte)(Coeffs[4 * i + 2] >> 4) | (Coeffs[4 * i + 3] << 2));
+                }
+            }
+            else if (Engine.Gamma2 == (DilithiumEngine.Q - 1) / 32)
+            {
+                for (i = 0; i < N / 2; ++i)
+                {
+                    r[off + i] = (byte)(Coeffs[2 * i + 0] | (Coeffs[2 * i + 1] << 4));
+                }
+            }
+        }
+
+        public void Challenge(byte[] seed)
+        {
+            int i, b, pos;
+            ulong signs;
+            byte[] buf = new byte[Symmetric.Stream256BlockBytes];
+
+            ShakeDigest ShakeDigest256 = new ShakeDigest(256);
+            ShakeDigest256.BlockUpdate(seed, 0, DilithiumEngine.SeedBytes);
+            ShakeDigest256.Output(buf, 0, Symmetric.Stream256BlockBytes);
+
+            signs = 0;
+            for (i = 0; i < 8; ++i)
+            {
+                signs |= (ulong)(buf[i] & 0xFF) << 8 * i;
+            }
+
+            pos = 8;
+
+            for (i = 0; i < N; ++i)
+            {
+                Coeffs[i] = 0;
+            }
+
+            for (i = N - Engine.Tau; i < N; ++i)
+            {
+                do
+                {
+                    if (pos >= Symmetric.Stream256BlockBytes)
+                    {
+                        ShakeDigest256.Output(buf, 0, Symmetric.Stream256BlockBytes);
+                        pos = 0;
+                    }
+                    b = (buf[pos++] & 0xFF);
+                }
+                while (b > i);
+
+                Coeffs[i] = Coeffs[b];
+                Coeffs[b] = (int)(1 - 2 * (signs & 1));
+                signs = signs >> 1;
+            }
+        }
+
+        public bool CheckNorm(int B)
+        {
+            int i, t;
+
+            if (B > (DilithiumEngine.Q - 1) / 8)
+            {
+                return true;
+            }
+
+            for (i = 0; i < N; ++i)
+            {
+                t = Coeffs[i] >> 31;
+                t = Coeffs[i] - (t & 2 * Coeffs[i]);
+
+                if (t >= B)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+        public int PolyMakeHint(Poly a0, Poly a1)
+        {
+            int i, s = 0;
+
+            for (i = 0; i < N; ++i)
+            {
+                Coeffs[i] = Rounding.MakeHint(a0.Coeffs[i], a1.Coeffs[i], Engine);
+                s += Coeffs[i];
+            }
+            return s;
+        }
+
+        public void PolyUseHint(Poly a, Poly h)
+        {
+            for (int i = 0; i < DilithiumEngine.N; ++i)
+            {
+                Coeffs[i] = Rounding.UseHint(a.Coeffs[i], h.Coeffs[i], Engine.Gamma2);
+            }
+        }
+
+        public void ShiftLeft()
+        {
+            for (int i = 0; i < N; ++i)
+            {
+                Coeffs[i] <<= DilithiumEngine.D;
+            }
+        }
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/PolyVecK.cs b/crypto/src/pqc/crypto/crystals/dilithium/PolyVecK.cs
new file mode 100644
index 000000000..315ce6ab0
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/PolyVecK.cs
@@ -0,0 +1,150 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class PolyVecK
+    {
+        public readonly Poly[] Vec;
+        private readonly DilithiumEngine Engine;
+        //private readonly int Mode;
+        private readonly int K;
+        //private readonly int L;
+
+        public PolyVecK(DilithiumEngine Engine)
+        {
+            this.Engine = Engine;
+            //Mode = Engine.Mode;
+            K = Engine.K;
+            //L = Engine.L;
+            Vec = new Poly[K];
+
+            for (int i = 0; i < K; i++)
+            {
+                Vec[i] = new Poly(Engine);
+            }
+        }
+
+        public void UniformEta(byte[] seed, ushort nonce)
+        {
+            ushort n = nonce;
+            for (int i = 0; i < K; i++)
+            {
+                Vec[i].UniformEta(seed, n++);
+            }
+        }
+
+        public void Reduce()
+        {
+            for (int i = 0; i < K; i++)
+            {
+                Vec[i].ReducePoly();
+            }
+        }
+
+        public void Ntt()
+        {
+            for (int i= 0; i < K; ++i)
+            {
+                Vec[i].PolyNtt();
+            }
+        }
+
+        public void InverseNttToMont()
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].InverseNttToMont();
+            }
+        }
+        
+        public void AddPolyVecK(PolyVecK b)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].AddPoly(b.Vec[i]);
+            }
+        }
+
+        public void Subtract(PolyVecK v)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].Subtract(v.Vec[i]);
+            }
+        }
+
+        public void ConditionalAddQ()
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].ConditionalAddQ();
+            }
+        }
+
+        public void Power2Round(PolyVecK v)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].Power2Round(v.Vec[i]);
+            }
+        }
+
+        public void Decompose(PolyVecK v)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].Decompose(v.Vec[i]);
+            }
+        }
+
+        public void PackW1(byte[] r)
+        {
+            for (int i = 0; i < K; i++)
+            {
+                Vec[i].PackW1(r, i * Engine.PolyW1PackedBytes);
+            }
+        }
+
+        public void PointwisePolyMontgomery(Poly a, PolyVecK v)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].PointwiseMontgomery(a, v.Vec[i]);
+            }
+        }
+
+        public bool CheckNorm(int bound)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                if (Vec[i].CheckNorm(bound))
+                    return true;
+            }
+            return false;
+        }
+
+        public int MakeHint(PolyVecK v0, PolyVecK v1)
+        {
+            int s = 0;
+            for (int i = 0; i < K; ++i)
+            {
+                s += Vec[i].PolyMakeHint(v0.Vec[i], v1.Vec[i]);
+            }
+            return s;
+        }
+
+        public void UseHint(PolyVecK a, PolyVecK h)
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].PolyUseHint(a.Vec[i], h.Vec[i]);
+            }
+        }
+
+        public void ShiftLeft()
+        {
+            for (int i = 0; i < K; ++i)
+            {
+                Vec[i].ShiftLeft();
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/PolyVecL.cs b/crypto/src/pqc/crypto/crystals/dilithium/PolyVecL.cs
new file mode 100644
index 000000000..0a87c2070
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/PolyVecL.cs
@@ -0,0 +1,103 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class PolyVecL
+    {
+        public readonly Poly[] Vec;
+        //private DilithiumEngine Engine;
+        //private int Mode;
+        private readonly int L;
+        private readonly int K;
+
+        public PolyVecL(DilithiumEngine Engine)
+        {
+            //this.Engine = Engine;
+            //Mode = Engine.Mode;
+            L = Engine.L;
+            K = Engine.K;
+            Vec = new Poly[L];
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i] = new Poly(Engine);
+            }
+        }
+
+        public void UniformEta(byte[] seed, ushort nonce)
+        {
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i].UniformEta(seed, nonce++);
+            }
+        }
+
+        public void CopyPolyVecL(PolyVecL OutPoly)
+        {
+            for (int i = 0; i < L; i++)
+            {
+                for (int j = 0; j < DilithiumEngine.N; j++)
+                {
+                    OutPoly.Vec[i].Coeffs[j] = Vec[i].Coeffs[j];
+                }
+            }
+        }
+
+        public void InverseNttToMont()
+        {
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i].InverseNttToMont();
+            }
+        }
+
+        public void Ntt()
+        {
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i].PolyNtt();
+            }
+        }
+        
+        public void UniformGamma1(byte[] seed, ushort nonce)
+        {
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i].UniformGamma1(seed, (ushort)(L * nonce + i));
+            }
+        }
+
+        public void PointwisePolyMontgomery(Poly a, PolyVecL v)
+        {
+            for (int i = 0; i < L; ++i)
+            {
+                Vec[i].PointwiseMontgomery(a, v.Vec[i]);
+            }
+        }
+
+        public void AddPolyVecL(PolyVecL b)
+        {
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i].AddPoly(b.Vec[i]);
+            }
+        }
+
+        public void Reduce()
+        {
+            for (int i = 0; i < L; i++)
+            {
+                Vec[i].ReducePoly();
+            }
+        }
+
+        public bool CheckNorm(int bound)
+        {
+            for (int i = 0; i < L; ++i)
+            {
+                if (Vec[i].CheckNorm(bound))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/PolyVecMatrix.cs b/crypto/src/pqc/crypto/crystals/dilithium/PolyVecMatrix.cs
new file mode 100644
index 000000000..fa1af0560
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/PolyVecMatrix.cs
@@ -0,0 +1,41 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class PolyVecMatrix
+    {
+        private int K, L;
+        public PolyVecL[] Matrix;
+
+        public PolyVecMatrix(DilithiumEngine Engine)
+        {
+            K = Engine.K;
+            L = Engine.L;
+            Matrix = new PolyVecL[K];
+
+            for (int i = 0; i < K; i++)
+            {
+                Matrix[i] = new PolyVecL(Engine);
+            }
+        }
+
+        public void ExpandMatrix(byte[] rho)
+        {
+            int i, j;
+            for (i = 0; i < K; ++i)
+            {
+                for (j = 0; j < L; ++j)
+                {
+                    Matrix[i].Vec[j].UniformBlocks(rho, (ushort)((ushort) (i << 8) + j));
+                }
+            }
+        }
+
+        public void PointwiseMontgomery(PolyVecK t, PolyVecL v)
+        {
+            int i;
+            for (i = 0; i < K; ++i)
+            {
+                t.Vec[i].PointwiseAccountMontgomery(Matrix[i], v);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/Reduce.cs b/crypto/src/pqc/crypto/crystals/dilithium/Reduce.cs
new file mode 100644
index 000000000..0f8b95ef1
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/Reduce.cs
@@ -0,0 +1,30 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class Reduce
+    {
+        public static int MontgomeryReduce(long a)
+        {
+            int t;
+            t = (int)(a * DilithiumEngine.QInv);
+            t = (int)((a - (long)((long)t * (long)DilithiumEngine.Q)) >> 32);
+            //Console.Write("{0}, ", t);
+            return t;
+        }
+
+        public static int Reduce32(int a)
+        {
+            int t;
+            t = (a + (1 << 22)) >> 23;
+            t = a - t * DilithiumEngine.Q;
+            return t;
+        }
+        
+        public static int ConditionalAddQ(int a)
+        {
+            a += (a >> 31) & DilithiumEngine.Q;
+            return a;
+        }
+
+
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/Rounding.cs b/crypto/src/pqc/crypto/crystals/dilithium/Rounding.cs
new file mode 100644
index 000000000..f8deb77f4
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/Rounding.cs
@@ -0,0 +1,90 @@
+using System;
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    internal class Rounding
+    {
+        public static int[] Power2Round(int a)
+        {
+            int[] r = new int[2];
+
+            r[0] = (a + (1 << (DilithiumEngine.D - 1)) - 1) >> DilithiumEngine.D;
+            r[1] = a - (r[0] << DilithiumEngine.D);
+            return r;
+        }
+
+        public static int[] Decompose(int a, int gamma2)
+        {
+            int a0, a1;
+            a1 = (a + 127) >> 7;
+            if (gamma2 == (DilithiumEngine.Q - 1) / 32)
+            {
+                a1 = (a1 * 1025 + (1 << 21)) >> 22;
+                a1 &= 15;
+            }
+            else if (gamma2 == (DilithiumEngine.Q - 1) / 88)
+            {
+                a1 = (a1 * 11275 + (1 << 23)) >> 24;
+                a1 ^= ((43 - a1) >> 31) & a1;
+            }
+            else
+            {
+                throw new ArgumentException("Wrong Gamma2!");
+            }
+
+            a0 = a - a1 * 2 * gamma2;
+            a0 -= (((DilithiumEngine.Q - 1) / 2 - a0) >> 31) & DilithiumEngine.Q;
+            return new int[] { a0, a1 };
+        }
+
+        public static int MakeHint(int a0, int a1, DilithiumEngine engine)
+        {
+            int g2 = engine.Gamma2, q = DilithiumEngine.Q;
+            if (a0 <= g2 || a0 > q - g2 || (a0 == q - g2 && a1 == 0))
+            {
+                return 0;
+            }
+            return 1;
+        }
+
+        public static int UseHint(int a, int hint, int gamma2)
+        {
+            int a0, a1;
+
+            int[] intArray = Decompose(a, gamma2);
+            a0 = intArray[0];
+            a1 = intArray[1];
+
+            if (hint == 0)
+            {
+                return a1;
+            }
+
+            if (gamma2 == (DilithiumEngine.Q - 1) / 32)
+            {
+                if (a0 > 0)
+                {
+                    return (a1 + 1) & 15;
+                }
+                else
+                {
+                    return (a1 - 1) & 15;
+                }
+            }
+            else if (gamma2 == (DilithiumEngine.Q - 1) / 88)
+            {
+                if (a0 > 0)
+                {
+                    return (a1 == 43) ? 0 : a1 + 1;
+                }
+                else
+                {
+                    return (a1 == 0) ? 43 : a1 - 1;
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Wrong Gamma2!");
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/dilithium/Symmetric.cs b/crypto/src/pqc/crypto/crystals/dilithium/Symmetric.cs
new file mode 100644
index 000000000..6cea667c6
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/dilithium/Symmetric.cs
@@ -0,0 +1,126 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium
+{
+    public abstract class Symmetric
+    {
+        public int Stream128BlockBytes;
+        public int Stream256BlockBytes;
+        
+        Symmetric(int stream128, int stream256)
+        {
+            Stream128BlockBytes = stream128;
+            Stream256BlockBytes = stream256;
+        }
+        
+        internal abstract void Stream128Init(byte[] seed, ushort nonce);
+
+        internal abstract void Stream256Init(byte[] seed, ushort nonce);
+
+        internal abstract void Stream128SqueezeBlocks(byte[] output, int offset, int size);
+
+        internal abstract void Stream256SqueezeBlocks(byte[] output, int offset, int size);
+        
+        internal class AesSymmetric
+            : Symmetric
+        {
+
+            private SicBlockCipher cipher;
+
+            public AesSymmetric()
+                : base(64, 64)
+            {
+                cipher = new SicBlockCipher(AesUtilities.CreateEngine());
+            }
+
+            private void Aes128(byte[] output, int offset, int size)
+            {
+                byte[] buf = new byte[size];   // TODO: there might be a more efficient way of doing this...
+                for (int i = 0; i < size; i+= 16)
+                {
+                    cipher.ProcessBlock(buf, i + offset, output, i + offset);
+                }
+            }
+
+            private void StreamInit(byte[] key, ushort nonce)
+            {
+                byte[] expnonce = new byte[12];
+                expnonce[0] = (byte)nonce;
+                expnonce[1] = (byte)(nonce >> 8);
+                
+                ParametersWithIV kp = new ParametersWithIV(new KeyParameter(key, 0, 32), expnonce);
+                cipher.Init(true, kp);
+            }
+
+            internal override void Stream128Init(byte[] seed, ushort nonce)
+            {
+                StreamInit(seed, nonce);
+            }
+
+            internal override void Stream256Init(byte[] seed, ushort nonce)
+            {
+                StreamInit(seed, nonce);
+            }
+
+            internal override void Stream128SqueezeBlocks(byte[] output, int offset, int size)
+            {
+                Aes128(output, offset, size);
+            }
+
+            internal override void Stream256SqueezeBlocks(byte[] output, int offset, int size)
+            {
+                Aes128(output, offset, size);
+            }
+        }
+
+
+        internal class ShakeSymmetric
+            : Symmetric
+        {
+            private ShakeDigest digest128;
+            private ShakeDigest digest256;
+
+            public ShakeSymmetric()
+                : base(168, 136)
+            {
+                digest128 = new ShakeDigest(128);
+                digest256 = new ShakeDigest(256);
+            }
+
+            private void StreamInit(ShakeDigest digest, byte[] seed, ushort nonce)
+            {
+                digest.Reset();
+                byte[] temp = new byte[2];
+                temp[0] = (byte)nonce;
+                temp[1] = (byte)(nonce >> 8);
+
+                digest.BlockUpdate(seed, 0, seed.Length);
+                digest.BlockUpdate(temp, 0, temp.Length);
+            }
+
+
+            internal override void Stream128Init(byte[] seed, ushort nonce)
+            {
+                StreamInit(digest128, seed, nonce);
+            }
+
+            internal override void Stream256Init(byte[] seed, ushort nonce)
+            {
+                StreamInit(digest256, seed, nonce);
+            }
+
+            internal override void Stream128SqueezeBlocks(byte[] output, int offset, int size)
+            {
+                digest128.Output(output, offset, size);
+            }
+
+            internal override void Stream256SqueezeBlocks(byte[] output, int offset, int size)
+            {
+                digest256.Output(output, offset, size);
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/CBD.cs b/crypto/src/pqc/crypto/crystals/kyber/CBD.cs
new file mode 100644
index 000000000..e87edc007
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/CBD.cs
@@ -0,0 +1,52 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class Cbd
+    {
+        internal static void Eta(Poly r, byte[] bytes, int eta)
+        {
+            switch (eta)
+            {
+            case 2:
+            {
+                for (int i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    uint t = Pack.LE_To_UInt32(bytes, 4 * i);
+                    uint d = t & 0x55555555;
+                    d += (t >> 1) & 0x55555555;
+                    for (int j = 0; j < 8; j++)
+                    {
+                        short a = (short)((d >> (4 * j + 0)) & 0x3);
+                        short b = (short)((d >> (4 * j + eta)) & 0x3);
+                        r.m_coeffs[8 * i + j] = (short) (a - b);
+                    }
+                }
+                break;
+            }
+            case 3:
+            {
+                for (int i = 0; i < KyberEngine.N / 4; i++)
+                {
+                    uint t = Pack.LE_To_UInt24(bytes, 3 * i);
+                    uint d = t & 0x00249249;
+                    d += (t >> 1) & 0x00249249;
+                    d += (t >> 2) & 0x00249249;
+
+                    for (int j = 0; j < 4; j++)
+                    {
+                        short a = (short)((d >> (6 * j + 0)) & 0x7);
+                        short b = (short)((d >> (6 * j + 3)) & 0x7);
+                        r.m_coeffs[4 * i + j] = (short)(a - b);
+                    }
+                }
+                break;
+            }
+            default:
+                throw new ArgumentException("Wrong Eta");
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs
new file mode 100644
index 000000000..e30115a95
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs
@@ -0,0 +1,180 @@
+using System;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal class KyberEngine
+    {
+        private SecureRandom m_random;
+        private KyberIndCpa m_indCpa;
+        // Constant Parameters
+        public const int N = 256;
+        public const int Q = 3329;
+        public const int QInv = 62209;
+
+        public static int SymBytes = 32;
+        private const int SharedSecretBytes = 32;
+
+        public static int PolyBytes = 384;
+
+        public const int Eta2 = 2;
+
+        public int IndCpaMsgBytes = SymBytes;
+        public Symmetric Symmetric { get; private set; }
+
+
+        // Parameters
+        public int K { get; private set; }
+        public int PolyVecBytes { get; private set; }
+        public int PolyCompressedBytes { get; private set; }
+        public int PolyVecCompressedBytes { get; private set; }
+        public int Eta1 { get; private set; }
+        public int IndCpaPublicKeyBytes { get; private set; }
+        public int IndCpaSecretKeyBytes { get; private set; }
+        public int IndCpaBytes { get; private set; }
+        public int PublicKeyBytes { get; private set; }
+        public int SecretKeyBytes { get; private set; }
+        public int CipherTextBytes { get; private set; }
+
+        // Crypto
+        public int CryptoBytes { get; private set; }
+        public int CryptoSecretKeyBytes { get; private set; }
+        public int CryptoPublicKeyBytes { get; private set; }
+        public int CryptoCipherTextBytes { get; private set; }
+
+        public KyberEngine(int k, bool usingAes)
+        {
+            K = k;
+            switch (k)
+            {
+            case 2:
+                Eta1 = 3;
+                PolyCompressedBytes = 128;
+                PolyVecCompressedBytes = K * 320;
+                break;
+            case 3:
+                Eta1 = 2;
+                PolyCompressedBytes = 128;
+                PolyVecCompressedBytes = K * 320;
+                break;
+            case 4:
+                Eta1 = 2;
+                PolyCompressedBytes = 160;
+                PolyVecCompressedBytes = K * 352;
+                break;
+            }
+
+            PolyVecBytes = k * PolyBytes;
+            IndCpaPublicKeyBytes = PolyVecBytes + SymBytes;
+            IndCpaSecretKeyBytes = PolyVecBytes;
+            IndCpaBytes = PolyVecCompressedBytes + PolyCompressedBytes;
+            PublicKeyBytes = IndCpaPublicKeyBytes;
+            SecretKeyBytes = IndCpaSecretKeyBytes + IndCpaPublicKeyBytes + 2 * SymBytes;
+            CipherTextBytes = IndCpaBytes;
+
+            // Define Crypto Params
+            CryptoBytes = SharedSecretBytes;
+            CryptoSecretKeyBytes = SecretKeyBytes;
+            CryptoPublicKeyBytes = PublicKeyBytes;
+            CryptoCipherTextBytes = CipherTextBytes;
+
+            if (usingAes)
+            {
+                Symmetric = new Symmetric.AesSymmetric();
+            }
+            else
+            {
+                Symmetric = new Symmetric.ShakeSymmetric();
+            }
+
+            m_indCpa = new KyberIndCpa(this);
+        }
+
+        internal void Init(SecureRandom random)
+        {
+            m_random = random;
+        }
+
+        internal void GenerateKemKeyPair(out byte[] t, out byte[] rho, out byte[] s, out byte[] hpk, out byte[] nonce)
+        {
+            byte[] pk, sk;
+            m_indCpa.GenerateKeyPair(out pk, out sk);
+            s = Arrays.CopyOfRange(sk, 0, IndCpaSecretKeyBytes);
+            
+            hpk = new byte[32];
+            Symmetric.Hash_h(hpk, pk, 0);
+
+            nonce = new byte[SymBytes];
+            m_random.NextBytes(nonce);
+            
+            t = Arrays.CopyOfRange(pk, 0, IndCpaPublicKeyBytes - 32);
+            rho = Arrays.CopyOfRange(pk, IndCpaPublicKeyBytes - 32, IndCpaPublicKeyBytes);
+
+        }
+
+        internal void KemEncrypt(byte[] cipherText, byte[] sharedSecret, byte[] pk)
+        {
+            byte[] randBytes = new byte[SymBytes];
+            byte[] buf = new byte[2 * SymBytes];
+            byte[] kr = new byte[2 * SymBytes];
+
+            m_random.NextBytes(randBytes, 0, SymBytes);
+
+            Symmetric.Hash_h(randBytes, randBytes, 0);
+            Array.Copy(randBytes, 0, buf, 0, SymBytes);
+
+            Symmetric.Hash_h(buf, pk, SymBytes);
+
+            Symmetric.Hash_g(kr, buf);
+            
+            m_indCpa.Encrypt(cipherText, Arrays.CopyOfRange(buf, 0, SymBytes), pk, Arrays.CopyOfRange(kr, SymBytes, 2 * SymBytes));
+
+            Symmetric.Hash_h(kr, cipherText, SymBytes);
+
+            Symmetric.Kdf(sharedSecret, kr);
+        }
+
+        internal void KemDecrypt(byte[] sharedSecret, byte[] cipherText, byte[] secretKey)
+        {
+            byte[] buf = new byte[2 * SymBytes], kr = new byte[2 * SymBytes], cmp = new byte[CipherTextBytes];
+            byte[] pk = Arrays.CopyOfRange(secretKey, IndCpaSecretKeyBytes, secretKey.Length);
+            m_indCpa.Decrypt(buf, cipherText, secretKey);
+            Array.Copy(secretKey, SecretKeyBytes - 2 * SymBytes, buf, SymBytes, SymBytes);
+
+            Symmetric.Hash_g(kr, buf);
+
+
+            m_indCpa.Encrypt(cmp, Arrays.CopyOf(buf, SymBytes), pk, Arrays.CopyOfRange(kr, SymBytes, kr.Length));
+
+            bool fail = !Arrays.ConstantTimeAreEqual(cipherText, cmp);
+            
+            Symmetric.Hash_h(kr, cipherText, SymBytes);
+
+
+            CMov(kr, Arrays.CopyOfRange(secretKey, SecretKeyBytes - SymBytes, SecretKeyBytes), SymBytes, fail);
+
+            Symmetric.Kdf(sharedSecret, kr);
+        }
+
+        private void CMov(byte[] r, byte[] x, int len, bool b)
+        {
+            if (b)
+            {
+                Array.Copy(x, 0, r, 0, len);
+            }
+            else
+            {
+                Array.Copy(r, 0, r, 0, len);
+            }
+        }
+        
+        internal void RandomBytes(byte[] buf, int len)
+        {
+            m_random.NextBytes(buf,0,len);
+        }
+    }
+}
+
+
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs
new file mode 100644
index 000000000..9400b776e
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs
@@ -0,0 +1,238 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal class KyberIndCpa
+    {
+        private readonly KyberEngine m_engine;
+        private Symmetric m_symmetric;
+        internal KyberIndCpa(KyberEngine mEngine)
+        {
+            m_engine = mEngine;
+            m_symmetric = mEngine.Symmetric;
+        }
+        
+        private int GenerateMatrixNBlocks => ((12 * KyberEngine.N / 8 * (1 << 12) / KyberEngine.Q + m_symmetric.XofBlockBytes) / m_symmetric.XofBlockBytes);
+
+        private void GenerateMatrix(PolyVec[] a, byte[] seed, bool transposed)
+        {
+            int K = m_engine.K;
+
+            byte[] buf = new byte[GenerateMatrixNBlocks * m_symmetric.XofBlockBytes + 2];
+            for (int i = 0; i < K; i++)
+            {
+                for (int j = 0; j < K; j++)
+                {
+                    if (transposed)
+                    {
+                        m_symmetric.XofAbsorb(seed, (byte) i, (byte) j);
+                    }
+                    else
+                    {
+                        m_symmetric.XofAbsorb(seed, (byte) j, (byte) i);
+                    }
+                    m_symmetric.XofSqueezeBlocks(buf, 0, GenerateMatrixNBlocks * m_symmetric.XofBlockBytes);
+                    int buflen = GenerateMatrixNBlocks * m_symmetric.XofBlockBytes;
+                    int ctr = RejectionSampling(a[i].m_vec[j].m_coeffs, 0, KyberEngine.N, buf, buflen);
+                    while (ctr < KyberEngine.N)
+                    {
+                        int off = buflen % 3;
+                        for (int k = 0; k < off; k++)
+                        {
+                            buf[k] = buf[buflen - off + k];
+                        }
+                        m_symmetric.XofSqueezeBlocks(buf, off, m_symmetric.XofBlockBytes * 2);
+                        buflen = off + m_symmetric.XofBlockBytes;
+                        ctr += RejectionSampling(a[i].m_vec[j].m_coeffs, ctr, KyberEngine.N - ctr, buf, buflen);
+                    }
+
+                }
+            }
+            return;
+        }
+
+        private int RejectionSampling(short[] r, int off, int len, byte[] buf, int buflen)
+        {
+            int ctr = 0, pos = 0;
+            while (ctr < len && pos + 3 <= buflen)
+            {
+                ushort val0 = (ushort) ((((ushort) (buf[pos + 0] & 0xFF) >> 0) | ((ushort)(buf[pos + 1] & 0xFF) << 8)) & 0xFFF);
+                ushort val1 = (ushort) ((((ushort) (buf[pos + 1] & 0xFF) >> 4) | ((ushort)(buf[pos + 2] & 0xFF) << 4)) & 0xFFF);
+                pos += 3;
+
+                if (val0 < KyberEngine.Q)
+                {
+                    r[off + ctr++] = (short)val0;
+                }
+                if (ctr < len && val1 < KyberEngine.Q)
+                {
+                    r[off + ctr++] = (short)val1;
+                }
+            }
+
+            return ctr;
+        }
+
+        internal void GenerateKeyPair(out byte[] pk, out byte[] sk)
+        {
+            int K = m_engine.K;
+
+            byte[] buf = new byte[2 * KyberEngine.SymBytes];
+            byte nonce = 0;
+            PolyVec[] Matrix = new PolyVec[K];
+            PolyVec e = new PolyVec(m_engine), pkpv = new PolyVec(m_engine), skpv = new PolyVec(m_engine);
+
+            byte[] d = new byte[32];
+            m_engine.RandomBytes(d, 32);
+            
+            m_symmetric.Hash_g(buf, d);
+
+            byte[] PublicSeed = Arrays.CopyOfRange(buf, 0, KyberEngine.SymBytes);
+            byte[] NoiseSeed = Arrays.CopyOfRange(buf, KyberEngine.SymBytes, 2 * KyberEngine.SymBytes);
+
+            for (int i = 0; i < K; i++)
+            {
+                Matrix[i] = new PolyVec(m_engine);
+            }
+
+            GenerateMatrix(Matrix, PublicSeed, false);
+
+            for (int i = 0; i < K; i++) 
+            {
+                skpv.m_vec[i].GetNoiseEta1(NoiseSeed, nonce++);
+            }
+
+            for (int i = 0; i < K; i++)
+            {
+                e.m_vec[i].GetNoiseEta1(NoiseSeed, nonce++);
+            }
+            
+            skpv.Ntt();
+            e.Ntt();
+            
+            for (int i = 0; i < K; i++)
+            {
+                PolyVec.PointwiseAccountMontgomery(pkpv.m_vec[i], Matrix[i], skpv, m_engine);
+                pkpv.m_vec[i].ToMont();
+            }
+            
+            pkpv.Add(e);
+            pkpv.Reduce();
+
+            PackSecretKey(out sk, skpv);
+            PackPublicKey(out pk, pkpv, PublicSeed);
+        }
+
+        private void PackSecretKey(out byte[] sk, PolyVec skpv)
+        {
+            sk = new byte[m_engine.PolyVecBytes];
+            skpv.ToBytes(sk);
+        }
+
+        private void UnpackSecretKey(PolyVec skpv, byte[] sk)
+        {
+            skpv.FromBytes(sk);
+        }
+
+        private void PackPublicKey(out byte[] pk, PolyVec pkpv, byte[] seed)
+        {
+            pk = new byte[m_engine.IndCpaPublicKeyBytes];
+            pkpv.ToBytes(pk);
+            Array.Copy(seed, 0, pk, m_engine.PolyVecBytes, KyberEngine.SymBytes);
+        }
+
+        private void UnpackPublicKey(PolyVec pkpv, byte[] seed, byte[] pk)
+        {
+            pkpv.FromBytes(pk);
+            Array.Copy(pk, m_engine.PolyVecBytes, seed, 0, KyberEngine.SymBytes);
+        }
+
+        public void Encrypt(byte[] c, byte[] m, byte[] pk, byte[] coins)
+        {
+            int K = m_engine.K;
+
+            byte[] seed = new byte[KyberEngine.SymBytes];
+            byte nonce = 0;
+            PolyVec sp = new PolyVec(m_engine), pkpv = new PolyVec(m_engine), ep = new PolyVec(m_engine), bp = new PolyVec(m_engine);
+            PolyVec[] MatrixTransposed = new PolyVec[K];
+            Poly v = new Poly(m_engine), k = new Poly(m_engine), epp = new Poly(m_engine);
+
+            UnpackPublicKey(pkpv, seed, pk);
+
+            k.FromMsg(m);
+
+            for (int i = 0; i < K; i++)
+            {
+                MatrixTransposed[i] = new PolyVec(m_engine);
+            }
+
+            GenerateMatrix(MatrixTransposed, seed, true);
+
+            for (int i = 0; i < K; i++)
+            {
+                sp.m_vec[i].GetNoiseEta1(coins, nonce++);
+            }
+
+            for (int i = 0; i < K; i++)
+            {
+                ep.m_vec[i].GetNoiseEta2(coins, nonce++);
+            }
+            epp.GetNoiseEta2(coins, nonce++);
+
+            sp.Ntt();
+
+            for (int i = 0; i < K; i++)
+            {
+                PolyVec.PointwiseAccountMontgomery(bp.m_vec[i], MatrixTransposed[i], sp, m_engine);
+            }
+
+            PolyVec.PointwiseAccountMontgomery(v, pkpv, sp, m_engine);
+
+            bp.InverseNttToMont();
+
+            v.PolyInverseNttToMont();
+
+            bp.Add(ep);
+
+            v.Add(epp);
+            v.Add(k);
+
+            bp.Reduce();
+            v.PolyReduce();
+
+            PackCipherText(c, bp, v);
+        }
+
+        private void PackCipherText(byte[] r, PolyVec b, Poly v)
+        {
+            b.CompressPolyVec(r);
+            v.CompressPoly(r, m_engine.PolyVecCompressedBytes);
+        }
+
+        private void UnpackCipherText(PolyVec b, Poly v, byte[] c)
+        {
+            b.DecompressPolyVec(c);
+            v.DecompressPoly(c, m_engine.PolyVecCompressedBytes);
+        }
+
+        internal void Decrypt(byte[] m, byte[] c, byte[] sk)
+        {
+            PolyVec bp = new PolyVec(m_engine), skpv = new PolyVec(m_engine);
+            Poly v = new Poly(m_engine), mp = new Poly(m_engine);
+
+            UnpackCipherText(bp, v, c);
+            UnpackSecretKey(skpv, sk);
+
+            bp.Ntt();
+
+            PolyVec.PointwiseAccountMontgomery(mp, skpv, bp, m_engine);
+
+            mp.PolyInverseNttToMont();
+            mp.Subtract(v);
+            mp.PolyReduce();
+            mp.ToMsg(m);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs
new file mode 100644
index 000000000..6dac60625
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs
@@ -0,0 +1,28 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public sealed class KyberKemExtractor
+        : IEncapsulatedSecretExtractor
+    {
+        private readonly KyberKeyParameters m_key;
+        private readonly KyberEngine m_engine;
+
+
+        public KyberKemExtractor(KyberKeyParameters privParams)
+        {
+            m_key = privParams;
+            m_engine = m_key.Parameters.Engine;
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            byte[] sharedSecret = new byte[m_engine.CryptoBytes];
+            m_engine.KemDecrypt(sharedSecret, encapsulation, ((KyberPrivateKeyParameters)m_key).GetEncoded());
+            return sharedSecret;
+        }
+
+        public int EncapsulationLength => m_engine.CryptoCipherTextBytes;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs
new file mode 100644
index 000000000..394890838
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs
@@ -0,0 +1,83 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public sealed class KyberKemGenerator
+        : IEncapsulatedSecretGenerator
+    {
+        // the source of randomness
+        private SecureRandom m_random;
+
+        public KyberKemGenerator(SecureRandom random)
+        {
+            m_random = random;
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            KyberPublicKeyParameters key = (KyberPublicKeyParameters) recipientKey;
+            KyberEngine engine = key.Parameters.Engine;
+            engine.Init(m_random);
+            byte[] cipherText = new byte[engine.CryptoCipherTextBytes];
+            byte[] sessionKey = new byte[engine.CryptoBytes];
+            engine.KemEncrypt(cipherText, sessionKey, key.GetEncoded());
+            return new SecretWithEncapsulationImpl(sessionKey, cipherText);
+        }
+
+        private sealed class SecretWithEncapsulationImpl
+            : ISecretWithEncapsulation
+        {
+            private volatile bool m_hasBeenDestroyed = false;
+
+            private byte[] m_sessionKey;
+            private byte[] m_cipherText;
+
+            internal SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
+            {
+                m_sessionKey = sessionKey;
+                m_cipherText = cipher_text;
+            }
+
+            public byte[] GetSecret()
+            {
+                CheckDestroyed();
+
+                return Arrays.Clone(m_sessionKey);
+            }
+
+            public byte[] GetEncapsulation()
+            {
+                CheckDestroyed();
+
+                return Arrays.Clone(m_cipherText);
+            }
+
+            public void Dispose()
+            {
+                if (!m_hasBeenDestroyed)
+                {
+                    m_hasBeenDestroyed = true;
+                    Arrays.Clear(m_sessionKey);
+                    Arrays.Clear(m_cipherText);
+                }
+            }
+
+            internal bool IsDestroyed()
+            {
+                return m_hasBeenDestroyed;
+            }
+
+            private void CheckDestroyed()
+            {
+                if (IsDestroyed())
+                {
+                    throw new ArgumentException("data has been destroyed");
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs
new file mode 100644
index 000000000..e42d6fdeb
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs
@@ -0,0 +1,19 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public sealed class KyberKeyGenerationParameters
+        : KeyGenerationParameters
+    {
+        private readonly KyberParameters m_parameters;
+
+        public KyberKeyGenerationParameters(SecureRandom random, KyberParameters KyberParameters)
+            : base(random, 256)
+        {
+            m_parameters = KyberParameters;
+        }
+
+        public KyberParameters Parameters => m_parameters;
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs
new file mode 100644
index 000000000..442944042
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs
@@ -0,0 +1,42 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberKeyPairGenerator
+        : IAsymmetricCipherKeyPairGenerator
+    {
+        private KyberParameters KyberParams;
+        
+        private SecureRandom random;
+
+        private void Initialize(
+            KeyGenerationParameters param)
+        {
+            this.KyberParams = ((KyberKeyGenerationParameters)param).Parameters;;
+            this.random = param.Random; 
+        }
+
+        private AsymmetricCipherKeyPair GenKeyPair()
+        {
+            KyberEngine engine = KyberParams.Engine;
+            engine.Init(random);
+            byte[] s, hpk, nonce, t, rho;
+            engine.GenerateKemKeyPair(out t, out rho, out s, out hpk, out nonce);
+
+            KyberPublicKeyParameters pubKey = new KyberPublicKeyParameters(KyberParams, t, rho);
+            KyberPrivateKeyParameters privKey = new KyberPrivateKeyParameters(KyberParams, s, hpk, nonce, t, rho);
+            return new AsymmetricCipherKeyPair(pubKey, privKey);
+        }
+
+        public void Init(KeyGenerationParameters param)
+        {
+            this.Initialize(param);
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            return GenKeyPair();
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs
new file mode 100644
index 000000000..9fb26c2ad
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs
@@ -0,0 +1,18 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public abstract class KyberKeyParameters
+        : AsymmetricKeyParameter
+    {
+        private readonly KyberParameters m_parameters;
+
+        public KyberKeyParameters(bool isPrivate, KyberParameters parameters)
+            : base(isPrivate)
+        {
+            m_parameters = parameters;
+        }
+
+        public KyberParameters Parameters => m_parameters;
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs
new file mode 100644
index 000000000..00bc3950b
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs
@@ -0,0 +1,34 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public sealed class KyberParameters
+        : ICipherParameters
+    {
+        public static KyberParameters kyber512 = new KyberParameters("kyber512", 2, 128, false);
+        public static KyberParameters kyber768 = new KyberParameters("kyber768", 3, 192, false);
+        public static KyberParameters kyber1024 = new KyberParameters("kyber1024", 4, 256, false);
+        public static KyberParameters kyber512_aes = new KyberParameters("kyber512-aes", 2, 128, true);
+        public static KyberParameters kyber768_aes = new KyberParameters("kyber768-aes", 3, 192, true);
+        public static KyberParameters kyber1024_aes = new KyberParameters("kyber1024-aes", 4, 256, true);
+
+        private string m_name;
+        private int m_sessionKeySize;
+        private KyberEngine m_engine;
+
+        public KyberParameters(string name, int k, int sessionKeySize, bool usingAes)
+        {
+            m_name = name;
+            this.m_sessionKeySize = sessionKeySize;
+            m_engine = new KyberEngine(k, usingAes);
+        }
+
+        public string Name => m_name;
+
+        public int K => m_engine.K;
+
+        public int SessionKeySize => m_sessionKeySize;
+
+        internal KyberEngine Engine => m_engine;
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs
new file mode 100644
index 000000000..c9cf3a360
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs
@@ -0,0 +1,36 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberPrivateKeyParameters
+        : KyberKeyParameters
+    {
+        private readonly byte[] m_s;
+        private readonly byte[] m_hpk;
+        private readonly byte[] m_nonce;
+        private readonly byte[] m_t;
+        private readonly byte[] m_rho;
+
+        public KyberPrivateKeyParameters(KyberParameters parameters, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho)
+            : base(true, parameters)
+        {
+            m_s = Arrays.Clone(s);
+            m_hpk = Arrays.Clone(hpk);
+            m_nonce = Arrays.Clone(nonce);
+            m_t = Arrays.Clone(t);
+            m_rho = Arrays.Clone(rho);
+        }
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.ConcatenateAll(m_s, m_t, m_rho, m_hpk, m_nonce);
+        }
+
+        internal byte[] S => m_s;
+        internal byte[] Hpk => m_hpk;
+        internal byte[] Nonce => m_nonce;
+        internal byte[] T => m_t;
+        internal byte[] Rho => m_rho;
+
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs
new file mode 100644
index 000000000..efc582abc
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs
@@ -0,0 +1,34 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public sealed class KyberPublicKeyParameters
+        : KyberKeyParameters
+    {
+        private readonly byte[] m_t;
+        private readonly byte[] m_rho;
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.Concatenate(m_t, m_rho);
+        }
+
+        public KyberPublicKeyParameters(KyberParameters parameters, byte[] encoding)
+            : base(false, parameters)
+        {
+            m_t = Arrays.CopyOfRange(encoding, 0, encoding.Length - KyberEngine.SymBytes);
+            m_rho = Arrays.CopyOfRange(encoding, encoding.Length - KyberEngine.SymBytes, encoding.Length);
+        }
+
+        public KyberPublicKeyParameters(KyberParameters parameters, byte[] t, byte[] rho)
+            : base(false, parameters)
+        {
+            m_t = Arrays.Clone(t);
+            m_rho = Arrays.Clone(rho);
+        }
+
+        internal byte[] T => m_t;
+        internal byte[] Rho => m_rho;
+    }
+}
+    
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Ntt.cs b/crypto/src/pqc/crypto/crystals/kyber/Ntt.cs
new file mode 100644
index 000000000..1b0c05c8e
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Ntt.cs
@@ -0,0 +1,91 @@
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class Ntt
+    {
+        internal static readonly short[] Zetas = {
+            2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962,
+            2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017,
+            732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047,
+            1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830,
+            107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226,
+            430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574,
+            1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349,
+            418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193,
+            1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459,
+            478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628
+        };
+
+        internal static readonly short[] ZetasInv = {
+            1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535,
+            1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465,
+            1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685,
+            1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235,
+            3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652,
+            1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853,
+            1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552,
+            2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871,
+            829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171,
+            3127, 3042, 1907, 1836, 1517, 359, 758, 1441
+        };
+
+        private static short FactorQMulMont(short a, short b)
+        {
+            return Reduce.MontgomeryReduce(a * b);
+        }
+
+        internal static void NTT(short[] r)
+        {
+            int j = 0, k = 1;
+            for (int len = 128; len >= 2; len >>= 1)
+            {
+                for (int start = 0; start < 256; start = j + len)
+                {
+                    short zeta = Zetas[k++];
+                    for (j = start; j < start + len; ++j)
+                    {
+                        short t = FactorQMulMont(zeta, r[j + len]);
+                        r[j + len] = (short)(r[j] - t);
+                        r[j] = (short)(r[j] + t);
+                    }
+                }
+            }
+        }
+
+        internal static void InvNTT(short[] r)
+        {
+            int j = 0, k = 0;
+            for (int len = 2; len <= 128; len <<= 1)
+            {
+                for (int start = 0; start < 256; start = j + len)
+                {
+                    short zeta = ZetasInv[k++];
+                    for (j = start; j < start + len; ++j)
+                    {
+                        short t = r[j];
+                        r[j] = Reduce.BarrettReduce((short)(t + r[j + len]));
+                        r[j + len] = (short)(t - r[j + len]);
+                        r[j + len] = FactorQMulMont(zeta, r[j + len]);
+
+                    }
+                }
+            }
+            for (int i = 0; i < 256; ++i)
+            {
+                r[i] = FactorQMulMont(r[i], ZetasInv[127]);
+            }
+        }
+
+        internal static void BaseMult(short[] r, int off, short a0, short a1, short b0, short b1, short zeta)
+        {
+            short outVal0 = FactorQMulMont(a1, b1);
+            outVal0 = FactorQMulMont(outVal0, zeta);
+            outVal0 += FactorQMulMont(a0, b0);
+            r[off] = outVal0;
+
+            short outVal1 = FactorQMulMont(a0, b1);
+            outVal1 += FactorQMulMont(a1, b0);
+            r[off + 1] = outVal1;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Poly.cs b/crypto/src/pqc/crypto/crystals/kyber/Poly.cs
new file mode 100644
index 000000000..db996f41a
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Poly.cs
@@ -0,0 +1,248 @@
+
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal class Poly
+    {
+        private KyberEngine m_engine;
+        public short[] m_coeffs = new short[KyberEngine.N];
+        private Symmetric m_symmetric;
+        public Poly(KyberEngine mEngine)
+        {
+            m_engine = mEngine;
+            m_symmetric = mEngine.Symmetric;
+        }
+        
+        internal short[] Coeffs => m_coeffs;
+
+        internal void GetNoiseEta1(byte[] seed, byte nonce)
+        {
+            byte[] buf = new byte[m_engine.Eta1 * KyberEngine.N / 4];
+            m_symmetric.Prf(buf, seed, nonce);
+            Cbd.Eta(this, buf, m_engine.Eta1);
+        }
+
+        internal void GetNoiseEta2(byte[] seed, byte nonce)
+        {
+            byte[] buf = new byte[KyberEngine.Eta2 * KyberEngine.N / 4];
+            m_symmetric.Prf(buf, seed, nonce);
+            Cbd.Eta(this, buf, KyberEngine.Eta2);
+        }
+
+        internal void PolyNtt()
+        {
+            Ntt.NTT(Coeffs);
+            PolyReduce();
+        }
+
+        internal void PolyInverseNttToMont()
+        {
+            Ntt.InvNTT(Coeffs);
+        }
+
+        internal static void BaseMultMontgomery(Poly r, Poly a, Poly b)
+        {
+            for (int i = 0; i < KyberEngine.N/4; i++)
+            {
+                Ntt.BaseMult(r.Coeffs, 4 * i,
+                    a.Coeffs[4 * i], a.Coeffs[4 * i + 1],
+                    b.Coeffs[4 * i], b.Coeffs[4 * i + 1],
+                    Ntt.Zetas[64 + i]);
+                Ntt.BaseMult(r.Coeffs, 4 * i + 2,
+                    a.Coeffs[4 * i + 2], a.Coeffs[4 * i + 3],
+                    b.Coeffs[4 * i + 2], b.Coeffs[4 * i + 3],
+                    (short) (-1  * Ntt.Zetas[64 + i]));
+            }
+        }
+
+        internal void ToMont()
+        {
+            const short f = (short) ((1UL << 32) % KyberEngine.Q);
+            for (int i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = Reduce.MontgomeryReduce(Coeffs[i] * f);
+            }
+        }
+
+        internal void Add(Poly a)
+        {
+            for (int i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] += a.Coeffs[i];
+            }
+        }
+
+        internal void Subtract(Poly a)
+        {
+            for (int i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = (short) (a.Coeffs[i] - Coeffs[i]);
+            }
+        }
+
+        internal void PolyReduce()
+        {
+            for (int i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = Reduce.BarrettReduce(Coeffs[i]);
+            }
+        }
+
+        internal void CompressPoly(byte[] r, int off)
+        {
+            byte[] t = new byte[8];
+            int count = 0;
+            CondSubQ();
+
+            if (m_engine.PolyCompressedBytes == 128)
+            {
+                for (int i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    for (int j = 0; j < 8; j++)
+                    {
+                        t[j] =
+                            (byte)(((((Coeffs[8 * i + j]) << 4)
+                                +
+                                (KyberEngine.Q / 2)
+                            ) / KyberEngine.Q)
+                                & 15);
+                    }
+
+                    r[off + count + 0] = (byte)(t[0] | (t[1] << 4));
+                    r[off + count + 1] = (byte)(t[2] | (t[3] << 4));
+                    r[off + count + 2] = (byte)(t[4] | (t[5] << 4));
+                    r[off + count + 3] = (byte)(t[6] | (t[7] << 4));
+                    count += 4;
+                }
+            }
+            else if (m_engine.PolyCompressedBytes == 160)
+            {
+                for (int i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    for (int j = 0; j < 8; j++)
+                    {
+                        t[j] =
+                            (byte)((((Coeffs[8 * i + j] << 5)
+                                +
+                                (KyberEngine.Q / 2)
+                            ) / KyberEngine.Q
+                            ) & 31
+                            );
+                    }
+                    r[off + count + 0] = (byte)((t[0] >> 0) | (t[1] << 5));
+                    r[off + count + 1] = (byte)((t[1] >> 3) | (t[2] << 2) | (t[3] << 7));
+                    r[off + count + 2] = (byte)((t[3] >> 1) | (t[4] << 4));
+                    r[off + count + 3] = (byte)((t[4] >> 4) | (t[5] << 1) | (t[6] << 6));
+                    r[off + count + 4] = (byte)((t[6] >> 2) | (t[7] << 3));
+                    count += 5;
+                }
+            }
+            else
+            {
+                throw new ArgumentException("PolyCompressedBytes is neither 128 or 160!");
+            }
+        }
+
+        internal void DecompressPoly(byte[] CompressedCipherText, int off)
+        {
+            int count = off;
+
+            if (m_engine.PolyCompressedBytes == 128)
+            {
+                for (int i = 0; i < KyberEngine.N / 2; i++)
+                {
+                    Coeffs[2 * i + 0]  = (short)((((short)((CompressedCipherText[count] & 0xFF) & 15) * KyberEngine.Q) + 8) >> 4);
+                    Coeffs[2 * i + 1] = (short)((((short)((CompressedCipherText[count] & 0xFF) >> 4) * KyberEngine.Q) + 8) >> 4);
+                    count += 1;
+                }
+            }
+            else if (m_engine.PolyCompressedBytes == 160)
+            {
+                byte[] t = new byte[8];
+                for (int i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    t[0] = (byte)((CompressedCipherText[count + 0] & 0xFF) >> 0);
+                    t[1] = (byte)(((CompressedCipherText[count + 0] & 0xFF) >> 5) | ((CompressedCipherText[count + 1] & 0xFF) << 3));
+                    t[2] = (byte)((CompressedCipherText[count + 1] & 0xFF) >> 2);
+                    t[3] = (byte)(((CompressedCipherText[count + 1] & 0xFF) >> 7) | ((CompressedCipherText[count + 2] & 0xFF) << 1));
+                    t[4] = (byte)(((CompressedCipherText[count + 2] & 0xFF) >> 4) | ((CompressedCipherText[count + 3] & 0xFF) << 4));
+                    t[5] = (byte)((CompressedCipherText[count + 3] & 0xFF) >> 1);
+                    t[6] = (byte)(((CompressedCipherText[count + 3] & 0xFF) >> 6) | ((CompressedCipherText[count + 4] & 0xFF) << 2));
+                    t[7] = (byte)((CompressedCipherText[count + 4] & 0xFF) >> 3);
+                    count += 5;
+                    for (int j = 0; j < 8; j++)
+                    {
+                        Coeffs[8 * i + j] = (short)(((t[j] & 31) * KyberEngine.Q + 16) >> 5);
+                    }
+                }
+            }
+            else
+            {
+                throw new ArgumentException("PolyCompressedBytes is neither 128 or 160!");
+            }
+        }
+        
+        internal void ToBytes(byte[] r, int off)
+        {
+            CondSubQ();
+
+            for (int i = 0; i < KyberEngine.N/2; i++)
+            {
+                ushort t0 = (ushort) Coeffs[2 * i];
+                ushort t1 = (ushort) Coeffs[2 * i + 1];
+                r[off + 3 * i + 0] = (byte) (ushort) (t0 >> 0);
+                r[off + 3 * i + 1] = (byte)((t0 >> 8) | (ushort) (t1 << 4));
+                r[off + 3 * i + 2] = (byte) (ushort) (t1 >> 4);
+            }
+        }
+
+        internal void FromBytes(byte[] a, int off)
+        {
+            for (int i = 0; i < KyberEngine.N / 2; i++)
+            {
+                Coeffs[2 * i] = (short) ((((a[off + 3 * i + 0] & 0xFF) >> 0) | (ushort)((a[off + 3 * i + 1] & 0xFF) << 8)) & 0xFFF);
+                Coeffs[2 * i + 1] = (short) ((((a[off + 3 * i + 1] & 0xFF) >> 4) | (ushort)((a[off + 3 * i + 2] & 0xFF) << 4)) & 0xFFF);
+            }
+        }
+
+        internal void ToMsg(byte[] msg)
+        {
+            CondSubQ();
+
+            for (int i = 0; i < KyberEngine.N / 8; i++)
+            {
+                msg[i] = 0;
+                for (int j = 0; j < 8; j++)
+                {
+                    short t = (short)(((((short)(Coeffs[8 * i + j] << 1) + KyberEngine.Q / 2) / KyberEngine.Q) & 1));
+                    msg[i] |= (byte)(t << j);
+                }
+            }
+        }
+
+        internal void FromMsg(byte[] m)
+        {
+            if (m.Length != KyberEngine.N / 8)
+            {
+                throw new ArgumentException("KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!");
+            }
+            for (int i = 0; i < KyberEngine.N / 8; i++)
+            {
+                for (int j = 0; j < 8; j++)
+                {
+                    short mask = (short)((-1) * (short)(((m[i] & 0xFF) >> j) & 1));
+                    Coeffs[8 * i + j] = (short)(mask & ((KyberEngine.Q + 1) / 2));
+                }
+            }
+        }
+
+        internal void CondSubQ()
+        {
+            for (int i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = Reduce.CondSubQ(Coeffs[i]);
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs b/crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs
new file mode 100644
index 000000000..325b35e95
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs
@@ -0,0 +1,228 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal class PolyVec
+    {
+        private KyberEngine m_engine;
+        internal Poly[] m_vec;
+
+        internal PolyVec(KyberEngine engine)
+        {
+            m_engine = engine;
+            m_vec = new Poly[engine.K];
+            for (int i = 0; i < engine.K; i++)
+            {
+                m_vec[i] = new Poly(engine);
+            }
+        }
+
+        internal void Ntt()
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].PolyNtt();
+            }
+        }
+
+        internal void InverseNttToMont()
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].PolyInverseNttToMont();
+            }
+        }
+
+        internal static void PointwiseAccountMontgomery(Poly r, PolyVec a, PolyVec b, KyberEngine engine)
+        {
+            Poly t = new Poly(engine);
+            Poly.BaseMultMontgomery(r, a.m_vec[0], b.m_vec[0]);
+            for (int i = 1; i < engine.K; i++)
+            {
+                Poly.BaseMultMontgomery(t, a.m_vec[i], b.m_vec[i]);
+                r.Add(t);
+            }
+            r.PolyReduce();
+        }
+
+        internal void Add(PolyVec a)
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].Add(a.m_vec[i]);
+            }
+        }
+
+        internal void Reduce()
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].PolyReduce();
+            }
+        }
+
+        internal void CompressPolyVec(byte[] r)
+        {
+            ConditionalSubQ();
+            int count = 0;
+            if (m_engine.PolyVecCompressedBytes == m_engine.K * 320)
+            {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<short> t = stackalloc short[4];
+#else
+                short[] t = new short[4];
+#endif
+
+                for (int i = 0; i < m_engine.K; i++)
+                {
+                    for (int j = 0; j < KyberEngine.N / 4; j++)
+                    {
+                        for (int k = 0; k < 4; k++)
+                        {
+                            t[k] = (short)
+                                (
+                                    (
+                                        (((uint) m_vec[i].m_coeffs[4 * j + k] << 10)
+                                            + (KyberEngine.Q / 2))
+                                            / KyberEngine.Q)
+                                        & 0x3ff);
+                        }
+                        r[count + 0] = (byte)(t[0] >> 0);
+                        r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 2));
+                        r[count + 2] = (byte)((t[1] >> 6) | (t[2] << 4));
+                        r[count + 3] = (byte)((t[2] >> 4) | (t[3] << 6));
+                        r[count + 4] = (byte)((t[3] >> 2));
+                        count += 5;
+                    }
+                }
+            }
+            else if (m_engine.PolyVecCompressedBytes == m_engine.K * 352)
+            {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<short> t = stackalloc short[8];
+#else
+                short[] t = new short[8];
+#endif
+
+                for (int i = 0; i < m_engine.K; i++)
+                {
+                    for (int j = 0; j < KyberEngine.N / 8; j++)
+                    {
+                        for (int k = 0; k < 8; k++)
+                        {
+                            t[k] = (short)
+                                (
+                                    (
+                                        (((uint) m_vec[i].m_coeffs[8 * j + k] << 11)
+                                            + (KyberEngine.Q / 2))
+                                            / KyberEngine.Q)
+                                        & 0x7ff);
+                        }
+                        r[count + 0] = (byte)((t[0] >> 0));
+                        r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 3));
+                        r[count + 2] = (byte)((t[1] >> 5) | (t[2] << 6));
+                        r[count + 3] = (byte)((t[2] >> 2));
+                        r[count + 4] = (byte)((t[2] >> 10) | (t[3] << 1));
+                        r[count + 5] = (byte)((t[3] >> 7) | (t[4] << 4));
+                        r[count + 6] = (byte)((t[4] >> 4) | (t[5] << 7));
+                        r[count + 7] = (byte)((t[5] >> 1));
+                        r[count + 8] = (byte)((t[5] >> 9) | (t[6] << 2));
+                        r[count + 9] = (byte)((t[6] >> 6) | (t[7] << 5));
+                        r[count + 10] = (byte)((t[7] >> 3));
+                        count += 11;
+                    }
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!");
+            }
+        }
+
+        internal void DecompressPolyVec(byte[] compressedCipherText)
+        {
+            int count = 0;
+
+            if (m_engine.PolyVecCompressedBytes == (m_engine.K * 320))
+            {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<short> t = stackalloc short[4];
+#else
+                short[] t = new short[4];
+#endif
+
+                for (int i = 0; i < m_engine.K; i++)
+                {
+                    for (int j = 0; j < KyberEngine.N / 4; j++)
+                    {
+                        t[0] = (short)(((compressedCipherText[count] & 0xFF) >> 0) | ((ushort)(compressedCipherText[count + 1] & 0xFF) << 8));
+                        t[1] = (short)(((compressedCipherText[count + 1] & 0xFF) >> 2) | ((ushort)(compressedCipherText[count + 2] & 0xFF) << 6));
+                        t[2] = (short)(((compressedCipherText[count + 2] & 0xFF) >> 4) | ((ushort)(compressedCipherText[count + 3] & 0xFF) << 4));
+                        t[3] = (short)(((compressedCipherText[count + 3] & 0xFF) >> 6) | ((ushort)(compressedCipherText[count + 4] & 0xFF) << 2));
+                        count += 5;
+                        for (int k = 0; k < 4; k++)
+                        {
+                            m_vec[i].m_coeffs[4 * j + k] = (short)(((t[k] & 0x3FF) * KyberEngine.Q + 512) >> 10);
+                        }
+                    }
+                }
+            }
+            else if (m_engine.PolyVecCompressedBytes == (m_engine.K * 352))
+            {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<short> t = stackalloc short[8];
+#else
+                short[] t = new short[8];
+#endif
+
+                for (int i = 0; i < m_engine.K; i++)
+                {
+                    for (int j = 0; j < KyberEngine.N / 8; j++)
+                    {
+                        t[0] = (short)(((compressedCipherText[count] & 0xFF) >> 0) | ((ushort)(compressedCipherText[count + 1] & 0xFF) << 8));
+                        t[1] = (short)(((compressedCipherText[count + 1] & 0xFF) >> 3) | ((ushort)(compressedCipherText[count + 2] & 0xFF) << 5));
+                        t[2] = (short)(((compressedCipherText[count + 2] & 0xFF) >> 6) | ((ushort)(compressedCipherText[count + 3] & 0xFF) << 2) | ((ushort)((compressedCipherText[count + 4] & 0xFF) << 10)));
+                        t[3] = (short)(((compressedCipherText[count + 4] & 0xFF) >> 1) | ((ushort)(compressedCipherText[count + 5] & 0xFF) << 7));
+                        t[4] = (short)(((compressedCipherText[count + 5] & 0xFF) >> 4) | ((ushort)(compressedCipherText[count + 6] & 0xFF) << 4));
+                        t[5] = (short)(((compressedCipherText[count + 6] & 0xFF) >> 7) | ((ushort)(compressedCipherText[count + 7] & 0xFF) << 1) | ((ushort)((compressedCipherText[count + 8] & 0xFF) << 9)));
+                        t[6] = (short)(((compressedCipherText[count + 8] & 0xFF) >> 2) | ((ushort)(compressedCipherText[count + 9] & 0xFF) << 6));
+                        t[7] = (short)(((compressedCipherText[count + 9] & 0xFF) >> 5) | ((ushort)(compressedCipherText[count + 10] & 0xFF) << 3));
+                        count += 11;
+                        for (int k = 0; k < 8; k++)
+                        {
+                            m_vec[i].m_coeffs[8 * j + k] = (short)(((t[k] & 0x7FF) * KyberEngine.Q + 1024) >> 11);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!");
+            }
+        }
+
+        internal void ToBytes(byte[] r)
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].ToBytes(r, i * KyberEngine.PolyBytes);
+            }
+        }
+
+        internal void FromBytes(byte[] pk)
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].FromBytes(pk, i * KyberEngine.PolyBytes);
+            }
+        }
+
+        private void ConditionalSubQ()
+        {
+            for (int i = 0; i < m_engine.K; i++)
+            {
+                m_vec[i].CondSubQ();
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Reduce.cs b/crypto/src/pqc/crypto/crystals/kyber/Reduce.cs
new file mode 100644
index 000000000..db80d6967
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Reduce.cs
@@ -0,0 +1,29 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class Reduce
+    {
+        internal static short MontgomeryReduce(int a)
+        {
+            short u = (short)(a * KyberEngine.QInv);
+            int t = u * KyberEngine.Q;
+            t = a - t;
+            t >>= 16;
+            return (short)t;
+        }
+
+        internal static short BarrettReduce(short a)
+        {
+            short v = (short)(((1U << 26) + (KyberEngine.Q / 2)) / KyberEngine.Q);
+            short t = (short)((v * a) >> 26);
+            t = (short)(t * KyberEngine.Q);
+            return (short)(a - t);
+        }
+
+        internal static short CondSubQ(short a)
+        {
+            a -= KyberEngine.Q;
+            a += (short)((a >> 15) & KyberEngine.Q);
+            return a;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs b/crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs
new file mode 100644
index 000000000..9aad815da
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs
@@ -0,0 +1,165 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public abstract class Symmetric
+    {
+        internal readonly int XofBlockBytes;
+        
+        internal abstract void Hash_h(byte[] output, byte[] input, int outOffset);
+
+        internal abstract void Hash_g(byte[] output, byte[] input);
+
+        internal abstract void XofAbsorb(byte[] seed, byte x, byte y);
+
+        internal abstract void XofSqueezeBlocks(byte[] output, int outOffset, int outLen);
+
+        internal abstract void Prf(byte[] output, byte[] key, byte nonce);
+
+        internal abstract void Kdf(byte[] output, byte[] input);
+
+        Symmetric(int xofBlockBytes)
+        {
+            this.XofBlockBytes = xofBlockBytes;
+        }
+
+        internal class ShakeSymmetric
+            : Symmetric
+        {
+            private ShakeDigest xof;
+            private Sha3Digest sha3Digest512;
+            private Sha3Digest sha3Digest256;
+            private ShakeDigest shakeDigest;
+
+            
+            internal ShakeSymmetric()
+                : base(164)
+            {
+                xof = new ShakeDigest(128);
+                shakeDigest = new ShakeDigest(256);
+                sha3Digest256 = new Sha3Digest(256);
+                sha3Digest512 = new Sha3Digest(512);
+            }
+
+            internal override void Hash_h(byte[] output, byte[] input, int outOffset)
+            {
+                sha3Digest256.BlockUpdate(input, 0, input.Length);
+                sha3Digest256.DoFinal(output, outOffset);
+            }
+
+            internal override void Hash_g(byte[] output, byte[] input)
+            {
+                sha3Digest512.BlockUpdate(input, 0, input.Length);
+                sha3Digest512.DoFinal(output, 0);
+            }
+
+            internal override void XofAbsorb(byte[] seed, byte x, byte y)
+            {
+                xof.Reset();
+                byte[] buf = new byte[seed.Length + 2];
+                Array.Copy(seed, 0, buf, 0, seed.Length);
+                buf[seed.Length] = x;
+                buf[seed.Length + 1] = y;
+                xof.BlockUpdate(buf, 0, seed.Length + 2);
+            }
+
+            internal override void XofSqueezeBlocks(byte[] output, int outOffset, int outLen)
+            {
+                xof.Output(output, outOffset, outLen);
+            }
+
+            internal override void Prf(byte[] output, byte[] seed, byte nonce)
+            {
+                byte[] extSeed = new byte[seed.Length + 1];
+                Array.Copy(seed, 0, extSeed, 0, seed.Length);
+                extSeed[seed.Length] = nonce;
+                shakeDigest.BlockUpdate(extSeed, 0, extSeed.Length);
+                shakeDigest.OutputFinal(output, 0, output.Length);
+            }
+
+            internal override void Kdf(byte[] output, byte[] input)
+            {
+                shakeDigest.BlockUpdate(input, 0, input.Length);
+                shakeDigest.OutputFinal(output, 0, output.Length);
+            }
+        }
+
+        internal class AesSymmetric
+            : Symmetric
+        {
+            private Sha256Digest sha256Digest;
+            private Sha512Digest sha512Digest;
+            private SicBlockCipher cipher;
+
+            internal AesSymmetric()
+                : base(64)
+            {
+                this.sha256Digest = new Sha256Digest();
+                this.sha512Digest = new Sha512Digest();
+                this.cipher = new SicBlockCipher(AesUtilities.CreateEngine());
+            }
+            private void DoDigest(IDigest digest, byte[] output, byte[] input, int outOffset)
+            {
+                digest.BlockUpdate(input, 0, input.Length);
+                digest.DoFinal(output, outOffset);
+            }
+            
+            private void Aes128(byte[] output, int offset, int size)
+            {
+                byte[] buf = new byte[size + offset];   // TODO: there might be a more efficient way of doing this...
+                for (int i = 0; i < size; i += 16)
+                {
+                    cipher.ProcessBlock(buf, i + offset, output, i + offset);
+                }
+            }
+            
+            internal override void Hash_h(byte[] output, byte[] input, int outOffset)
+            {
+                DoDigest(sha256Digest, output, input, outOffset);
+            }
+
+            internal override void Hash_g(byte[] output, byte[] input)
+            {
+                DoDigest(sha512Digest, output, input, 0);
+            }
+
+            internal override void XofAbsorb(byte[] key, byte x, byte y)
+            {
+                byte[] expnonce = new byte[12];
+                expnonce[0] = x;
+                expnonce[1] = y;
+
+                ParametersWithIV kp = new ParametersWithIV(new KeyParameter(key, 0, 32), expnonce);
+                cipher.Init(true, kp);
+            }
+
+            internal override void XofSqueezeBlocks(byte[] output, int outOffset, int outLen)
+            {
+                Aes128(output, outOffset, outLen);
+            }
+
+            internal override void Prf(byte[] output, byte[] key, byte nonce)
+            {
+                byte[] expnonce = new byte[12];
+                expnonce[0] = nonce;
+
+                ParametersWithIV kp = new ParametersWithIV(new KeyParameter(key, 0, 32), expnonce);
+                cipher.Init(true, kp);
+                Aes128(output, 0, output.Length);
+            }
+
+            internal override void Kdf(byte[] output, byte[] input)
+            {
+                byte[] buf = new byte[32];
+                DoDigest(sha256Digest, buf, input, 0);
+                Array.Copy(buf, 0, output, 0, output.Length);            
+            }
+        }
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FPREngine.cs b/crypto/src/pqc/crypto/falcon/FPREngine.cs
new file mode 100644
index 000000000..d92c23235
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FPREngine.cs
@@ -0,0 +1,1311 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FPREngine
+    {
+        internal FalconFPR FPR(double v)
+        {
+            return new FalconFPR(v);
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+
+        internal FalconFPR fpr_of(long i)
+        {
+            return FPR((double)i);
+        }
+
+
+
+        internal long fpr_rint(FalconFPR x)
+        {
+            /*
+            * We do not want to use llrint() since it might be not
+            * constant-time.
+            *
+            * Suppose that x >= 0. If x >= 2^52, then it is already an
+            * integer. Otherwise, if x < 2^52, then computing x+2^52 will
+            * yield a value that will be rounded to the nearest integer
+            * with exactly the right rules (round-to-nearest-even).
+            *
+            * In order to have constant-time processing, we must do the
+            * computation for both x >= 0 and x < 0 cases, and use a
+            * cast to an integer to access the sign and select the proper
+            * value. Such casts also allow us to find out if |x| < 2^52.
+            */
+            long sx, tx, rp, rn, m;
+            uint ub;
+
+            sx = (long)(x.v - 1.0);
+            tx = (long)x.v;
+            rp = (long)(x.v + 4503599627370496.0) - 4503599627370496;
+            rn = (long)(x.v - 4503599627370496.0) + 4503599627370496;
+
+            /*
+            * If tx >= 2^52 or tx < -2^52, then result is tx.
+            * Otherwise, if sx >= 0, then result is rp.
+            * Otherwise, result is rn. We use the fact that when x is
+            * close to 0 (|x| <= 0.25) then both rp and rn are correct;
+            * and if x is not close to 0, then trunc(x-1.0) yields the
+            * appropriate sign.
+            */
+
+            /*
+            * Clamp rp to zero if tx < 0.
+            * Clamp rn to zero if tx >= 0.
+            */
+            m = sx >> 63;
+            rn &= m;
+            rp &= ~m;
+
+            /*
+            * Get the 12 upper bits of tx; if they are not all zeros or
+            * all ones, then tx >= 2^52 or tx < -2^52, and we clamp both
+            * rp and rn to zero. Otherwise, we clamp tx to zero.
+            */
+            ub = (uint)((ulong)tx >> 52);
+            m = -(long)((((ub + 1) & 0xFFF) - 2) >> 31);
+            rp &= m;
+            rn &= m;
+            tx &= ~m;
+
+            /*
+            * Only one of tx, rn or rp (at most) can be non-zero at this
+            * point.
+            */
+            return tx | rn | rp;
+        }
+
+        internal long fpr_floor(FalconFPR x)
+        {
+            long r;
+
+            /*
+            * The cast performs a trunc() (rounding toward 0) and thus is
+            * wrong by 1 for most negative values. The correction below is
+            * constant-time as long as the compiler turns the
+            * floating-point conversion result into a 0/1 integer without a
+            * conditional branch or another non-constant-time construction.
+            * This should hold on all modern architectures with an FPU (and
+            * if it is false on a given arch, then chances are that the FPU
+            * itself is not constant-time, making the point moot).
+            */
+            r = (long)x.v;
+            return r - ((x.v < (double)r) ? 1 : 0);
+        }
+
+        internal long fpr_trunc(FalconFPR x)
+        {
+            return (long)x.v;
+        }
+
+        internal FalconFPR fpr_add(FalconFPR x, FalconFPR y)
+        {
+            return FPR(x.v + y.v);
+        }
+
+        internal FalconFPR fpr_sub(FalconFPR x, FalconFPR y)
+        {
+            return FPR(x.v - y.v);
+        }
+
+        internal FalconFPR fpr_neg(FalconFPR x)
+        {
+            return FPR(-x.v);
+        }
+
+        internal FalconFPR fpr_half(FalconFPR x)
+        {
+            return FPR(x.v * 0.5);
+        }
+
+        internal FalconFPR fpr_double(FalconFPR x)
+        {
+            return FPR(x.v + x.v);
+        }
+
+        internal FalconFPR fpr_mul(FalconFPR x, FalconFPR y)
+        {
+            return FPR(x.v * y.v);
+        }
+
+        internal FalconFPR fpr_sqr(FalconFPR x)
+        {
+            return FPR(x.v * x.v);
+        }
+
+        internal FalconFPR fpr_inv(FalconFPR x)
+        {
+            return FPR(1.0 / x.v);
+        }
+
+        internal FalconFPR fpr_div(FalconFPR x, FalconFPR y)
+        {
+            return FPR(x.v / y.v);
+        }
+
+
+        internal FalconFPR fpr_sqrt(FalconFPR x)
+        {
+            return FPR(System.Math.Sqrt(x.v));
+        }
+
+        internal bool fpr_lt(FalconFPR x, FalconFPR y)
+        {
+            return x.v < y.v;
+        }
+
+        internal ulong fpr_expm_p63(FalconFPR x, FalconFPR ccs)
+        {
+            /*
+            * Polynomial approximation of exp(-x) is taken from FACCT:
+            *   https://eprint.iacr.org/2018/1234
+            * Specifically, values are extracted from the implementation
+            * referenced from the FACCT article, and available at:
+            *   https://github.com/raykzhao/gaussian
+            * Tests over more than 24 billions of random inputs in the
+            * 0..log(2) range have never shown a deviation larger than
+            * 2^(-50) from the true mathematical value.
+            */
+
+
+            /*
+            * Normal implementation uses Horner's method, which minimizes
+            * the number of operations.
+            */
+
+            double d, y;
+
+            d = x.v;
+            y = 0.000000002073772366009083061987;
+            y = 0.000000025299506379442070029551 - y * d;
+            y = 0.000000275607356160477811864927 - y * d;
+            y = 0.000002755586350219122514855659 - y * d;
+            y = 0.000024801566833585381209939524 - y * d;
+            y = 0.000198412739277311890541063977 - y * d;
+            y = 0.001388888894063186997887560103 - y * d;
+            y = 0.008333333327800835146903501993 - y * d;
+            y = 0.041666666666110491190622155955 - y * d;
+            y = 0.166666666666984014666397229121 - y * d;
+            y = 0.500000000000019206858326015208 - y * d;
+            y = 0.999999999999994892974086724280 - y * d;
+            y = 1.000000000000000000000000000000 - y * d;
+            y *= ccs.v;
+            return (ulong)(y * fpr_ptwo63.v);
+
+        }
+
+        internal FalconFPR[] fpr_gm_tab = {
+                new FalconFPR(0), new FalconFPR(0), /* unused */
+                new FalconFPR(-0.000000000000000000000000000), new FalconFPR( 1.000000000000000000000000000),
+                new FalconFPR( 0.707106781186547524400844362), new FalconFPR( 0.707106781186547524400844362),
+                new FalconFPR(-0.707106781186547524400844362), new FalconFPR( 0.707106781186547524400844362),
+                new FalconFPR( 0.923879532511286756128183189), new FalconFPR( 0.382683432365089771728459984),
+                new FalconFPR(-0.382683432365089771728459984), new FalconFPR( 0.923879532511286756128183189),
+                new FalconFPR( 0.382683432365089771728459984), new FalconFPR( 0.923879532511286756128183189),
+                new FalconFPR(-0.923879532511286756128183189), new FalconFPR( 0.382683432365089771728459984),
+                new FalconFPR( 0.980785280403230449126182236), new FalconFPR( 0.195090322016128267848284868),
+                new FalconFPR(-0.195090322016128267848284868), new FalconFPR( 0.980785280403230449126182236),
+                new FalconFPR( 0.555570233019602224742830814), new FalconFPR( 0.831469612302545237078788378),
+                new FalconFPR(-0.831469612302545237078788378), new FalconFPR( 0.555570233019602224742830814),
+                new FalconFPR( 0.831469612302545237078788378), new FalconFPR( 0.555570233019602224742830814),
+                new FalconFPR(-0.555570233019602224742830814), new FalconFPR( 0.831469612302545237078788378),
+                new FalconFPR( 0.195090322016128267848284868), new FalconFPR( 0.980785280403230449126182236),
+                new FalconFPR(-0.980785280403230449126182236), new FalconFPR( 0.195090322016128267848284868),
+                new FalconFPR( 0.995184726672196886244836953), new FalconFPR( 0.098017140329560601994195564),
+                new FalconFPR(-0.098017140329560601994195564), new FalconFPR( 0.995184726672196886244836953),
+                new FalconFPR( 0.634393284163645498215171613), new FalconFPR( 0.773010453362736960810906610),
+                new FalconFPR(-0.773010453362736960810906610), new FalconFPR( 0.634393284163645498215171613),
+                new FalconFPR( 0.881921264348355029712756864), new FalconFPR( 0.471396736825997648556387626),
+                new FalconFPR(-0.471396736825997648556387626), new FalconFPR( 0.881921264348355029712756864),
+                new FalconFPR( 0.290284677254462367636192376), new FalconFPR( 0.956940335732208864935797887),
+                new FalconFPR(-0.956940335732208864935797887), new FalconFPR( 0.290284677254462367636192376),
+                new FalconFPR( 0.956940335732208864935797887), new FalconFPR( 0.290284677254462367636192376),
+                new FalconFPR(-0.290284677254462367636192376), new FalconFPR( 0.956940335732208864935797887),
+                new FalconFPR( 0.471396736825997648556387626), new FalconFPR( 0.881921264348355029712756864),
+                new FalconFPR(-0.881921264348355029712756864), new FalconFPR( 0.471396736825997648556387626),
+                new FalconFPR( 0.773010453362736960810906610), new FalconFPR( 0.634393284163645498215171613),
+                new FalconFPR(-0.634393284163645498215171613), new FalconFPR( 0.773010453362736960810906610),
+                new FalconFPR( 0.098017140329560601994195564), new FalconFPR( 0.995184726672196886244836953),
+                new FalconFPR(-0.995184726672196886244836953), new FalconFPR( 0.098017140329560601994195564),
+                new FalconFPR( 0.998795456205172392714771605), new FalconFPR( 0.049067674327418014254954977),
+                new FalconFPR(-0.049067674327418014254954977), new FalconFPR( 0.998795456205172392714771605),
+                new FalconFPR( 0.671558954847018400625376850), new FalconFPR( 0.740951125354959091175616897),
+                new FalconFPR(-0.740951125354959091175616897), new FalconFPR( 0.671558954847018400625376850),
+                new FalconFPR( 0.903989293123443331586200297), new FalconFPR( 0.427555093430282094320966857),
+                new FalconFPR(-0.427555093430282094320966857), new FalconFPR( 0.903989293123443331586200297),
+                new FalconFPR( 0.336889853392220050689253213), new FalconFPR( 0.941544065183020778412509403),
+                new FalconFPR(-0.941544065183020778412509403), new FalconFPR( 0.336889853392220050689253213),
+                new FalconFPR( 0.970031253194543992603984207), new FalconFPR( 0.242980179903263889948274162),
+                new FalconFPR(-0.242980179903263889948274162), new FalconFPR( 0.970031253194543992603984207),
+                new FalconFPR( 0.514102744193221726593693839), new FalconFPR( 0.857728610000272069902269984),
+                new FalconFPR(-0.857728610000272069902269984), new FalconFPR( 0.514102744193221726593693839),
+                new FalconFPR( 0.803207531480644909806676513), new FalconFPR( 0.595699304492433343467036529),
+                new FalconFPR(-0.595699304492433343467036529), new FalconFPR( 0.803207531480644909806676513),
+                new FalconFPR( 0.146730474455361751658850130), new FalconFPR( 0.989176509964780973451673738),
+                new FalconFPR(-0.989176509964780973451673738), new FalconFPR( 0.146730474455361751658850130),
+                new FalconFPR( 0.989176509964780973451673738), new FalconFPR( 0.146730474455361751658850130),
+                new FalconFPR(-0.146730474455361751658850130), new FalconFPR( 0.989176509964780973451673738),
+                new FalconFPR( 0.595699304492433343467036529), new FalconFPR( 0.803207531480644909806676513),
+                new FalconFPR(-0.803207531480644909806676513), new FalconFPR( 0.595699304492433343467036529),
+                new FalconFPR( 0.857728610000272069902269984), new FalconFPR( 0.514102744193221726593693839),
+                new FalconFPR(-0.514102744193221726593693839), new FalconFPR( 0.857728610000272069902269984),
+                new FalconFPR( 0.242980179903263889948274162), new FalconFPR( 0.970031253194543992603984207),
+                new FalconFPR(-0.970031253194543992603984207), new FalconFPR( 0.242980179903263889948274162),
+                new FalconFPR( 0.941544065183020778412509403), new FalconFPR( 0.336889853392220050689253213),
+                new FalconFPR(-0.336889853392220050689253213), new FalconFPR( 0.941544065183020778412509403),
+                new FalconFPR( 0.427555093430282094320966857), new FalconFPR( 0.903989293123443331586200297),
+                new FalconFPR(-0.903989293123443331586200297), new FalconFPR( 0.427555093430282094320966857),
+                new FalconFPR( 0.740951125354959091175616897), new FalconFPR( 0.671558954847018400625376850),
+                new FalconFPR(-0.671558954847018400625376850), new FalconFPR( 0.740951125354959091175616897),
+                new FalconFPR( 0.049067674327418014254954977), new FalconFPR( 0.998795456205172392714771605),
+                new FalconFPR(-0.998795456205172392714771605), new FalconFPR( 0.049067674327418014254954977),
+                new FalconFPR( 0.999698818696204220115765650), new FalconFPR( 0.024541228522912288031734529),
+                new FalconFPR(-0.024541228522912288031734529), new FalconFPR( 0.999698818696204220115765650),
+                new FalconFPR( 0.689540544737066924616730630), new FalconFPR( 0.724247082951466920941069243),
+                new FalconFPR(-0.724247082951466920941069243), new FalconFPR( 0.689540544737066924616730630),
+                new FalconFPR( 0.914209755703530654635014829), new FalconFPR( 0.405241314004989870908481306),
+                new FalconFPR(-0.405241314004989870908481306), new FalconFPR( 0.914209755703530654635014829),
+                new FalconFPR( 0.359895036534988148775104572), new FalconFPR( 0.932992798834738887711660256),
+                new FalconFPR(-0.932992798834738887711660256), new FalconFPR( 0.359895036534988148775104572),
+                new FalconFPR( 0.975702130038528544460395766), new FalconFPR( 0.219101240156869797227737547),
+                new FalconFPR(-0.219101240156869797227737547), new FalconFPR( 0.975702130038528544460395766),
+                new FalconFPR( 0.534997619887097210663076905), new FalconFPR( 0.844853565249707073259571205),
+                new FalconFPR(-0.844853565249707073259571205), new FalconFPR( 0.534997619887097210663076905),
+                new FalconFPR( 0.817584813151583696504920884), new FalconFPR( 0.575808191417845300745972454),
+                new FalconFPR(-0.575808191417845300745972454), new FalconFPR( 0.817584813151583696504920884),
+                new FalconFPR( 0.170961888760301226363642357), new FalconFPR( 0.985277642388941244774018433),
+                new FalconFPR(-0.985277642388941244774018433), new FalconFPR( 0.170961888760301226363642357),
+                new FalconFPR( 0.992479534598709998156767252), new FalconFPR( 0.122410675199216198498704474),
+                new FalconFPR(-0.122410675199216198498704474), new FalconFPR( 0.992479534598709998156767252),
+                new FalconFPR( 0.615231590580626845484913563), new FalconFPR( 0.788346427626606262009164705),
+                new FalconFPR(-0.788346427626606262009164705), new FalconFPR( 0.615231590580626845484913563),
+                new FalconFPR( 0.870086991108711418652292404), new FalconFPR( 0.492898192229784036873026689),
+                new FalconFPR(-0.492898192229784036873026689), new FalconFPR( 0.870086991108711418652292404),
+                new FalconFPR( 0.266712757474898386325286515), new FalconFPR( 0.963776065795439866686464356),
+                new FalconFPR(-0.963776065795439866686464356), new FalconFPR( 0.266712757474898386325286515),
+                new FalconFPR( 0.949528180593036667195936074), new FalconFPR( 0.313681740398891476656478846),
+                new FalconFPR(-0.313681740398891476656478846), new FalconFPR( 0.949528180593036667195936074),
+                new FalconFPR( 0.449611329654606600046294579), new FalconFPR( 0.893224301195515320342416447),
+                new FalconFPR(-0.893224301195515320342416447), new FalconFPR( 0.449611329654606600046294579),
+                new FalconFPR( 0.757208846506484547575464054), new FalconFPR( 0.653172842953776764084203014),
+                new FalconFPR(-0.653172842953776764084203014), new FalconFPR( 0.757208846506484547575464054),
+                new FalconFPR( 0.073564563599667423529465622), new FalconFPR( 0.997290456678690216135597140),
+                new FalconFPR(-0.997290456678690216135597140), new FalconFPR( 0.073564563599667423529465622),
+                new FalconFPR( 0.997290456678690216135597140), new FalconFPR( 0.073564563599667423529465622),
+                new FalconFPR(-0.073564563599667423529465622), new FalconFPR( 0.997290456678690216135597140),
+                new FalconFPR( 0.653172842953776764084203014), new FalconFPR( 0.757208846506484547575464054),
+                new FalconFPR(-0.757208846506484547575464054), new FalconFPR( 0.653172842953776764084203014),
+                new FalconFPR( 0.893224301195515320342416447), new FalconFPR( 0.449611329654606600046294579),
+                new FalconFPR(-0.449611329654606600046294579), new FalconFPR( 0.893224301195515320342416447),
+                new FalconFPR( 0.313681740398891476656478846), new FalconFPR( 0.949528180593036667195936074),
+                new FalconFPR(-0.949528180593036667195936074), new FalconFPR( 0.313681740398891476656478846),
+                new FalconFPR( 0.963776065795439866686464356), new FalconFPR( 0.266712757474898386325286515),
+                new FalconFPR(-0.266712757474898386325286515), new FalconFPR( 0.963776065795439866686464356),
+                new FalconFPR( 0.492898192229784036873026689), new FalconFPR( 0.870086991108711418652292404),
+                new FalconFPR(-0.870086991108711418652292404), new FalconFPR( 0.492898192229784036873026689),
+                new FalconFPR( 0.788346427626606262009164705), new FalconFPR( 0.615231590580626845484913563),
+                new FalconFPR(-0.615231590580626845484913563), new FalconFPR( 0.788346427626606262009164705),
+                new FalconFPR( 0.122410675199216198498704474), new FalconFPR( 0.992479534598709998156767252),
+                new FalconFPR(-0.992479534598709998156767252), new FalconFPR( 0.122410675199216198498704474),
+                new FalconFPR( 0.985277642388941244774018433), new FalconFPR( 0.170961888760301226363642357),
+                new FalconFPR(-0.170961888760301226363642357), new FalconFPR( 0.985277642388941244774018433),
+                new FalconFPR( 0.575808191417845300745972454), new FalconFPR( 0.817584813151583696504920884),
+                new FalconFPR(-0.817584813151583696504920884), new FalconFPR( 0.575808191417845300745972454),
+                new FalconFPR( 0.844853565249707073259571205), new FalconFPR( 0.534997619887097210663076905),
+                new FalconFPR(-0.534997619887097210663076905), new FalconFPR( 0.844853565249707073259571205),
+                new FalconFPR( 0.219101240156869797227737547), new FalconFPR( 0.975702130038528544460395766),
+                new FalconFPR(-0.975702130038528544460395766), new FalconFPR( 0.219101240156869797227737547),
+                new FalconFPR( 0.932992798834738887711660256), new FalconFPR( 0.359895036534988148775104572),
+                new FalconFPR(-0.359895036534988148775104572), new FalconFPR( 0.932992798834738887711660256),
+                new FalconFPR( 0.405241314004989870908481306), new FalconFPR( 0.914209755703530654635014829),
+                new FalconFPR(-0.914209755703530654635014829), new FalconFPR( 0.405241314004989870908481306),
+                new FalconFPR( 0.724247082951466920941069243), new FalconFPR( 0.689540544737066924616730630),
+                new FalconFPR(-0.689540544737066924616730630), new FalconFPR( 0.724247082951466920941069243),
+                new FalconFPR( 0.024541228522912288031734529), new FalconFPR( 0.999698818696204220115765650),
+                new FalconFPR(-0.999698818696204220115765650), new FalconFPR( 0.024541228522912288031734529),
+                new FalconFPR( 0.999924701839144540921646491), new FalconFPR( 0.012271538285719926079408262),
+                new FalconFPR(-0.012271538285719926079408262), new FalconFPR( 0.999924701839144540921646491),
+                new FalconFPR( 0.698376249408972853554813503), new FalconFPR( 0.715730825283818654125532623),
+                new FalconFPR(-0.715730825283818654125532623), new FalconFPR( 0.698376249408972853554813503),
+                new FalconFPR( 0.919113851690057743908477789), new FalconFPR( 0.393992040061048108596188661),
+                new FalconFPR(-0.393992040061048108596188661), new FalconFPR( 0.919113851690057743908477789),
+                new FalconFPR( 0.371317193951837543411934967), new FalconFPR( 0.928506080473215565937167396),
+                new FalconFPR(-0.928506080473215565937167396), new FalconFPR( 0.371317193951837543411934967),
+                new FalconFPR( 0.978317370719627633106240097), new FalconFPR( 0.207111376192218549708116020),
+                new FalconFPR(-0.207111376192218549708116020), new FalconFPR( 0.978317370719627633106240097),
+                new FalconFPR( 0.545324988422046422313987347), new FalconFPR( 0.838224705554838043186996856),
+                new FalconFPR(-0.838224705554838043186996856), new FalconFPR( 0.545324988422046422313987347),
+                new FalconFPR( 0.824589302785025264474803737), new FalconFPR( 0.565731810783613197389765011),
+                new FalconFPR(-0.565731810783613197389765011), new FalconFPR( 0.824589302785025264474803737),
+                new FalconFPR( 0.183039887955140958516532578), new FalconFPR( 0.983105487431216327180301155),
+                new FalconFPR(-0.983105487431216327180301155), new FalconFPR( 0.183039887955140958516532578),
+                new FalconFPR( 0.993906970002356041546922813), new FalconFPR( 0.110222207293883058807899140),
+                new FalconFPR(-0.110222207293883058807899140), new FalconFPR( 0.993906970002356041546922813),
+                new FalconFPR( 0.624859488142386377084072816), new FalconFPR( 0.780737228572094478301588484),
+                new FalconFPR(-0.780737228572094478301588484), new FalconFPR( 0.624859488142386377084072816),
+                new FalconFPR( 0.876070094195406607095844268), new FalconFPR( 0.482183772079122748517344481),
+                new FalconFPR(-0.482183772079122748517344481), new FalconFPR( 0.876070094195406607095844268),
+                new FalconFPR( 0.278519689385053105207848526), new FalconFPR( 0.960430519415565811199035138),
+                new FalconFPR(-0.960430519415565811199035138), new FalconFPR( 0.278519689385053105207848526),
+                new FalconFPR( 0.953306040354193836916740383), new FalconFPR( 0.302005949319228067003463232),
+                new FalconFPR(-0.302005949319228067003463232), new FalconFPR( 0.953306040354193836916740383),
+                new FalconFPR( 0.460538710958240023633181487), new FalconFPR( 0.887639620402853947760181617),
+                new FalconFPR(-0.887639620402853947760181617), new FalconFPR( 0.460538710958240023633181487),
+                new FalconFPR( 0.765167265622458925888815999), new FalconFPR( 0.643831542889791465068086063),
+                new FalconFPR(-0.643831542889791465068086063), new FalconFPR( 0.765167265622458925888815999),
+                new FalconFPR( 0.085797312344439890461556332), new FalconFPR( 0.996312612182778012627226190),
+                new FalconFPR(-0.996312612182778012627226190), new FalconFPR( 0.085797312344439890461556332),
+                new FalconFPR( 0.998118112900149207125155861), new FalconFPR( 0.061320736302208577782614593),
+                new FalconFPR(-0.061320736302208577782614593), new FalconFPR( 0.998118112900149207125155861),
+                new FalconFPR( 0.662415777590171761113069817), new FalconFPR( 0.749136394523459325469203257),
+                new FalconFPR(-0.749136394523459325469203257), new FalconFPR( 0.662415777590171761113069817),
+                new FalconFPR( 0.898674465693953843041976744), new FalconFPR( 0.438616238538527637647025738),
+                new FalconFPR(-0.438616238538527637647025738), new FalconFPR( 0.898674465693953843041976744),
+                new FalconFPR( 0.325310292162262934135954708), new FalconFPR( 0.945607325380521325730945387),
+                new FalconFPR(-0.945607325380521325730945387), new FalconFPR( 0.325310292162262934135954708),
+                new FalconFPR( 0.966976471044852109087220226), new FalconFPR( 0.254865659604514571553980779),
+                new FalconFPR(-0.254865659604514571553980779), new FalconFPR( 0.966976471044852109087220226),
+                new FalconFPR( 0.503538383725717558691867071), new FalconFPR( 0.863972856121586737918147054),
+                new FalconFPR(-0.863972856121586737918147054), new FalconFPR( 0.503538383725717558691867071),
+                new FalconFPR( 0.795836904608883536262791915), new FalconFPR( 0.605511041404325513920626941),
+                new FalconFPR(-0.605511041404325513920626941), new FalconFPR( 0.795836904608883536262791915),
+                new FalconFPR( 0.134580708507126186316358409), new FalconFPR( 0.990902635427780025108237011),
+                new FalconFPR(-0.990902635427780025108237011), new FalconFPR( 0.134580708507126186316358409),
+                new FalconFPR( 0.987301418157858382399815802), new FalconFPR( 0.158858143333861441684385360),
+                new FalconFPR(-0.158858143333861441684385360), new FalconFPR( 0.987301418157858382399815802),
+                new FalconFPR( 0.585797857456438860328080838), new FalconFPR( 0.810457198252594791726703434),
+                new FalconFPR(-0.810457198252594791726703434), new FalconFPR( 0.585797857456438860328080838),
+                new FalconFPR( 0.851355193105265142261290312), new FalconFPR( 0.524589682678468906215098464),
+                new FalconFPR(-0.524589682678468906215098464), new FalconFPR( 0.851355193105265142261290312),
+                new FalconFPR( 0.231058108280671119643236018), new FalconFPR( 0.972939952205560145467720114),
+                new FalconFPR(-0.972939952205560145467720114), new FalconFPR( 0.231058108280671119643236018),
+                new FalconFPR( 0.937339011912574923201899593), new FalconFPR( 0.348418680249434568419308588),
+                new FalconFPR(-0.348418680249434568419308588), new FalconFPR( 0.937339011912574923201899593),
+                new FalconFPR( 0.416429560097637182562598911), new FalconFPR( 0.909167983090522376563884788),
+                new FalconFPR(-0.909167983090522376563884788), new FalconFPR( 0.416429560097637182562598911),
+                new FalconFPR( 0.732654271672412834615546649), new FalconFPR( 0.680600997795453050594430464),
+                new FalconFPR(-0.680600997795453050594430464), new FalconFPR( 0.732654271672412834615546649),
+                new FalconFPR( 0.036807222941358832324332691), new FalconFPR( 0.999322384588349500896221011),
+                new FalconFPR(-0.999322384588349500896221011), new FalconFPR( 0.036807222941358832324332691),
+                new FalconFPR( 0.999322384588349500896221011), new FalconFPR( 0.036807222941358832324332691),
+                new FalconFPR(-0.036807222941358832324332691), new FalconFPR( 0.999322384588349500896221011),
+                new FalconFPR( 0.680600997795453050594430464), new FalconFPR( 0.732654271672412834615546649),
+                new FalconFPR(-0.732654271672412834615546649), new FalconFPR( 0.680600997795453050594430464),
+                new FalconFPR( 0.909167983090522376563884788), new FalconFPR( 0.416429560097637182562598911),
+                new FalconFPR(-0.416429560097637182562598911), new FalconFPR( 0.909167983090522376563884788),
+                new FalconFPR( 0.348418680249434568419308588), new FalconFPR( 0.937339011912574923201899593),
+                new FalconFPR(-0.937339011912574923201899593), new FalconFPR( 0.348418680249434568419308588),
+                new FalconFPR( 0.972939952205560145467720114), new FalconFPR( 0.231058108280671119643236018),
+                new FalconFPR(-0.231058108280671119643236018), new FalconFPR( 0.972939952205560145467720114),
+                new FalconFPR( 0.524589682678468906215098464), new FalconFPR( 0.851355193105265142261290312),
+                new FalconFPR(-0.851355193105265142261290312), new FalconFPR( 0.524589682678468906215098464),
+                new FalconFPR( 0.810457198252594791726703434), new FalconFPR( 0.585797857456438860328080838),
+                new FalconFPR(-0.585797857456438860328080838), new FalconFPR( 0.810457198252594791726703434),
+                new FalconFPR( 0.158858143333861441684385360), new FalconFPR( 0.987301418157858382399815802),
+                new FalconFPR(-0.987301418157858382399815802), new FalconFPR( 0.158858143333861441684385360),
+                new FalconFPR( 0.990902635427780025108237011), new FalconFPR( 0.134580708507126186316358409),
+                new FalconFPR(-0.134580708507126186316358409), new FalconFPR( 0.990902635427780025108237011),
+                new FalconFPR( 0.605511041404325513920626941), new FalconFPR( 0.795836904608883536262791915),
+                new FalconFPR(-0.795836904608883536262791915), new FalconFPR( 0.605511041404325513920626941),
+                new FalconFPR( 0.863972856121586737918147054), new FalconFPR( 0.503538383725717558691867071),
+                new FalconFPR(-0.503538383725717558691867071), new FalconFPR( 0.863972856121586737918147054),
+                new FalconFPR( 0.254865659604514571553980779), new FalconFPR( 0.966976471044852109087220226),
+                new FalconFPR(-0.966976471044852109087220226), new FalconFPR( 0.254865659604514571553980779),
+                new FalconFPR( 0.945607325380521325730945387), new FalconFPR( 0.325310292162262934135954708),
+                new FalconFPR(-0.325310292162262934135954708), new FalconFPR( 0.945607325380521325730945387),
+                new FalconFPR( 0.438616238538527637647025738), new FalconFPR( 0.898674465693953843041976744),
+                new FalconFPR(-0.898674465693953843041976744), new FalconFPR( 0.438616238538527637647025738),
+                new FalconFPR( 0.749136394523459325469203257), new FalconFPR( 0.662415777590171761113069817),
+                new FalconFPR(-0.662415777590171761113069817), new FalconFPR( 0.749136394523459325469203257),
+                new FalconFPR( 0.061320736302208577782614593), new FalconFPR( 0.998118112900149207125155861),
+                new FalconFPR(-0.998118112900149207125155861), new FalconFPR( 0.061320736302208577782614593),
+                new FalconFPR( 0.996312612182778012627226190), new FalconFPR( 0.085797312344439890461556332),
+                new FalconFPR(-0.085797312344439890461556332), new FalconFPR( 0.996312612182778012627226190),
+                new FalconFPR( 0.643831542889791465068086063), new FalconFPR( 0.765167265622458925888815999),
+                new FalconFPR(-0.765167265622458925888815999), new FalconFPR( 0.643831542889791465068086063),
+                new FalconFPR( 0.887639620402853947760181617), new FalconFPR( 0.460538710958240023633181487),
+                new FalconFPR(-0.460538710958240023633181487), new FalconFPR( 0.887639620402853947760181617),
+                new FalconFPR( 0.302005949319228067003463232), new FalconFPR( 0.953306040354193836916740383),
+                new FalconFPR(-0.953306040354193836916740383), new FalconFPR( 0.302005949319228067003463232),
+                new FalconFPR( 0.960430519415565811199035138), new FalconFPR( 0.278519689385053105207848526),
+                new FalconFPR(-0.278519689385053105207848526), new FalconFPR( 0.960430519415565811199035138),
+                new FalconFPR( 0.482183772079122748517344481), new FalconFPR( 0.876070094195406607095844268),
+                new FalconFPR(-0.876070094195406607095844268), new FalconFPR( 0.482183772079122748517344481),
+                new FalconFPR( 0.780737228572094478301588484), new FalconFPR( 0.624859488142386377084072816),
+                new FalconFPR(-0.624859488142386377084072816), new FalconFPR( 0.780737228572094478301588484),
+                new FalconFPR( 0.110222207293883058807899140), new FalconFPR( 0.993906970002356041546922813),
+                new FalconFPR(-0.993906970002356041546922813), new FalconFPR( 0.110222207293883058807899140),
+                new FalconFPR( 0.983105487431216327180301155), new FalconFPR( 0.183039887955140958516532578),
+                new FalconFPR(-0.183039887955140958516532578), new FalconFPR( 0.983105487431216327180301155),
+                new FalconFPR( 0.565731810783613197389765011), new FalconFPR( 0.824589302785025264474803737),
+                new FalconFPR(-0.824589302785025264474803737), new FalconFPR( 0.565731810783613197389765011),
+                new FalconFPR( 0.838224705554838043186996856), new FalconFPR( 0.545324988422046422313987347),
+                new FalconFPR(-0.545324988422046422313987347), new FalconFPR( 0.838224705554838043186996856),
+                new FalconFPR( 0.207111376192218549708116020), new FalconFPR( 0.978317370719627633106240097),
+                new FalconFPR(-0.978317370719627633106240097), new FalconFPR( 0.207111376192218549708116020),
+                new FalconFPR( 0.928506080473215565937167396), new FalconFPR( 0.371317193951837543411934967),
+                new FalconFPR(-0.371317193951837543411934967), new FalconFPR( 0.928506080473215565937167396),
+                new FalconFPR( 0.393992040061048108596188661), new FalconFPR( 0.919113851690057743908477789),
+                new FalconFPR(-0.919113851690057743908477789), new FalconFPR( 0.393992040061048108596188661),
+                new FalconFPR( 0.715730825283818654125532623), new FalconFPR( 0.698376249408972853554813503),
+                new FalconFPR(-0.698376249408972853554813503), new FalconFPR( 0.715730825283818654125532623),
+                new FalconFPR( 0.012271538285719926079408262), new FalconFPR( 0.999924701839144540921646491),
+                new FalconFPR(-0.999924701839144540921646491), new FalconFPR( 0.012271538285719926079408262),
+                new FalconFPR( 0.999981175282601142656990438), new FalconFPR( 0.006135884649154475359640235),
+                new FalconFPR(-0.006135884649154475359640235), new FalconFPR( 0.999981175282601142656990438),
+                new FalconFPR( 0.702754744457225302452914421), new FalconFPR( 0.711432195745216441522130290),
+                new FalconFPR(-0.711432195745216441522130290), new FalconFPR( 0.702754744457225302452914421),
+                new FalconFPR( 0.921514039342041943465396332), new FalconFPR( 0.388345046698826291624993541),
+                new FalconFPR(-0.388345046698826291624993541), new FalconFPR( 0.921514039342041943465396332),
+                new FalconFPR( 0.377007410216418256726567823), new FalconFPR( 0.926210242138311341974793388),
+                new FalconFPR(-0.926210242138311341974793388), new FalconFPR( 0.377007410216418256726567823),
+                new FalconFPR( 0.979569765685440534439326110), new FalconFPR( 0.201104634842091911558443546),
+                new FalconFPR(-0.201104634842091911558443546), new FalconFPR( 0.979569765685440534439326110),
+                new FalconFPR( 0.550457972936604802977289893), new FalconFPR( 0.834862874986380056304401383),
+                new FalconFPR(-0.834862874986380056304401383), new FalconFPR( 0.550457972936604802977289893),
+                new FalconFPR( 0.828045045257755752067527592), new FalconFPR( 0.560661576197336023839710223),
+                new FalconFPR(-0.560661576197336023839710223), new FalconFPR( 0.828045045257755752067527592),
+                new FalconFPR( 0.189068664149806212754997837), new FalconFPR( 0.981963869109555264072848154),
+                new FalconFPR(-0.981963869109555264072848154), new FalconFPR( 0.189068664149806212754997837),
+                new FalconFPR( 0.994564570734255452119106243), new FalconFPR( 0.104121633872054579120943880),
+                new FalconFPR(-0.104121633872054579120943880), new FalconFPR( 0.994564570734255452119106243),
+                new FalconFPR( 0.629638238914927025372981341), new FalconFPR( 0.776888465673232450040827983),
+                new FalconFPR(-0.776888465673232450040827983), new FalconFPR( 0.629638238914927025372981341),
+                new FalconFPR( 0.879012226428633477831323711), new FalconFPR( 0.476799230063322133342158117),
+                new FalconFPR(-0.476799230063322133342158117), new FalconFPR( 0.879012226428633477831323711),
+                new FalconFPR( 0.284407537211271843618310615), new FalconFPR( 0.958703474895871555374645792),
+                new FalconFPR(-0.958703474895871555374645792), new FalconFPR( 0.284407537211271843618310615),
+                new FalconFPR( 0.955141168305770721498157712), new FalconFPR( 0.296150888243623824121786128),
+                new FalconFPR(-0.296150888243623824121786128), new FalconFPR( 0.955141168305770721498157712),
+                new FalconFPR( 0.465976495767966177902756065), new FalconFPR( 0.884797098430937780104007041),
+                new FalconFPR(-0.884797098430937780104007041), new FalconFPR( 0.465976495767966177902756065),
+                new FalconFPR( 0.769103337645579639346626069), new FalconFPR( 0.639124444863775743801488193),
+                new FalconFPR(-0.639124444863775743801488193), new FalconFPR( 0.769103337645579639346626069),
+                new FalconFPR( 0.091908956497132728624990979), new FalconFPR( 0.995767414467659793982495643),
+                new FalconFPR(-0.995767414467659793982495643), new FalconFPR( 0.091908956497132728624990979),
+                new FalconFPR( 0.998475580573294752208559038), new FalconFPR( 0.055195244349689939809447526),
+                new FalconFPR(-0.055195244349689939809447526), new FalconFPR( 0.998475580573294752208559038),
+                new FalconFPR( 0.666999922303637506650154222), new FalconFPR( 0.745057785441465962407907310),
+                new FalconFPR(-0.745057785441465962407907310), new FalconFPR( 0.666999922303637506650154222),
+                new FalconFPR( 0.901348847046022014570746093), new FalconFPR( 0.433093818853151968484222638),
+                new FalconFPR(-0.433093818853151968484222638), new FalconFPR( 0.901348847046022014570746093),
+                new FalconFPR( 0.331106305759876401737190737), new FalconFPR( 0.943593458161960361495301445),
+                new FalconFPR(-0.943593458161960361495301445), new FalconFPR( 0.331106305759876401737190737),
+                new FalconFPR( 0.968522094274417316221088329), new FalconFPR( 0.248927605745720168110682816),
+                new FalconFPR(-0.248927605745720168110682816), new FalconFPR( 0.968522094274417316221088329),
+                new FalconFPR( 0.508830142543107036931749324), new FalconFPR( 0.860866938637767279344583877),
+                new FalconFPR(-0.860866938637767279344583877), new FalconFPR( 0.508830142543107036931749324),
+                new FalconFPR( 0.799537269107905033500246232), new FalconFPR( 0.600616479383868926653875896),
+                new FalconFPR(-0.600616479383868926653875896), new FalconFPR( 0.799537269107905033500246232),
+                new FalconFPR( 0.140658239332849230714788846), new FalconFPR( 0.990058210262297105505906464),
+                new FalconFPR(-0.990058210262297105505906464), new FalconFPR( 0.140658239332849230714788846),
+                new FalconFPR( 0.988257567730749491404792538), new FalconFPR( 0.152797185258443427720336613),
+                new FalconFPR(-0.152797185258443427720336613), new FalconFPR( 0.988257567730749491404792538),
+                new FalconFPR( 0.590759701858874228423887908), new FalconFPR( 0.806847553543799272206514313),
+                new FalconFPR(-0.806847553543799272206514313), new FalconFPR( 0.590759701858874228423887908),
+                new FalconFPR( 0.854557988365400520767862276), new FalconFPR( 0.519355990165589587361829932),
+                new FalconFPR(-0.519355990165589587361829932), new FalconFPR( 0.854557988365400520767862276),
+                new FalconFPR( 0.237023605994367206867735915), new FalconFPR( 0.971503890986251775537099622),
+                new FalconFPR(-0.971503890986251775537099622), new FalconFPR( 0.237023605994367206867735915),
+                new FalconFPR( 0.939459223602189911962669246), new FalconFPR( 0.342660717311994397592781983),
+                new FalconFPR(-0.342660717311994397592781983), new FalconFPR( 0.939459223602189911962669246),
+                new FalconFPR( 0.422000270799799685941287941), new FalconFPR( 0.906595704514915365332960588),
+                new FalconFPR(-0.906595704514915365332960588), new FalconFPR( 0.422000270799799685941287941),
+                new FalconFPR( 0.736816568877369875090132520), new FalconFPR( 0.676092703575315960360419228),
+                new FalconFPR(-0.676092703575315960360419228), new FalconFPR( 0.736816568877369875090132520),
+                new FalconFPR( 0.042938256934940823077124540), new FalconFPR( 0.999077727752645382888781997),
+                new FalconFPR(-0.999077727752645382888781997), new FalconFPR( 0.042938256934940823077124540),
+                new FalconFPR( 0.999529417501093163079703322), new FalconFPR( 0.030674803176636625934021028),
+                new FalconFPR(-0.030674803176636625934021028), new FalconFPR( 0.999529417501093163079703322),
+                new FalconFPR( 0.685083667772700381362052545), new FalconFPR( 0.728464390448225196492035438),
+                new FalconFPR(-0.728464390448225196492035438), new FalconFPR( 0.685083667772700381362052545),
+                new FalconFPR( 0.911706032005429851404397325), new FalconFPR( 0.410843171057903942183466675),
+                new FalconFPR(-0.410843171057903942183466675), new FalconFPR( 0.911706032005429851404397325),
+                new FalconFPR( 0.354163525420490382357395796), new FalconFPR( 0.935183509938947577642207480),
+                new FalconFPR(-0.935183509938947577642207480), new FalconFPR( 0.354163525420490382357395796),
+                new FalconFPR( 0.974339382785575860518721668), new FalconFPR( 0.225083911359792835991642120),
+                new FalconFPR(-0.225083911359792835991642120), new FalconFPR( 0.974339382785575860518721668),
+                new FalconFPR( 0.529803624686294668216054671), new FalconFPR( 0.848120344803297251279133563),
+                new FalconFPR(-0.848120344803297251279133563), new FalconFPR( 0.529803624686294668216054671),
+                new FalconFPR( 0.814036329705948361654516690), new FalconFPR( 0.580813958095764545075595272),
+                new FalconFPR(-0.580813958095764545075595272), new FalconFPR( 0.814036329705948361654516690),
+                new FalconFPR( 0.164913120489969921418189113), new FalconFPR( 0.986308097244598647863297524),
+                new FalconFPR(-0.986308097244598647863297524), new FalconFPR( 0.164913120489969921418189113),
+                new FalconFPR( 0.991709753669099522860049931), new FalconFPR( 0.128498110793793172624415589),
+                new FalconFPR(-0.128498110793793172624415589), new FalconFPR( 0.991709753669099522860049931),
+                new FalconFPR( 0.610382806276309452716352152), new FalconFPR( 0.792106577300212351782342879),
+                new FalconFPR(-0.792106577300212351782342879), new FalconFPR( 0.610382806276309452716352152),
+                new FalconFPR( 0.867046245515692651480195629), new FalconFPR( 0.498227666972781852410983869),
+                new FalconFPR(-0.498227666972781852410983869), new FalconFPR( 0.867046245515692651480195629),
+                new FalconFPR( 0.260794117915275518280186509), new FalconFPR( 0.965394441697689374550843858),
+                new FalconFPR(-0.965394441697689374550843858), new FalconFPR( 0.260794117915275518280186509),
+                new FalconFPR( 0.947585591017741134653387321), new FalconFPR( 0.319502030816015677901518272),
+                new FalconFPR(-0.319502030816015677901518272), new FalconFPR( 0.947585591017741134653387321),
+                new FalconFPR( 0.444122144570429231642069418), new FalconFPR( 0.895966249756185155914560282),
+                new FalconFPR(-0.895966249756185155914560282), new FalconFPR( 0.444122144570429231642069418),
+                new FalconFPR( 0.753186799043612482483430486), new FalconFPR( 0.657806693297078656931182264),
+                new FalconFPR(-0.657806693297078656931182264), new FalconFPR( 0.753186799043612482483430486),
+                new FalconFPR( 0.067443919563664057897972422), new FalconFPR( 0.997723066644191609848546728),
+                new FalconFPR(-0.997723066644191609848546728), new FalconFPR( 0.067443919563664057897972422),
+                new FalconFPR( 0.996820299291165714972629398), new FalconFPR( 0.079682437971430121147120656),
+                new FalconFPR(-0.079682437971430121147120656), new FalconFPR( 0.996820299291165714972629398),
+                new FalconFPR( 0.648514401022112445084560551), new FalconFPR( 0.761202385484261814029709836),
+                new FalconFPR(-0.761202385484261814029709836), new FalconFPR( 0.648514401022112445084560551),
+                new FalconFPR( 0.890448723244757889952150560), new FalconFPR( 0.455083587126343823535869268),
+                new FalconFPR(-0.455083587126343823535869268), new FalconFPR( 0.890448723244757889952150560),
+                new FalconFPR( 0.307849640041534893682063646), new FalconFPR( 0.951435020969008369549175569),
+                new FalconFPR(-0.951435020969008369549175569), new FalconFPR( 0.307849640041534893682063646),
+                new FalconFPR( 0.962121404269041595429604316), new FalconFPR( 0.272621355449948984493347477),
+                new FalconFPR(-0.272621355449948984493347477), new FalconFPR( 0.962121404269041595429604316),
+                new FalconFPR( 0.487550160148435954641485027), new FalconFPR( 0.873094978418290098636085973),
+                new FalconFPR(-0.873094978418290098636085973), new FalconFPR( 0.487550160148435954641485027),
+                new FalconFPR( 0.784556597155575233023892575), new FalconFPR( 0.620057211763289178646268191),
+                new FalconFPR(-0.620057211763289178646268191), new FalconFPR( 0.784556597155575233023892575),
+                new FalconFPR( 0.116318630911904767252544319), new FalconFPR( 0.993211949234794533104601012),
+                new FalconFPR(-0.993211949234794533104601012), new FalconFPR( 0.116318630911904767252544319),
+                new FalconFPR( 0.984210092386929073193874387), new FalconFPR( 0.177004220412148756196839844),
+                new FalconFPR(-0.177004220412148756196839844), new FalconFPR( 0.984210092386929073193874387),
+                new FalconFPR( 0.570780745886967280232652864), new FalconFPR( 0.821102514991104679060430820),
+                new FalconFPR(-0.821102514991104679060430820), new FalconFPR( 0.570780745886967280232652864),
+                new FalconFPR( 0.841554977436898409603499520), new FalconFPR( 0.540171472729892881297845480),
+                new FalconFPR(-0.540171472729892881297845480), new FalconFPR( 0.841554977436898409603499520),
+                new FalconFPR( 0.213110319916091373967757518), new FalconFPR( 0.977028142657754351485866211),
+                new FalconFPR(-0.977028142657754351485866211), new FalconFPR( 0.213110319916091373967757518),
+                new FalconFPR( 0.930766961078983731944872340), new FalconFPR( 0.365612997804773870011745909),
+                new FalconFPR(-0.365612997804773870011745909), new FalconFPR( 0.930766961078983731944872340),
+                new FalconFPR( 0.399624199845646828544117031), new FalconFPR( 0.916679059921042663116457013),
+                new FalconFPR(-0.916679059921042663116457013), new FalconFPR( 0.399624199845646828544117031),
+                new FalconFPR( 0.720002507961381629076682999), new FalconFPR( 0.693971460889654009003734389),
+                new FalconFPR(-0.693971460889654009003734389), new FalconFPR( 0.720002507961381629076682999),
+                new FalconFPR( 0.018406729905804820927366313), new FalconFPR( 0.999830581795823422015722275),
+                new FalconFPR(-0.999830581795823422015722275), new FalconFPR( 0.018406729905804820927366313),
+                new FalconFPR( 0.999830581795823422015722275), new FalconFPR( 0.018406729905804820927366313),
+                new FalconFPR(-0.018406729905804820927366313), new FalconFPR( 0.999830581795823422015722275),
+                new FalconFPR( 0.693971460889654009003734389), new FalconFPR( 0.720002507961381629076682999),
+                new FalconFPR(-0.720002507961381629076682999), new FalconFPR( 0.693971460889654009003734389),
+                new FalconFPR( 0.916679059921042663116457013), new FalconFPR( 0.399624199845646828544117031),
+                new FalconFPR(-0.399624199845646828544117031), new FalconFPR( 0.916679059921042663116457013),
+                new FalconFPR( 0.365612997804773870011745909), new FalconFPR( 0.930766961078983731944872340),
+                new FalconFPR(-0.930766961078983731944872340), new FalconFPR( 0.365612997804773870011745909),
+                new FalconFPR( 0.977028142657754351485866211), new FalconFPR( 0.213110319916091373967757518),
+                new FalconFPR(-0.213110319916091373967757518), new FalconFPR( 0.977028142657754351485866211),
+                new FalconFPR( 0.540171472729892881297845480), new FalconFPR( 0.841554977436898409603499520),
+                new FalconFPR(-0.841554977436898409603499520), new FalconFPR( 0.540171472729892881297845480),
+                new FalconFPR( 0.821102514991104679060430820), new FalconFPR( 0.570780745886967280232652864),
+                new FalconFPR(-0.570780745886967280232652864), new FalconFPR( 0.821102514991104679060430820),
+                new FalconFPR( 0.177004220412148756196839844), new FalconFPR( 0.984210092386929073193874387),
+                new FalconFPR(-0.984210092386929073193874387), new FalconFPR( 0.177004220412148756196839844),
+                new FalconFPR( 0.993211949234794533104601012), new FalconFPR( 0.116318630911904767252544319),
+                new FalconFPR(-0.116318630911904767252544319), new FalconFPR( 0.993211949234794533104601012),
+                new FalconFPR( 0.620057211763289178646268191), new FalconFPR( 0.784556597155575233023892575),
+                new FalconFPR(-0.784556597155575233023892575), new FalconFPR( 0.620057211763289178646268191),
+                new FalconFPR( 0.873094978418290098636085973), new FalconFPR( 0.487550160148435954641485027),
+                new FalconFPR(-0.487550160148435954641485027), new FalconFPR( 0.873094978418290098636085973),
+                new FalconFPR( 0.272621355449948984493347477), new FalconFPR( 0.962121404269041595429604316),
+                new FalconFPR(-0.962121404269041595429604316), new FalconFPR( 0.272621355449948984493347477),
+                new FalconFPR( 0.951435020969008369549175569), new FalconFPR( 0.307849640041534893682063646),
+                new FalconFPR(-0.307849640041534893682063646), new FalconFPR( 0.951435020969008369549175569),
+                new FalconFPR( 0.455083587126343823535869268), new FalconFPR( 0.890448723244757889952150560),
+                new FalconFPR(-0.890448723244757889952150560), new FalconFPR( 0.455083587126343823535869268),
+                new FalconFPR( 0.761202385484261814029709836), new FalconFPR( 0.648514401022112445084560551),
+                new FalconFPR(-0.648514401022112445084560551), new FalconFPR( 0.761202385484261814029709836),
+                new FalconFPR( 0.079682437971430121147120656), new FalconFPR( 0.996820299291165714972629398),
+                new FalconFPR(-0.996820299291165714972629398), new FalconFPR( 0.079682437971430121147120656),
+                new FalconFPR( 0.997723066644191609848546728), new FalconFPR( 0.067443919563664057897972422),
+                new FalconFPR(-0.067443919563664057897972422), new FalconFPR( 0.997723066644191609848546728),
+                new FalconFPR( 0.657806693297078656931182264), new FalconFPR( 0.753186799043612482483430486),
+                new FalconFPR(-0.753186799043612482483430486), new FalconFPR( 0.657806693297078656931182264),
+                new FalconFPR( 0.895966249756185155914560282), new FalconFPR( 0.444122144570429231642069418),
+                new FalconFPR(-0.444122144570429231642069418), new FalconFPR( 0.895966249756185155914560282),
+                new FalconFPR( 0.319502030816015677901518272), new FalconFPR( 0.947585591017741134653387321),
+                new FalconFPR(-0.947585591017741134653387321), new FalconFPR( 0.319502030816015677901518272),
+                new FalconFPR( 0.965394441697689374550843858), new FalconFPR( 0.260794117915275518280186509),
+                new FalconFPR(-0.260794117915275518280186509), new FalconFPR( 0.965394441697689374550843858),
+                new FalconFPR( 0.498227666972781852410983869), new FalconFPR( 0.867046245515692651480195629),
+                new FalconFPR(-0.867046245515692651480195629), new FalconFPR( 0.498227666972781852410983869),
+                new FalconFPR( 0.792106577300212351782342879), new FalconFPR( 0.610382806276309452716352152),
+                new FalconFPR(-0.610382806276309452716352152), new FalconFPR( 0.792106577300212351782342879),
+                new FalconFPR( 0.128498110793793172624415589), new FalconFPR( 0.991709753669099522860049931),
+                new FalconFPR(-0.991709753669099522860049931), new FalconFPR( 0.128498110793793172624415589),
+                new FalconFPR( 0.986308097244598647863297524), new FalconFPR( 0.164913120489969921418189113),
+                new FalconFPR(-0.164913120489969921418189113), new FalconFPR( 0.986308097244598647863297524),
+                new FalconFPR( 0.580813958095764545075595272), new FalconFPR( 0.814036329705948361654516690),
+                new FalconFPR(-0.814036329705948361654516690), new FalconFPR( 0.580813958095764545075595272),
+                new FalconFPR( 0.848120344803297251279133563), new FalconFPR( 0.529803624686294668216054671),
+                new FalconFPR(-0.529803624686294668216054671), new FalconFPR( 0.848120344803297251279133563),
+                new FalconFPR( 0.225083911359792835991642120), new FalconFPR( 0.974339382785575860518721668),
+                new FalconFPR(-0.974339382785575860518721668), new FalconFPR( 0.225083911359792835991642120),
+                new FalconFPR( 0.935183509938947577642207480), new FalconFPR( 0.354163525420490382357395796),
+                new FalconFPR(-0.354163525420490382357395796), new FalconFPR( 0.935183509938947577642207480),
+                new FalconFPR( 0.410843171057903942183466675), new FalconFPR( 0.911706032005429851404397325),
+                new FalconFPR(-0.911706032005429851404397325), new FalconFPR( 0.410843171057903942183466675),
+                new FalconFPR( 0.728464390448225196492035438), new FalconFPR( 0.685083667772700381362052545),
+                new FalconFPR(-0.685083667772700381362052545), new FalconFPR( 0.728464390448225196492035438),
+                new FalconFPR( 0.030674803176636625934021028), new FalconFPR( 0.999529417501093163079703322),
+                new FalconFPR(-0.999529417501093163079703322), new FalconFPR( 0.030674803176636625934021028),
+                new FalconFPR( 0.999077727752645382888781997), new FalconFPR( 0.042938256934940823077124540),
+                new FalconFPR(-0.042938256934940823077124540), new FalconFPR( 0.999077727752645382888781997),
+                new FalconFPR( 0.676092703575315960360419228), new FalconFPR( 0.736816568877369875090132520),
+                new FalconFPR(-0.736816568877369875090132520), new FalconFPR( 0.676092703575315960360419228),
+                new FalconFPR( 0.906595704514915365332960588), new FalconFPR( 0.422000270799799685941287941),
+                new FalconFPR(-0.422000270799799685941287941), new FalconFPR( 0.906595704514915365332960588),
+                new FalconFPR( 0.342660717311994397592781983), new FalconFPR( 0.939459223602189911962669246),
+                new FalconFPR(-0.939459223602189911962669246), new FalconFPR( 0.342660717311994397592781983),
+                new FalconFPR( 0.971503890986251775537099622), new FalconFPR( 0.237023605994367206867735915),
+                new FalconFPR(-0.237023605994367206867735915), new FalconFPR( 0.971503890986251775537099622),
+                new FalconFPR( 0.519355990165589587361829932), new FalconFPR( 0.854557988365400520767862276),
+                new FalconFPR(-0.854557988365400520767862276), new FalconFPR( 0.519355990165589587361829932),
+                new FalconFPR( 0.806847553543799272206514313), new FalconFPR( 0.590759701858874228423887908),
+                new FalconFPR(-0.590759701858874228423887908), new FalconFPR( 0.806847553543799272206514313),
+                new FalconFPR( 0.152797185258443427720336613), new FalconFPR( 0.988257567730749491404792538),
+                new FalconFPR(-0.988257567730749491404792538), new FalconFPR( 0.152797185258443427720336613),
+                new FalconFPR( 0.990058210262297105505906464), new FalconFPR( 0.140658239332849230714788846),
+                new FalconFPR(-0.140658239332849230714788846), new FalconFPR( 0.990058210262297105505906464),
+                new FalconFPR( 0.600616479383868926653875896), new FalconFPR( 0.799537269107905033500246232),
+                new FalconFPR(-0.799537269107905033500246232), new FalconFPR( 0.600616479383868926653875896),
+                new FalconFPR( 0.860866938637767279344583877), new FalconFPR( 0.508830142543107036931749324),
+                new FalconFPR(-0.508830142543107036931749324), new FalconFPR( 0.860866938637767279344583877),
+                new FalconFPR( 0.248927605745720168110682816), new FalconFPR( 0.968522094274417316221088329),
+                new FalconFPR(-0.968522094274417316221088329), new FalconFPR( 0.248927605745720168110682816),
+                new FalconFPR( 0.943593458161960361495301445), new FalconFPR( 0.331106305759876401737190737),
+                new FalconFPR(-0.331106305759876401737190737), new FalconFPR( 0.943593458161960361495301445),
+                new FalconFPR( 0.433093818853151968484222638), new FalconFPR( 0.901348847046022014570746093),
+                new FalconFPR(-0.901348847046022014570746093), new FalconFPR( 0.433093818853151968484222638),
+                new FalconFPR( 0.745057785441465962407907310), new FalconFPR( 0.666999922303637506650154222),
+                new FalconFPR(-0.666999922303637506650154222), new FalconFPR( 0.745057785441465962407907310),
+                new FalconFPR( 0.055195244349689939809447526), new FalconFPR( 0.998475580573294752208559038),
+                new FalconFPR(-0.998475580573294752208559038), new FalconFPR( 0.055195244349689939809447526),
+                new FalconFPR( 0.995767414467659793982495643), new FalconFPR( 0.091908956497132728624990979),
+                new FalconFPR(-0.091908956497132728624990979), new FalconFPR( 0.995767414467659793982495643),
+                new FalconFPR( 0.639124444863775743801488193), new FalconFPR( 0.769103337645579639346626069),
+                new FalconFPR(-0.769103337645579639346626069), new FalconFPR( 0.639124444863775743801488193),
+                new FalconFPR( 0.884797098430937780104007041), new FalconFPR( 0.465976495767966177902756065),
+                new FalconFPR(-0.465976495767966177902756065), new FalconFPR( 0.884797098430937780104007041),
+                new FalconFPR( 0.296150888243623824121786128), new FalconFPR( 0.955141168305770721498157712),
+                new FalconFPR(-0.955141168305770721498157712), new FalconFPR( 0.296150888243623824121786128),
+                new FalconFPR( 0.958703474895871555374645792), new FalconFPR( 0.284407537211271843618310615),
+                new FalconFPR(-0.284407537211271843618310615), new FalconFPR( 0.958703474895871555374645792),
+                new FalconFPR( 0.476799230063322133342158117), new FalconFPR( 0.879012226428633477831323711),
+                new FalconFPR(-0.879012226428633477831323711), new FalconFPR( 0.476799230063322133342158117),
+                new FalconFPR( 0.776888465673232450040827983), new FalconFPR( 0.629638238914927025372981341),
+                new FalconFPR(-0.629638238914927025372981341), new FalconFPR( 0.776888465673232450040827983),
+                new FalconFPR( 0.104121633872054579120943880), new FalconFPR( 0.994564570734255452119106243),
+                new FalconFPR(-0.994564570734255452119106243), new FalconFPR( 0.104121633872054579120943880),
+                new FalconFPR( 0.981963869109555264072848154), new FalconFPR( 0.189068664149806212754997837),
+                new FalconFPR(-0.189068664149806212754997837), new FalconFPR( 0.981963869109555264072848154),
+                new FalconFPR( 0.560661576197336023839710223), new FalconFPR( 0.828045045257755752067527592),
+                new FalconFPR(-0.828045045257755752067527592), new FalconFPR( 0.560661576197336023839710223),
+                new FalconFPR( 0.834862874986380056304401383), new FalconFPR( 0.550457972936604802977289893),
+                new FalconFPR(-0.550457972936604802977289893), new FalconFPR( 0.834862874986380056304401383),
+                new FalconFPR( 0.201104634842091911558443546), new FalconFPR( 0.979569765685440534439326110),
+                new FalconFPR(-0.979569765685440534439326110), new FalconFPR( 0.201104634842091911558443546),
+                new FalconFPR( 0.926210242138311341974793388), new FalconFPR( 0.377007410216418256726567823),
+                new FalconFPR(-0.377007410216418256726567823), new FalconFPR( 0.926210242138311341974793388),
+                new FalconFPR( 0.388345046698826291624993541), new FalconFPR( 0.921514039342041943465396332),
+                new FalconFPR(-0.921514039342041943465396332), new FalconFPR( 0.388345046698826291624993541),
+                new FalconFPR( 0.711432195745216441522130290), new FalconFPR( 0.702754744457225302452914421),
+                new FalconFPR(-0.702754744457225302452914421), new FalconFPR( 0.711432195745216441522130290),
+                new FalconFPR( 0.006135884649154475359640235), new FalconFPR( 0.999981175282601142656990438),
+                new FalconFPR(-0.999981175282601142656990438), new FalconFPR( 0.006135884649154475359640235),
+                new FalconFPR( 0.999995293809576171511580126), new FalconFPR( 0.003067956762965976270145365),
+                new FalconFPR(-0.003067956762965976270145365), new FalconFPR( 0.999995293809576171511580126),
+                new FalconFPR( 0.704934080375904908852523758), new FalconFPR( 0.709272826438865651316533772),
+                new FalconFPR(-0.709272826438865651316533772), new FalconFPR( 0.704934080375904908852523758),
+                new FalconFPR( 0.922701128333878570437264227), new FalconFPR( 0.385516053843918864075607949),
+                new FalconFPR(-0.385516053843918864075607949), new FalconFPR( 0.922701128333878570437264227),
+                new FalconFPR( 0.379847208924051170576281147), new FalconFPR( 0.925049240782677590302371869),
+                new FalconFPR(-0.925049240782677590302371869), new FalconFPR( 0.379847208924051170576281147),
+                new FalconFPR( 0.980182135968117392690210009), new FalconFPR( 0.198098410717953586179324918),
+                new FalconFPR(-0.198098410717953586179324918), new FalconFPR( 0.980182135968117392690210009),
+                new FalconFPR( 0.553016705580027531764226988), new FalconFPR( 0.833170164701913186439915922),
+                new FalconFPR(-0.833170164701913186439915922), new FalconFPR( 0.553016705580027531764226988),
+                new FalconFPR( 0.829761233794523042469023765), new FalconFPR( 0.558118531220556115693702964),
+                new FalconFPR(-0.558118531220556115693702964), new FalconFPR( 0.829761233794523042469023765),
+                new FalconFPR( 0.192080397049892441679288205), new FalconFPR( 0.981379193313754574318224190),
+                new FalconFPR(-0.981379193313754574318224190), new FalconFPR( 0.192080397049892441679288205),
+                new FalconFPR( 0.994879330794805620591166107), new FalconFPR( 0.101069862754827824987887585),
+                new FalconFPR(-0.101069862754827824987887585), new FalconFPR( 0.994879330794805620591166107),
+                new FalconFPR( 0.632018735939809021909403706), new FalconFPR( 0.774953106594873878359129282),
+                new FalconFPR(-0.774953106594873878359129282), new FalconFPR( 0.632018735939809021909403706),
+                new FalconFPR( 0.880470889052160770806542929), new FalconFPR( 0.474100214650550014398580015),
+                new FalconFPR(-0.474100214650550014398580015), new FalconFPR( 0.880470889052160770806542929),
+                new FalconFPR( 0.287347459544729526477331841), new FalconFPR( 0.957826413027532890321037029),
+                new FalconFPR(-0.957826413027532890321037029), new FalconFPR( 0.287347459544729526477331841),
+                new FalconFPR( 0.956045251349996443270479823), new FalconFPR( 0.293219162694258650606608599),
+                new FalconFPR(-0.293219162694258650606608599), new FalconFPR( 0.956045251349996443270479823),
+                new FalconFPR( 0.468688822035827933697617870), new FalconFPR( 0.883363338665731594736308015),
+                new FalconFPR(-0.883363338665731594736308015), new FalconFPR( 0.468688822035827933697617870),
+                new FalconFPR( 0.771060524261813773200605759), new FalconFPR( 0.636761861236284230413943435),
+                new FalconFPR(-0.636761861236284230413943435), new FalconFPR( 0.771060524261813773200605759),
+                new FalconFPR( 0.094963495329638998938034312), new FalconFPR( 0.995480755491926941769171600),
+                new FalconFPR(-0.995480755491926941769171600), new FalconFPR( 0.094963495329638998938034312),
+                new FalconFPR( 0.998640218180265222418199049), new FalconFPR( 0.052131704680283321236358216),
+                new FalconFPR(-0.052131704680283321236358216), new FalconFPR( 0.998640218180265222418199049),
+                new FalconFPR( 0.669282588346636065720696366), new FalconFPR( 0.743007952135121693517362293),
+                new FalconFPR(-0.743007952135121693517362293), new FalconFPR( 0.669282588346636065720696366),
+                new FalconFPR( 0.902673318237258806751502391), new FalconFPR( 0.430326481340082633908199031),
+                new FalconFPR(-0.430326481340082633908199031), new FalconFPR( 0.902673318237258806751502391),
+                new FalconFPR( 0.333999651442009404650865481), new FalconFPR( 0.942573197601446879280758735),
+                new FalconFPR(-0.942573197601446879280758735), new FalconFPR( 0.333999651442009404650865481),
+                new FalconFPR( 0.969281235356548486048290738), new FalconFPR( 0.245955050335794611599924709),
+                new FalconFPR(-0.245955050335794611599924709), new FalconFPR( 0.969281235356548486048290738),
+                new FalconFPR( 0.511468850437970399504391001), new FalconFPR( 0.859301818357008404783582139),
+                new FalconFPR(-0.859301818357008404783582139), new FalconFPR( 0.511468850437970399504391001),
+                new FalconFPR( 0.801376171723140219430247777), new FalconFPR( 0.598160706996342311724958652),
+                new FalconFPR(-0.598160706996342311724958652), new FalconFPR( 0.801376171723140219430247777),
+                new FalconFPR( 0.143695033150294454819773349), new FalconFPR( 0.989622017463200834623694454),
+                new FalconFPR(-0.989622017463200834623694454), new FalconFPR( 0.143695033150294454819773349),
+                new FalconFPR( 0.988721691960323767604516485), new FalconFPR( 0.149764534677321517229695737),
+                new FalconFPR(-0.149764534677321517229695737), new FalconFPR( 0.988721691960323767604516485),
+                new FalconFPR( 0.593232295039799808047809426), new FalconFPR( 0.805031331142963597922659282),
+                new FalconFPR(-0.805031331142963597922659282), new FalconFPR( 0.593232295039799808047809426),
+                new FalconFPR( 0.856147328375194481019630732), new FalconFPR( 0.516731799017649881508753876),
+                new FalconFPR(-0.516731799017649881508753876), new FalconFPR( 0.856147328375194481019630732),
+                new FalconFPR( 0.240003022448741486568922365), new FalconFPR( 0.970772140728950302138169611),
+                new FalconFPR(-0.970772140728950302138169611), new FalconFPR( 0.240003022448741486568922365),
+                new FalconFPR( 0.940506070593268323787291309), new FalconFPR( 0.339776884406826857828825803),
+                new FalconFPR(-0.339776884406826857828825803), new FalconFPR( 0.940506070593268323787291309),
+                new FalconFPR( 0.424779681209108833357226189), new FalconFPR( 0.905296759318118774354048329),
+                new FalconFPR(-0.905296759318118774354048329), new FalconFPR( 0.424779681209108833357226189),
+                new FalconFPR( 0.738887324460615147933116508), new FalconFPR( 0.673829000378756060917568372),
+                new FalconFPR(-0.673829000378756060917568372), new FalconFPR( 0.738887324460615147933116508),
+                new FalconFPR( 0.046003182130914628814301788), new FalconFPR( 0.998941293186856850633930266),
+                new FalconFPR(-0.998941293186856850633930266), new FalconFPR( 0.046003182130914628814301788),
+                new FalconFPR( 0.999618822495178597116830637), new FalconFPR( 0.027608145778965741612354872),
+                new FalconFPR(-0.027608145778965741612354872), new FalconFPR( 0.999618822495178597116830637),
+                new FalconFPR( 0.687315340891759108199186948), new FalconFPR( 0.726359155084345976817494315),
+                new FalconFPR(-0.726359155084345976817494315), new FalconFPR( 0.687315340891759108199186948),
+                new FalconFPR( 0.912962190428398164628018233), new FalconFPR( 0.408044162864978680820747499),
+                new FalconFPR(-0.408044162864978680820747499), new FalconFPR( 0.912962190428398164628018233),
+                new FalconFPR( 0.357030961233430032614954036), new FalconFPR( 0.934092550404258914729877883),
+                new FalconFPR(-0.934092550404258914729877883), new FalconFPR( 0.357030961233430032614954036),
+                new FalconFPR( 0.975025345066994146844913468), new FalconFPR( 0.222093620973203534094094721),
+                new FalconFPR(-0.222093620973203534094094721), new FalconFPR( 0.975025345066994146844913468),
+                new FalconFPR( 0.532403127877197971442805218), new FalconFPR( 0.846490938774052078300544488),
+                new FalconFPR(-0.846490938774052078300544488), new FalconFPR( 0.532403127877197971442805218),
+                new FalconFPR( 0.815814410806733789010772660), new FalconFPR( 0.578313796411655563342245019),
+                new FalconFPR(-0.578313796411655563342245019), new FalconFPR( 0.815814410806733789010772660),
+                new FalconFPR( 0.167938294974731178054745536), new FalconFPR( 0.985797509167567424700995000),
+                new FalconFPR(-0.985797509167567424700995000), new FalconFPR( 0.167938294974731178054745536),
+                new FalconFPR( 0.992099313142191757112085445), new FalconFPR( 0.125454983411546238542336453),
+                new FalconFPR(-0.125454983411546238542336453), new FalconFPR( 0.992099313142191757112085445),
+                new FalconFPR( 0.612810082429409703935211936), new FalconFPR( 0.790230221437310055030217152),
+                new FalconFPR(-0.790230221437310055030217152), new FalconFPR( 0.612810082429409703935211936),
+                new FalconFPR( 0.868570705971340895340449876), new FalconFPR( 0.495565261825772531150266670),
+                new FalconFPR(-0.495565261825772531150266670), new FalconFPR( 0.868570705971340895340449876),
+                new FalconFPR( 0.263754678974831383611349322), new FalconFPR( 0.964589793289812723836432159),
+                new FalconFPR(-0.964589793289812723836432159), new FalconFPR( 0.263754678974831383611349322),
+                new FalconFPR( 0.948561349915730288158494826), new FalconFPR( 0.316593375556165867243047035),
+                new FalconFPR(-0.316593375556165867243047035), new FalconFPR( 0.948561349915730288158494826),
+                new FalconFPR( 0.446868840162374195353044389), new FalconFPR( 0.894599485631382678433072126),
+                new FalconFPR(-0.894599485631382678433072126), new FalconFPR( 0.446868840162374195353044389),
+                new FalconFPR( 0.755201376896536527598710756), new FalconFPR( 0.655492852999615385312679701),
+                new FalconFPR(-0.655492852999615385312679701), new FalconFPR( 0.755201376896536527598710756),
+                new FalconFPR( 0.070504573389613863027351471), new FalconFPR( 0.997511456140303459699448390),
+                new FalconFPR(-0.997511456140303459699448390), new FalconFPR( 0.070504573389613863027351471),
+                new FalconFPR( 0.997060070339482978987989949), new FalconFPR( 0.076623861392031492278332463),
+                new FalconFPR(-0.076623861392031492278332463), new FalconFPR( 0.997060070339482978987989949),
+                new FalconFPR( 0.650846684996380915068975573), new FalconFPR( 0.759209188978388033485525443),
+                new FalconFPR(-0.759209188978388033485525443), new FalconFPR( 0.650846684996380915068975573),
+                new FalconFPR( 0.891840709392342727796478697), new FalconFPR( 0.452349587233770874133026703),
+                new FalconFPR(-0.452349587233770874133026703), new FalconFPR( 0.891840709392342727796478697),
+                new FalconFPR( 0.310767152749611495835997250), new FalconFPR( 0.950486073949481721759926101),
+                new FalconFPR(-0.950486073949481721759926101), new FalconFPR( 0.310767152749611495835997250),
+                new FalconFPR( 0.962953266873683886347921481), new FalconFPR( 0.269668325572915106525464462),
+                new FalconFPR(-0.269668325572915106525464462), new FalconFPR( 0.962953266873683886347921481),
+                new FalconFPR( 0.490226483288291154229598449), new FalconFPR( 0.871595086655951034842481435),
+                new FalconFPR(-0.871595086655951034842481435), new FalconFPR( 0.490226483288291154229598449),
+                new FalconFPR( 0.786455213599085757522319464), new FalconFPR( 0.617647307937803932403979402),
+                new FalconFPR(-0.617647307937803932403979402), new FalconFPR( 0.786455213599085757522319464),
+                new FalconFPR( 0.119365214810991364593637790), new FalconFPR( 0.992850414459865090793563344),
+                new FalconFPR(-0.992850414459865090793563344), new FalconFPR( 0.119365214810991364593637790),
+                new FalconFPR( 0.984748501801904218556553176), new FalconFPR( 0.173983873387463827950700807),
+                new FalconFPR(-0.173983873387463827950700807), new FalconFPR( 0.984748501801904218556553176),
+                new FalconFPR( 0.573297166698042212820171239), new FalconFPR( 0.819347520076796960824689637),
+                new FalconFPR(-0.819347520076796960824689637), new FalconFPR( 0.573297166698042212820171239),
+                new FalconFPR( 0.843208239641845437161743865), new FalconFPR( 0.537587076295645482502214932),
+                new FalconFPR(-0.537587076295645482502214932), new FalconFPR( 0.843208239641845437161743865),
+                new FalconFPR( 0.216106797076219509948385131), new FalconFPR( 0.976369731330021149312732194),
+                new FalconFPR(-0.976369731330021149312732194), new FalconFPR( 0.216106797076219509948385131),
+                new FalconFPR( 0.931884265581668106718557199), new FalconFPR( 0.362755724367397216204854462),
+                new FalconFPR(-0.362755724367397216204854462), new FalconFPR( 0.931884265581668106718557199),
+                new FalconFPR( 0.402434650859418441082533934), new FalconFPR( 0.915448716088267819566431292),
+                new FalconFPR(-0.915448716088267819566431292), new FalconFPR( 0.402434650859418441082533934),
+                new FalconFPR( 0.722128193929215321243607198), new FalconFPR( 0.691759258364157774906734132),
+                new FalconFPR(-0.691759258364157774906734132), new FalconFPR( 0.722128193929215321243607198),
+                new FalconFPR( 0.021474080275469507418374898), new FalconFPR( 0.999769405351215321657617036),
+                new FalconFPR(-0.999769405351215321657617036), new FalconFPR( 0.021474080275469507418374898),
+                new FalconFPR( 0.999882347454212525633049627), new FalconFPR( 0.015339206284988101044151868),
+                new FalconFPR(-0.015339206284988101044151868), new FalconFPR( 0.999882347454212525633049627),
+                new FalconFPR( 0.696177131491462944788582591), new FalconFPR( 0.717870045055731736211325329),
+                new FalconFPR(-0.717870045055731736211325329), new FalconFPR( 0.696177131491462944788582591),
+                new FalconFPR( 0.917900775621390457642276297), new FalconFPR( 0.396809987416710328595290911),
+                new FalconFPR(-0.396809987416710328595290911), new FalconFPR( 0.917900775621390457642276297),
+                new FalconFPR( 0.368466829953372331712746222), new FalconFPR( 0.929640895843181265457918066),
+                new FalconFPR(-0.929640895843181265457918066), new FalconFPR( 0.368466829953372331712746222),
+                new FalconFPR( 0.977677357824509979943404762), new FalconFPR( 0.210111836880469621717489972),
+                new FalconFPR(-0.210111836880469621717489972), new FalconFPR( 0.977677357824509979943404762),
+                new FalconFPR( 0.542750784864515906586768661), new FalconFPR( 0.839893794195999504583383987),
+                new FalconFPR(-0.839893794195999504583383987), new FalconFPR( 0.542750784864515906586768661),
+                new FalconFPR( 0.822849781375826332046780034), new FalconFPR( 0.568258952670131549790548489),
+                new FalconFPR(-0.568258952670131549790548489), new FalconFPR( 0.822849781375826332046780034),
+                new FalconFPR( 0.180022901405699522679906590), new FalconFPR( 0.983662419211730274396237776),
+                new FalconFPR(-0.983662419211730274396237776), new FalconFPR( 0.180022901405699522679906590),
+                new FalconFPR( 0.993564135520595333782021697), new FalconFPR( 0.113270952177564349018228733),
+                new FalconFPR(-0.113270952177564349018228733), new FalconFPR( 0.993564135520595333782021697),
+                new FalconFPR( 0.622461279374149972519166721), new FalconFPR( 0.782650596166575738458949301),
+                new FalconFPR(-0.782650596166575738458949301), new FalconFPR( 0.622461279374149972519166721),
+                new FalconFPR( 0.874586652278176112634431897), new FalconFPR( 0.484869248000791101822951699),
+                new FalconFPR(-0.484869248000791101822951699), new FalconFPR( 0.874586652278176112634431897),
+                new FalconFPR( 0.275571819310958163076425168), new FalconFPR( 0.961280485811320641748659653),
+                new FalconFPR(-0.961280485811320641748659653), new FalconFPR( 0.275571819310958163076425168),
+                new FalconFPR( 0.952375012719765858529893608), new FalconFPR( 0.304929229735402406490728633),
+                new FalconFPR(-0.304929229735402406490728633), new FalconFPR( 0.952375012719765858529893608),
+                new FalconFPR( 0.457813303598877221904961155), new FalconFPR( 0.889048355854664562540777729),
+                new FalconFPR(-0.889048355854664562540777729), new FalconFPR( 0.457813303598877221904961155),
+                new FalconFPR( 0.763188417263381271704838297), new FalconFPR( 0.646176012983316364832802220),
+                new FalconFPR(-0.646176012983316364832802220), new FalconFPR( 0.763188417263381271704838297),
+                new FalconFPR( 0.082740264549375693111987083), new FalconFPR( 0.996571145790554847093566910),
+                new FalconFPR(-0.996571145790554847093566910), new FalconFPR( 0.082740264549375693111987083),
+                new FalconFPR( 0.997925286198596012623025462), new FalconFPR( 0.064382630929857460819324537),
+                new FalconFPR(-0.064382630929857460819324537), new FalconFPR( 0.997925286198596012623025462),
+                new FalconFPR( 0.660114342067420478559490747), new FalconFPR( 0.751165131909686411205819422),
+                new FalconFPR(-0.751165131909686411205819422), new FalconFPR( 0.660114342067420478559490747),
+                new FalconFPR( 0.897324580705418281231391836), new FalconFPR( 0.441371268731716692879988968),
+                new FalconFPR(-0.441371268731716692879988968), new FalconFPR( 0.897324580705418281231391836),
+                new FalconFPR( 0.322407678801069848384807478), new FalconFPR( 0.946600913083283570044599823),
+                new FalconFPR(-0.946600913083283570044599823), new FalconFPR( 0.322407678801069848384807478),
+                new FalconFPR( 0.966190003445412555433832961), new FalconFPR( 0.257831102162159005614471295),
+                new FalconFPR(-0.257831102162159005614471295), new FalconFPR( 0.966190003445412555433832961),
+                new FalconFPR( 0.500885382611240786241285004), new FalconFPR( 0.865513624090569082825488358),
+                new FalconFPR(-0.865513624090569082825488358), new FalconFPR( 0.500885382611240786241285004),
+                new FalconFPR( 0.793975477554337164895083757), new FalconFPR( 0.607949784967773667243642671),
+                new FalconFPR(-0.607949784967773667243642671), new FalconFPR( 0.793975477554337164895083757),
+                new FalconFPR( 0.131540028702883111103387493), new FalconFPR( 0.991310859846115418957349799),
+                new FalconFPR(-0.991310859846115418957349799), new FalconFPR( 0.131540028702883111103387493),
+                new FalconFPR( 0.986809401814185476970235952), new FalconFPR( 0.161886393780111837641387995),
+                new FalconFPR(-0.161886393780111837641387995), new FalconFPR( 0.986809401814185476970235952),
+                new FalconFPR( 0.583308652937698294392830961), new FalconFPR( 0.812250586585203913049744181),
+                new FalconFPR(-0.812250586585203913049744181), new FalconFPR( 0.583308652937698294392830961),
+                new FalconFPR( 0.849741768000852489471268395), new FalconFPR( 0.527199134781901348464274575),
+                new FalconFPR(-0.527199134781901348464274575), new FalconFPR( 0.849741768000852489471268395),
+                new FalconFPR( 0.228072083170885739254457379), new FalconFPR( 0.973644249650811925318383912),
+                new FalconFPR(-0.973644249650811925318383912), new FalconFPR( 0.228072083170885739254457379),
+                new FalconFPR( 0.936265667170278246576310996), new FalconFPR( 0.351292756085567125601307623),
+                new FalconFPR(-0.351292756085567125601307623), new FalconFPR( 0.936265667170278246576310996),
+                new FalconFPR( 0.413638312238434547471944324), new FalconFPR( 0.910441292258067196934095369),
+                new FalconFPR(-0.910441292258067196934095369), new FalconFPR( 0.413638312238434547471944324),
+                new FalconFPR( 0.730562769227827561177758850), new FalconFPR( 0.682845546385248068164596123),
+                new FalconFPR(-0.682845546385248068164596123), new FalconFPR( 0.730562769227827561177758850),
+                new FalconFPR( 0.033741171851377584833716112), new FalconFPR( 0.999430604555461772019008327),
+                new FalconFPR(-0.999430604555461772019008327), new FalconFPR( 0.033741171851377584833716112),
+                new FalconFPR( 0.999204758618363895492950001), new FalconFPR( 0.039872927587739811128578738),
+                new FalconFPR(-0.039872927587739811128578738), new FalconFPR( 0.999204758618363895492950001),
+                new FalconFPR( 0.678350043129861486873655042), new FalconFPR( 0.734738878095963464563223604),
+                new FalconFPR(-0.734738878095963464563223604), new FalconFPR( 0.678350043129861486873655042),
+                new FalconFPR( 0.907886116487666212038681480), new FalconFPR( 0.419216888363223956433010020),
+                new FalconFPR(-0.419216888363223956433010020), new FalconFPR( 0.907886116487666212038681480),
+                new FalconFPR( 0.345541324963989065539191723), new FalconFPR( 0.938403534063108112192420774),
+                new FalconFPR(-0.938403534063108112192420774), new FalconFPR( 0.345541324963989065539191723),
+                new FalconFPR( 0.972226497078936305708321144), new FalconFPR( 0.234041958583543423191242045),
+                new FalconFPR(-0.234041958583543423191242045), new FalconFPR( 0.972226497078936305708321144),
+                new FalconFPR( 0.521975292937154342694258318), new FalconFPR( 0.852960604930363657746588082),
+                new FalconFPR(-0.852960604930363657746588082), new FalconFPR( 0.521975292937154342694258318),
+                new FalconFPR( 0.808656181588174991946968128), new FalconFPR( 0.588281548222645304786439813),
+                new FalconFPR(-0.588281548222645304786439813), new FalconFPR( 0.808656181588174991946968128),
+                new FalconFPR( 0.155828397654265235743101486), new FalconFPR( 0.987784141644572154230969032),
+                new FalconFPR(-0.987784141644572154230969032), new FalconFPR( 0.155828397654265235743101486),
+                new FalconFPR( 0.990485084256457037998682243), new FalconFPR( 0.137620121586486044948441663),
+                new FalconFPR(-0.137620121586486044948441663), new FalconFPR( 0.990485084256457037998682243),
+                new FalconFPR( 0.603066598540348201693430617), new FalconFPR( 0.797690840943391108362662755),
+                new FalconFPR(-0.797690840943391108362662755), new FalconFPR( 0.603066598540348201693430617),
+                new FalconFPR( 0.862423956111040538690933878), new FalconFPR( 0.506186645345155291048942344),
+                new FalconFPR(-0.506186645345155291048942344), new FalconFPR( 0.862423956111040538690933878),
+                new FalconFPR( 0.251897818154216950498106628), new FalconFPR( 0.967753837093475465243391912),
+                new FalconFPR(-0.967753837093475465243391912), new FalconFPR( 0.251897818154216950498106628),
+                new FalconFPR( 0.944604837261480265659265493), new FalconFPR( 0.328209843579092526107916817),
+                new FalconFPR(-0.328209843579092526107916817), new FalconFPR( 0.944604837261480265659265493),
+                new FalconFPR( 0.435857079922255491032544080), new FalconFPR( 0.900015892016160228714535267),
+                new FalconFPR(-0.900015892016160228714535267), new FalconFPR( 0.435857079922255491032544080),
+                new FalconFPR( 0.747100605980180144323078847), new FalconFPR( 0.664710978203344868130324985),
+                new FalconFPR(-0.664710978203344868130324985), new FalconFPR( 0.747100605980180144323078847),
+                new FalconFPR( 0.058258264500435759613979782), new FalconFPR( 0.998301544933892840738782163),
+                new FalconFPR(-0.998301544933892840738782163), new FalconFPR( 0.058258264500435759613979782),
+                new FalconFPR( 0.996044700901251989887944810), new FalconFPR( 0.088853552582524596561586535),
+                new FalconFPR(-0.088853552582524596561586535), new FalconFPR( 0.996044700901251989887944810),
+                new FalconFPR( 0.641481012808583151988739898), new FalconFPR( 0.767138911935820381181694573),
+                new FalconFPR(-0.767138911935820381181694573), new FalconFPR( 0.641481012808583151988739898),
+                new FalconFPR( 0.886222530148880631647990821), new FalconFPR( 0.463259783551860197390719637),
+                new FalconFPR(-0.463259783551860197390719637), new FalconFPR( 0.886222530148880631647990821),
+                new FalconFPR( 0.299079826308040476750336973), new FalconFPR( 0.954228095109105629780430732),
+                new FalconFPR(-0.954228095109105629780430732), new FalconFPR( 0.299079826308040476750336973),
+                new FalconFPR( 0.959571513081984528335528181), new FalconFPR( 0.281464937925757984095231007),
+                new FalconFPR(-0.281464937925757984095231007), new FalconFPR( 0.959571513081984528335528181),
+                new FalconFPR( 0.479493757660153026679839798), new FalconFPR( 0.877545290207261291668470750),
+                new FalconFPR(-0.877545290207261291668470750), new FalconFPR( 0.479493757660153026679839798),
+                new FalconFPR( 0.778816512381475953374724325), new FalconFPR( 0.627251815495144113509622565),
+                new FalconFPR(-0.627251815495144113509622565), new FalconFPR( 0.778816512381475953374724325),
+                new FalconFPR( 0.107172424956808849175529148), new FalconFPR( 0.994240449453187946358413442),
+                new FalconFPR(-0.994240449453187946358413442), new FalconFPR( 0.107172424956808849175529148),
+                new FalconFPR( 0.982539302287441255907040396), new FalconFPR( 0.186055151663446648105438304),
+                new FalconFPR(-0.186055151663446648105438304), new FalconFPR( 0.982539302287441255907040396),
+                new FalconFPR( 0.563199344013834115007363772), new FalconFPR( 0.826321062845663480311195452),
+                new FalconFPR(-0.826321062845663480311195452), new FalconFPR( 0.563199344013834115007363772),
+                new FalconFPR( 0.836547727223511984524285790), new FalconFPR( 0.547894059173100165608820571),
+                new FalconFPR(-0.547894059173100165608820571), new FalconFPR( 0.836547727223511984524285790),
+                new FalconFPR( 0.204108966092816874181696950), new FalconFPR( 0.978948175319062194715480124),
+                new FalconFPR(-0.978948175319062194715480124), new FalconFPR( 0.204108966092816874181696950),
+                new FalconFPR( 0.927362525650401087274536959), new FalconFPR( 0.374164062971457997104393020),
+                new FalconFPR(-0.374164062971457997104393020), new FalconFPR( 0.927362525650401087274536959),
+                new FalconFPR( 0.391170384302253888687512949), new FalconFPR( 0.920318276709110566440076541),
+                new FalconFPR(-0.920318276709110566440076541), new FalconFPR( 0.391170384302253888687512949),
+                new FalconFPR( 0.713584868780793592903125099), new FalconFPR( 0.700568793943248366792866380),
+                new FalconFPR(-0.700568793943248366792866380), new FalconFPR( 0.713584868780793592903125099),
+                new FalconFPR( 0.009203754782059819315102378), new FalconFPR( 0.999957644551963866333120920),
+                new FalconFPR(-0.999957644551963866333120920), new FalconFPR( 0.009203754782059819315102378),
+                new FalconFPR( 0.999957644551963866333120920), new FalconFPR( 0.009203754782059819315102378),
+                new FalconFPR(-0.009203754782059819315102378), new FalconFPR( 0.999957644551963866333120920),
+                new FalconFPR( 0.700568793943248366792866380), new FalconFPR( 0.713584868780793592903125099),
+                new FalconFPR(-0.713584868780793592903125099), new FalconFPR( 0.700568793943248366792866380),
+                new FalconFPR( 0.920318276709110566440076541), new FalconFPR( 0.391170384302253888687512949),
+                new FalconFPR(-0.391170384302253888687512949), new FalconFPR( 0.920318276709110566440076541),
+                new FalconFPR( 0.374164062971457997104393020), new FalconFPR( 0.927362525650401087274536959),
+                new FalconFPR(-0.927362525650401087274536959), new FalconFPR( 0.374164062971457997104393020),
+                new FalconFPR( 0.978948175319062194715480124), new FalconFPR( 0.204108966092816874181696950),
+                new FalconFPR(-0.204108966092816874181696950), new FalconFPR( 0.978948175319062194715480124),
+                new FalconFPR( 0.547894059173100165608820571), new FalconFPR( 0.836547727223511984524285790),
+                new FalconFPR(-0.836547727223511984524285790), new FalconFPR( 0.547894059173100165608820571),
+                new FalconFPR( 0.826321062845663480311195452), new FalconFPR( 0.563199344013834115007363772),
+                new FalconFPR(-0.563199344013834115007363772), new FalconFPR( 0.826321062845663480311195452),
+                new FalconFPR( 0.186055151663446648105438304), new FalconFPR( 0.982539302287441255907040396),
+                new FalconFPR(-0.982539302287441255907040396), new FalconFPR( 0.186055151663446648105438304),
+                new FalconFPR( 0.994240449453187946358413442), new FalconFPR( 0.107172424956808849175529148),
+                new FalconFPR(-0.107172424956808849175529148), new FalconFPR( 0.994240449453187946358413442),
+                new FalconFPR( 0.627251815495144113509622565), new FalconFPR( 0.778816512381475953374724325),
+                new FalconFPR(-0.778816512381475953374724325), new FalconFPR( 0.627251815495144113509622565),
+                new FalconFPR( 0.877545290207261291668470750), new FalconFPR( 0.479493757660153026679839798),
+                new FalconFPR(-0.479493757660153026679839798), new FalconFPR( 0.877545290207261291668470750),
+                new FalconFPR( 0.281464937925757984095231007), new FalconFPR( 0.959571513081984528335528181),
+                new FalconFPR(-0.959571513081984528335528181), new FalconFPR( 0.281464937925757984095231007),
+                new FalconFPR( 0.954228095109105629780430732), new FalconFPR( 0.299079826308040476750336973),
+                new FalconFPR(-0.299079826308040476750336973), new FalconFPR( 0.954228095109105629780430732),
+                new FalconFPR( 0.463259783551860197390719637), new FalconFPR( 0.886222530148880631647990821),
+                new FalconFPR(-0.886222530148880631647990821), new FalconFPR( 0.463259783551860197390719637),
+                new FalconFPR( 0.767138911935820381181694573), new FalconFPR( 0.641481012808583151988739898),
+                new FalconFPR(-0.641481012808583151988739898), new FalconFPR( 0.767138911935820381181694573),
+                new FalconFPR( 0.088853552582524596561586535), new FalconFPR( 0.996044700901251989887944810),
+                new FalconFPR(-0.996044700901251989887944810), new FalconFPR( 0.088853552582524596561586535),
+                new FalconFPR( 0.998301544933892840738782163), new FalconFPR( 0.058258264500435759613979782),
+                new FalconFPR(-0.058258264500435759613979782), new FalconFPR( 0.998301544933892840738782163),
+                new FalconFPR( 0.664710978203344868130324985), new FalconFPR( 0.747100605980180144323078847),
+                new FalconFPR(-0.747100605980180144323078847), new FalconFPR( 0.664710978203344868130324985),
+                new FalconFPR( 0.900015892016160228714535267), new FalconFPR( 0.435857079922255491032544080),
+                new FalconFPR(-0.435857079922255491032544080), new FalconFPR( 0.900015892016160228714535267),
+                new FalconFPR( 0.328209843579092526107916817), new FalconFPR( 0.944604837261480265659265493),
+                new FalconFPR(-0.944604837261480265659265493), new FalconFPR( 0.328209843579092526107916817),
+                new FalconFPR( 0.967753837093475465243391912), new FalconFPR( 0.251897818154216950498106628),
+                new FalconFPR(-0.251897818154216950498106628), new FalconFPR( 0.967753837093475465243391912),
+                new FalconFPR( 0.506186645345155291048942344), new FalconFPR( 0.862423956111040538690933878),
+                new FalconFPR(-0.862423956111040538690933878), new FalconFPR( 0.506186645345155291048942344),
+                new FalconFPR( 0.797690840943391108362662755), new FalconFPR( 0.603066598540348201693430617),
+                new FalconFPR(-0.603066598540348201693430617), new FalconFPR( 0.797690840943391108362662755),
+                new FalconFPR( 0.137620121586486044948441663), new FalconFPR( 0.990485084256457037998682243),
+                new FalconFPR(-0.990485084256457037998682243), new FalconFPR( 0.137620121586486044948441663),
+                new FalconFPR( 0.987784141644572154230969032), new FalconFPR( 0.155828397654265235743101486),
+                new FalconFPR(-0.155828397654265235743101486), new FalconFPR( 0.987784141644572154230969032),
+                new FalconFPR( 0.588281548222645304786439813), new FalconFPR( 0.808656181588174991946968128),
+                new FalconFPR(-0.808656181588174991946968128), new FalconFPR( 0.588281548222645304786439813),
+                new FalconFPR( 0.852960604930363657746588082), new FalconFPR( 0.521975292937154342694258318),
+                new FalconFPR(-0.521975292937154342694258318), new FalconFPR( 0.852960604930363657746588082),
+                new FalconFPR( 0.234041958583543423191242045), new FalconFPR( 0.972226497078936305708321144),
+                new FalconFPR(-0.972226497078936305708321144), new FalconFPR( 0.234041958583543423191242045),
+                new FalconFPR( 0.938403534063108112192420774), new FalconFPR( 0.345541324963989065539191723),
+                new FalconFPR(-0.345541324963989065539191723), new FalconFPR( 0.938403534063108112192420774),
+                new FalconFPR( 0.419216888363223956433010020), new FalconFPR( 0.907886116487666212038681480),
+                new FalconFPR(-0.907886116487666212038681480), new FalconFPR( 0.419216888363223956433010020),
+                new FalconFPR( 0.734738878095963464563223604), new FalconFPR( 0.678350043129861486873655042),
+                new FalconFPR(-0.678350043129861486873655042), new FalconFPR( 0.734738878095963464563223604),
+                new FalconFPR( 0.039872927587739811128578738), new FalconFPR( 0.999204758618363895492950001),
+                new FalconFPR(-0.999204758618363895492950001), new FalconFPR( 0.039872927587739811128578738),
+                new FalconFPR( 0.999430604555461772019008327), new FalconFPR( 0.033741171851377584833716112),
+                new FalconFPR(-0.033741171851377584833716112), new FalconFPR( 0.999430604555461772019008327),
+                new FalconFPR( 0.682845546385248068164596123), new FalconFPR( 0.730562769227827561177758850),
+                new FalconFPR(-0.730562769227827561177758850), new FalconFPR( 0.682845546385248068164596123),
+                new FalconFPR( 0.910441292258067196934095369), new FalconFPR( 0.413638312238434547471944324),
+                new FalconFPR(-0.413638312238434547471944324), new FalconFPR( 0.910441292258067196934095369),
+                new FalconFPR( 0.351292756085567125601307623), new FalconFPR( 0.936265667170278246576310996),
+                new FalconFPR(-0.936265667170278246576310996), new FalconFPR( 0.351292756085567125601307623),
+                new FalconFPR( 0.973644249650811925318383912), new FalconFPR( 0.228072083170885739254457379),
+                new FalconFPR(-0.228072083170885739254457379), new FalconFPR( 0.973644249650811925318383912),
+                new FalconFPR( 0.527199134781901348464274575), new FalconFPR( 0.849741768000852489471268395),
+                new FalconFPR(-0.849741768000852489471268395), new FalconFPR( 0.527199134781901348464274575),
+                new FalconFPR( 0.812250586585203913049744181), new FalconFPR( 0.583308652937698294392830961),
+                new FalconFPR(-0.583308652937698294392830961), new FalconFPR( 0.812250586585203913049744181),
+                new FalconFPR( 0.161886393780111837641387995), new FalconFPR( 0.986809401814185476970235952),
+                new FalconFPR(-0.986809401814185476970235952), new FalconFPR( 0.161886393780111837641387995),
+                new FalconFPR( 0.991310859846115418957349799), new FalconFPR( 0.131540028702883111103387493),
+                new FalconFPR(-0.131540028702883111103387493), new FalconFPR( 0.991310859846115418957349799),
+                new FalconFPR( 0.607949784967773667243642671), new FalconFPR( 0.793975477554337164895083757),
+                new FalconFPR(-0.793975477554337164895083757), new FalconFPR( 0.607949784967773667243642671),
+                new FalconFPR( 0.865513624090569082825488358), new FalconFPR( 0.500885382611240786241285004),
+                new FalconFPR(-0.500885382611240786241285004), new FalconFPR( 0.865513624090569082825488358),
+                new FalconFPR( 0.257831102162159005614471295), new FalconFPR( 0.966190003445412555433832961),
+                new FalconFPR(-0.966190003445412555433832961), new FalconFPR( 0.257831102162159005614471295),
+                new FalconFPR( 0.946600913083283570044599823), new FalconFPR( 0.322407678801069848384807478),
+                new FalconFPR(-0.322407678801069848384807478), new FalconFPR( 0.946600913083283570044599823),
+                new FalconFPR( 0.441371268731716692879988968), new FalconFPR( 0.897324580705418281231391836),
+                new FalconFPR(-0.897324580705418281231391836), new FalconFPR( 0.441371268731716692879988968),
+                new FalconFPR( 0.751165131909686411205819422), new FalconFPR( 0.660114342067420478559490747),
+                new FalconFPR(-0.660114342067420478559490747), new FalconFPR( 0.751165131909686411205819422),
+                new FalconFPR( 0.064382630929857460819324537), new FalconFPR( 0.997925286198596012623025462),
+                new FalconFPR(-0.997925286198596012623025462), new FalconFPR( 0.064382630929857460819324537),
+                new FalconFPR( 0.996571145790554847093566910), new FalconFPR( 0.082740264549375693111987083),
+                new FalconFPR(-0.082740264549375693111987083), new FalconFPR( 0.996571145790554847093566910),
+                new FalconFPR( 0.646176012983316364832802220), new FalconFPR( 0.763188417263381271704838297),
+                new FalconFPR(-0.763188417263381271704838297), new FalconFPR( 0.646176012983316364832802220),
+                new FalconFPR( 0.889048355854664562540777729), new FalconFPR( 0.457813303598877221904961155),
+                new FalconFPR(-0.457813303598877221904961155), new FalconFPR( 0.889048355854664562540777729),
+                new FalconFPR( 0.304929229735402406490728633), new FalconFPR( 0.952375012719765858529893608),
+                new FalconFPR(-0.952375012719765858529893608), new FalconFPR( 0.304929229735402406490728633),
+                new FalconFPR( 0.961280485811320641748659653), new FalconFPR( 0.275571819310958163076425168),
+                new FalconFPR(-0.275571819310958163076425168), new FalconFPR( 0.961280485811320641748659653),
+                new FalconFPR( 0.484869248000791101822951699), new FalconFPR( 0.874586652278176112634431897),
+                new FalconFPR(-0.874586652278176112634431897), new FalconFPR( 0.484869248000791101822951699),
+                new FalconFPR( 0.782650596166575738458949301), new FalconFPR( 0.622461279374149972519166721),
+                new FalconFPR(-0.622461279374149972519166721), new FalconFPR( 0.782650596166575738458949301),
+                new FalconFPR( 0.113270952177564349018228733), new FalconFPR( 0.993564135520595333782021697),
+                new FalconFPR(-0.993564135520595333782021697), new FalconFPR( 0.113270952177564349018228733),
+                new FalconFPR( 0.983662419211730274396237776), new FalconFPR( 0.180022901405699522679906590),
+                new FalconFPR(-0.180022901405699522679906590), new FalconFPR( 0.983662419211730274396237776),
+                new FalconFPR( 0.568258952670131549790548489), new FalconFPR( 0.822849781375826332046780034),
+                new FalconFPR(-0.822849781375826332046780034), new FalconFPR( 0.568258952670131549790548489),
+                new FalconFPR( 0.839893794195999504583383987), new FalconFPR( 0.542750784864515906586768661),
+                new FalconFPR(-0.542750784864515906586768661), new FalconFPR( 0.839893794195999504583383987),
+                new FalconFPR( 0.210111836880469621717489972), new FalconFPR( 0.977677357824509979943404762),
+                new FalconFPR(-0.977677357824509979943404762), new FalconFPR( 0.210111836880469621717489972),
+                new FalconFPR( 0.929640895843181265457918066), new FalconFPR( 0.368466829953372331712746222),
+                new FalconFPR(-0.368466829953372331712746222), new FalconFPR( 0.929640895843181265457918066),
+                new FalconFPR( 0.396809987416710328595290911), new FalconFPR( 0.917900775621390457642276297),
+                new FalconFPR(-0.917900775621390457642276297), new FalconFPR( 0.396809987416710328595290911),
+                new FalconFPR( 0.717870045055731736211325329), new FalconFPR( 0.696177131491462944788582591),
+                new FalconFPR(-0.696177131491462944788582591), new FalconFPR( 0.717870045055731736211325329),
+                new FalconFPR( 0.015339206284988101044151868), new FalconFPR( 0.999882347454212525633049627),
+                new FalconFPR(-0.999882347454212525633049627), new FalconFPR( 0.015339206284988101044151868),
+                new FalconFPR( 0.999769405351215321657617036), new FalconFPR( 0.021474080275469507418374898),
+                new FalconFPR(-0.021474080275469507418374898), new FalconFPR( 0.999769405351215321657617036),
+                new FalconFPR( 0.691759258364157774906734132), new FalconFPR( 0.722128193929215321243607198),
+                new FalconFPR(-0.722128193929215321243607198), new FalconFPR( 0.691759258364157774906734132),
+                new FalconFPR( 0.915448716088267819566431292), new FalconFPR( 0.402434650859418441082533934),
+                new FalconFPR(-0.402434650859418441082533934), new FalconFPR( 0.915448716088267819566431292),
+                new FalconFPR( 0.362755724367397216204854462), new FalconFPR( 0.931884265581668106718557199),
+                new FalconFPR(-0.931884265581668106718557199), new FalconFPR( 0.362755724367397216204854462),
+                new FalconFPR( 0.976369731330021149312732194), new FalconFPR( 0.216106797076219509948385131),
+                new FalconFPR(-0.216106797076219509948385131), new FalconFPR( 0.976369731330021149312732194),
+                new FalconFPR( 0.537587076295645482502214932), new FalconFPR( 0.843208239641845437161743865),
+                new FalconFPR(-0.843208239641845437161743865), new FalconFPR( 0.537587076295645482502214932),
+                new FalconFPR( 0.819347520076796960824689637), new FalconFPR( 0.573297166698042212820171239),
+                new FalconFPR(-0.573297166698042212820171239), new FalconFPR( 0.819347520076796960824689637),
+                new FalconFPR( 0.173983873387463827950700807), new FalconFPR( 0.984748501801904218556553176),
+                new FalconFPR(-0.984748501801904218556553176), new FalconFPR( 0.173983873387463827950700807),
+                new FalconFPR( 0.992850414459865090793563344), new FalconFPR( 0.119365214810991364593637790),
+                new FalconFPR(-0.119365214810991364593637790), new FalconFPR( 0.992850414459865090793563344),
+                new FalconFPR( 0.617647307937803932403979402), new FalconFPR( 0.786455213599085757522319464),
+                new FalconFPR(-0.786455213599085757522319464), new FalconFPR( 0.617647307937803932403979402),
+                new FalconFPR( 0.871595086655951034842481435), new FalconFPR( 0.490226483288291154229598449),
+                new FalconFPR(-0.490226483288291154229598449), new FalconFPR( 0.871595086655951034842481435),
+                new FalconFPR( 0.269668325572915106525464462), new FalconFPR( 0.962953266873683886347921481),
+                new FalconFPR(-0.962953266873683886347921481), new FalconFPR( 0.269668325572915106525464462),
+                new FalconFPR( 0.950486073949481721759926101), new FalconFPR( 0.310767152749611495835997250),
+                new FalconFPR(-0.310767152749611495835997250), new FalconFPR( 0.950486073949481721759926101),
+                new FalconFPR( 0.452349587233770874133026703), new FalconFPR( 0.891840709392342727796478697),
+                new FalconFPR(-0.891840709392342727796478697), new FalconFPR( 0.452349587233770874133026703),
+                new FalconFPR( 0.759209188978388033485525443), new FalconFPR( 0.650846684996380915068975573),
+                new FalconFPR(-0.650846684996380915068975573), new FalconFPR( 0.759209188978388033485525443),
+                new FalconFPR( 0.076623861392031492278332463), new FalconFPR( 0.997060070339482978987989949),
+                new FalconFPR(-0.997060070339482978987989949), new FalconFPR( 0.076623861392031492278332463),
+                new FalconFPR( 0.997511456140303459699448390), new FalconFPR( 0.070504573389613863027351471),
+                new FalconFPR(-0.070504573389613863027351471), new FalconFPR( 0.997511456140303459699448390),
+                new FalconFPR( 0.655492852999615385312679701), new FalconFPR( 0.755201376896536527598710756),
+                new FalconFPR(-0.755201376896536527598710756), new FalconFPR( 0.655492852999615385312679701),
+                new FalconFPR( 0.894599485631382678433072126), new FalconFPR( 0.446868840162374195353044389),
+                new FalconFPR(-0.446868840162374195353044389), new FalconFPR( 0.894599485631382678433072126),
+                new FalconFPR( 0.316593375556165867243047035), new FalconFPR( 0.948561349915730288158494826),
+                new FalconFPR(-0.948561349915730288158494826), new FalconFPR( 0.316593375556165867243047035),
+                new FalconFPR( 0.964589793289812723836432159), new FalconFPR( 0.263754678974831383611349322),
+                new FalconFPR(-0.263754678974831383611349322), new FalconFPR( 0.964589793289812723836432159),
+                new FalconFPR( 0.495565261825772531150266670), new FalconFPR( 0.868570705971340895340449876),
+                new FalconFPR(-0.868570705971340895340449876), new FalconFPR( 0.495565261825772531150266670),
+                new FalconFPR( 0.790230221437310055030217152), new FalconFPR( 0.612810082429409703935211936),
+                new FalconFPR(-0.612810082429409703935211936), new FalconFPR( 0.790230221437310055030217152),
+                new FalconFPR( 0.125454983411546238542336453), new FalconFPR( 0.992099313142191757112085445),
+                new FalconFPR(-0.992099313142191757112085445), new FalconFPR( 0.125454983411546238542336453),
+                new FalconFPR( 0.985797509167567424700995000), new FalconFPR( 0.167938294974731178054745536),
+                new FalconFPR(-0.167938294974731178054745536), new FalconFPR( 0.985797509167567424700995000),
+                new FalconFPR( 0.578313796411655563342245019), new FalconFPR( 0.815814410806733789010772660),
+                new FalconFPR(-0.815814410806733789010772660), new FalconFPR( 0.578313796411655563342245019),
+                new FalconFPR( 0.846490938774052078300544488), new FalconFPR( 0.532403127877197971442805218),
+                new FalconFPR(-0.532403127877197971442805218), new FalconFPR( 0.846490938774052078300544488),
+                new FalconFPR( 0.222093620973203534094094721), new FalconFPR( 0.975025345066994146844913468),
+                new FalconFPR(-0.975025345066994146844913468), new FalconFPR( 0.222093620973203534094094721),
+                new FalconFPR( 0.934092550404258914729877883), new FalconFPR( 0.357030961233430032614954036),
+                new FalconFPR(-0.357030961233430032614954036), new FalconFPR( 0.934092550404258914729877883),
+                new FalconFPR( 0.408044162864978680820747499), new FalconFPR( 0.912962190428398164628018233),
+                new FalconFPR(-0.912962190428398164628018233), new FalconFPR( 0.408044162864978680820747499),
+                new FalconFPR( 0.726359155084345976817494315), new FalconFPR( 0.687315340891759108199186948),
+                new FalconFPR(-0.687315340891759108199186948), new FalconFPR( 0.726359155084345976817494315),
+                new FalconFPR( 0.027608145778965741612354872), new FalconFPR( 0.999618822495178597116830637),
+                new FalconFPR(-0.999618822495178597116830637), new FalconFPR( 0.027608145778965741612354872),
+                new FalconFPR( 0.998941293186856850633930266), new FalconFPR( 0.046003182130914628814301788),
+                new FalconFPR(-0.046003182130914628814301788), new FalconFPR( 0.998941293186856850633930266),
+                new FalconFPR( 0.673829000378756060917568372), new FalconFPR( 0.738887324460615147933116508),
+                new FalconFPR(-0.738887324460615147933116508), new FalconFPR( 0.673829000378756060917568372),
+                new FalconFPR( 0.905296759318118774354048329), new FalconFPR( 0.424779681209108833357226189),
+                new FalconFPR(-0.424779681209108833357226189), new FalconFPR( 0.905296759318118774354048329),
+                new FalconFPR( 0.339776884406826857828825803), new FalconFPR( 0.940506070593268323787291309),
+                new FalconFPR(-0.940506070593268323787291309), new FalconFPR( 0.339776884406826857828825803),
+                new FalconFPR( 0.970772140728950302138169611), new FalconFPR( 0.240003022448741486568922365),
+                new FalconFPR(-0.240003022448741486568922365), new FalconFPR( 0.970772140728950302138169611),
+                new FalconFPR( 0.516731799017649881508753876), new FalconFPR( 0.856147328375194481019630732),
+                new FalconFPR(-0.856147328375194481019630732), new FalconFPR( 0.516731799017649881508753876),
+                new FalconFPR( 0.805031331142963597922659282), new FalconFPR( 0.593232295039799808047809426),
+                new FalconFPR(-0.593232295039799808047809426), new FalconFPR( 0.805031331142963597922659282),
+                new FalconFPR( 0.149764534677321517229695737), new FalconFPR( 0.988721691960323767604516485),
+                new FalconFPR(-0.988721691960323767604516485), new FalconFPR( 0.149764534677321517229695737),
+                new FalconFPR( 0.989622017463200834623694454), new FalconFPR( 0.143695033150294454819773349),
+                new FalconFPR(-0.143695033150294454819773349), new FalconFPR( 0.989622017463200834623694454),
+                new FalconFPR( 0.598160706996342311724958652), new FalconFPR( 0.801376171723140219430247777),
+                new FalconFPR(-0.801376171723140219430247777), new FalconFPR( 0.598160706996342311724958652),
+                new FalconFPR( 0.859301818357008404783582139), new FalconFPR( 0.511468850437970399504391001),
+                new FalconFPR(-0.511468850437970399504391001), new FalconFPR( 0.859301818357008404783582139),
+                new FalconFPR( 0.245955050335794611599924709), new FalconFPR( 0.969281235356548486048290738),
+                new FalconFPR(-0.969281235356548486048290738), new FalconFPR( 0.245955050335794611599924709),
+                new FalconFPR( 0.942573197601446879280758735), new FalconFPR( 0.333999651442009404650865481),
+                new FalconFPR(-0.333999651442009404650865481), new FalconFPR( 0.942573197601446879280758735),
+                new FalconFPR( 0.430326481340082633908199031), new FalconFPR( 0.902673318237258806751502391),
+                new FalconFPR(-0.902673318237258806751502391), new FalconFPR( 0.430326481340082633908199031),
+                new FalconFPR( 0.743007952135121693517362293), new FalconFPR( 0.669282588346636065720696366),
+                new FalconFPR(-0.669282588346636065720696366), new FalconFPR( 0.743007952135121693517362293),
+                new FalconFPR( 0.052131704680283321236358216), new FalconFPR( 0.998640218180265222418199049),
+                new FalconFPR(-0.998640218180265222418199049), new FalconFPR( 0.052131704680283321236358216),
+                new FalconFPR( 0.995480755491926941769171600), new FalconFPR( 0.094963495329638998938034312),
+                new FalconFPR(-0.094963495329638998938034312), new FalconFPR( 0.995480755491926941769171600),
+                new FalconFPR( 0.636761861236284230413943435), new FalconFPR( 0.771060524261813773200605759),
+                new FalconFPR(-0.771060524261813773200605759), new FalconFPR( 0.636761861236284230413943435),
+                new FalconFPR( 0.883363338665731594736308015), new FalconFPR( 0.468688822035827933697617870),
+                new FalconFPR(-0.468688822035827933697617870), new FalconFPR( 0.883363338665731594736308015),
+                new FalconFPR( 0.293219162694258650606608599), new FalconFPR( 0.956045251349996443270479823),
+                new FalconFPR(-0.956045251349996443270479823), new FalconFPR( 0.293219162694258650606608599),
+                new FalconFPR( 0.957826413027532890321037029), new FalconFPR( 0.287347459544729526477331841),
+                new FalconFPR(-0.287347459544729526477331841), new FalconFPR( 0.957826413027532890321037029),
+                new FalconFPR( 0.474100214650550014398580015), new FalconFPR( 0.880470889052160770806542929),
+                new FalconFPR(-0.880470889052160770806542929), new FalconFPR( 0.474100214650550014398580015),
+                new FalconFPR( 0.774953106594873878359129282), new FalconFPR( 0.632018735939809021909403706),
+                new FalconFPR(-0.632018735939809021909403706), new FalconFPR( 0.774953106594873878359129282),
+                new FalconFPR( 0.101069862754827824987887585), new FalconFPR( 0.994879330794805620591166107),
+                new FalconFPR(-0.994879330794805620591166107), new FalconFPR( 0.101069862754827824987887585),
+                new FalconFPR( 0.981379193313754574318224190), new FalconFPR( 0.192080397049892441679288205),
+                new FalconFPR(-0.192080397049892441679288205), new FalconFPR( 0.981379193313754574318224190),
+                new FalconFPR( 0.558118531220556115693702964), new FalconFPR( 0.829761233794523042469023765),
+                new FalconFPR(-0.829761233794523042469023765), new FalconFPR( 0.558118531220556115693702964),
+                new FalconFPR( 0.833170164701913186439915922), new FalconFPR( 0.553016705580027531764226988),
+                new FalconFPR(-0.553016705580027531764226988), new FalconFPR( 0.833170164701913186439915922),
+                new FalconFPR( 0.198098410717953586179324918), new FalconFPR( 0.980182135968117392690210009),
+                new FalconFPR(-0.980182135968117392690210009), new FalconFPR( 0.198098410717953586179324918),
+                new FalconFPR( 0.925049240782677590302371869), new FalconFPR( 0.379847208924051170576281147),
+                new FalconFPR(-0.379847208924051170576281147), new FalconFPR( 0.925049240782677590302371869),
+                new FalconFPR( 0.385516053843918864075607949), new FalconFPR( 0.922701128333878570437264227),
+                new FalconFPR(-0.922701128333878570437264227), new FalconFPR( 0.385516053843918864075607949),
+                new FalconFPR( 0.709272826438865651316533772), new FalconFPR( 0.704934080375904908852523758),
+                new FalconFPR(-0.704934080375904908852523758), new FalconFPR( 0.709272826438865651316533772),
+                new FalconFPR( 0.003067956762965976270145365), new FalconFPR( 0.999995293809576171511580126),
+                new FalconFPR(-0.999995293809576171511580126), new FalconFPR( 0.003067956762965976270145365)
+        };
+
+        internal FalconFPR[] fpr_p2_tab = {
+                new FalconFPR( 2.00000000000 ),
+                new FalconFPR( 1.00000000000 ),
+                new FalconFPR( 0.50000000000 ),
+                new FalconFPR( 0.25000000000 ),
+                new FalconFPR( 0.12500000000 ),
+                new FalconFPR( 0.06250000000 ),
+                new FalconFPR( 0.03125000000 ),
+                new FalconFPR( 0.01562500000 ),
+                new FalconFPR( 0.00781250000 ),
+                new FalconFPR( 0.00390625000 ),
+                new FalconFPR( 0.00195312500 )
+        };
+        internal FalconFPR fpr_log2 = new FalconFPR(0.69314718055994530941723212146);
+        internal FalconFPR fpr_inv_log2 = new FalconFPR(1.4426950408889634073599246810);
+        internal FalconFPR fpr_bnorm_max = new FalconFPR(16822.4121);
+        internal FalconFPR fpr_zero = new FalconFPR(0.0);
+        internal FalconFPR fpr_one = new FalconFPR(1.0);
+        internal FalconFPR fpr_two = new FalconFPR(2.0);
+        internal FalconFPR fpr_onehalf = new FalconFPR(0.5);
+        internal FalconFPR fpr_invsqrt2 = new FalconFPR(0.707106781186547524400844362105);
+        internal FalconFPR fpr_invsqrt8 = new FalconFPR(0.353553390593273762200422181052);
+        internal FalconFPR fpr_ptwo31 = new FalconFPR(2147483648.0);
+        internal FalconFPR fpr_ptwo31m1 = new FalconFPR(2147483647.0);
+        internal FalconFPR fpr_mtwo31m1 = new FalconFPR(-2147483647.0);
+        internal FalconFPR fpr_ptwo63m1 = new FalconFPR(9223372036854775807.0);
+        internal FalconFPR fpr_mtwo63m1 = new FalconFPR(-9223372036854775807.0);
+        internal FalconFPR fpr_ptwo63 = new FalconFPR(9223372036854775808.0);
+        internal FalconFPR fpr_q = new FalconFPR(12289.0);
+        internal FalconFPR fpr_inverse_of_q = new FalconFPR(1.0 / 12289.0);
+        internal FalconFPR fpr_inv_2sqrsigma0 = new FalconFPR(0.150865048875372721532312163019);
+        internal FalconFPR[] fpr_inv_sigma = {
+            new FalconFPR( 0.0 ), /* unused */
+            new FalconFPR( 0.0069054793295940891952143765991630516 ),
+            new FalconFPR( 0.0068102267767177975961393730687908629 ),
+            new FalconFPR( 0.0067188101910722710707826117910434131 ),
+            new FalconFPR( 0.0065883354370073665545865037227681924 ),
+            new FalconFPR( 0.0064651781207602900738053897763485516 ),
+            new FalconFPR( 0.0063486788828078995327741182928037856 ),
+            new FalconFPR( 0.0062382586529084374473367528433697537 ),
+            new FalconFPR( 0.0061334065020930261548984001431770281 ),
+            new FalconFPR( 0.0060336696681577241031668062510953022 ),
+            new FalconFPR( 0.0059386453095331159950250124336477482 )
+        };
+        internal FalconFPR[] fpr_sigma_min = {
+            new FalconFPR( 0.0 ), /* unused */
+            new FalconFPR( 1.1165085072329102588881898380334015 ),
+            new FalconFPR( 1.1321247692325272405718031785357108 ),
+            new FalconFPR( 1.1475285353733668684571123112513188 ),
+            new FalconFPR( 1.1702540788534828939713084716509250 ),
+            new FalconFPR( 1.1925466358390344011122170489094133 ),
+            new FalconFPR( 1.2144300507766139921088487776957699 ),
+            new FalconFPR( 1.2359260567719808790104525941706723 ),
+            new FalconFPR( 1.2570545284063214162779743112075080 ),
+            new FalconFPR( 1.2778336969128335860256340575729042 ),
+            new FalconFPR( 1.2982803343442918539708792538826807 )
+        };
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconCodec.cs b/crypto/src/pqc/crypto/falcon/FalconCodec.cs
new file mode 100644
index 000000000..062e006e4
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconCodec.cs
@@ -0,0 +1,576 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconCodec
+    {
+        
+        internal FalconCodec() {
+            
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+
+        internal int modq_encode(
+            byte[] outarrsrc, int outarr, int max_out_len,
+            ushort[] xsrc, int x, uint logn)
+        {
+            int n, out_len, u;
+            int buf;
+            uint acc;
+            int acc_len;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                if (xsrc[x+u] >= 12289) {
+                    return 0;
+                }
+            }
+            out_len = ((n * 14) + 7) >> 3;
+            if (outarrsrc == null) {
+                return out_len;
+            }
+            if (out_len > max_out_len) {
+                return 0;
+            }
+            buf = outarr;
+            acc = 0;
+            acc_len = 0;
+            for (u = 0; u < n; u ++) {
+                acc = (acc << 14) | xsrc[x+u];
+                acc_len += 14;
+                while (acc_len >= 8) {
+                    acc_len -= 8;
+                    outarrsrc[buf ++] = (byte)(acc >> acc_len);
+                }
+            }
+            if (acc_len > 0) {
+                outarrsrc[buf] = (byte)(acc << (8 - acc_len));
+            }
+            return out_len;
+        }
+
+        internal int modq_decode(
+            ushort[] xsrc, int x, uint logn,
+            byte[] inarrsrc, int inarr, int max_in_len)
+        {
+            int n, in_len, u;
+            int buf;
+            uint acc;
+            int acc_len;
+
+            n = (int)1 << (int)logn;
+            in_len = ((n * 14) + 7) >> 3;
+            if (in_len > max_in_len) {
+                return 0;
+            }
+            buf = inarr;
+            acc = 0;
+            acc_len = 0;
+            u = 0;
+            while (u < n) {
+                acc = (acc << 8) | (inarrsrc[buf ++]);
+                acc_len += 8;
+                if (acc_len >= 14) {
+                    uint w;
+
+                    acc_len -= 14;
+                    w = (acc >> acc_len) & 0x3FFF;
+                    if (w >= 12289) {
+                        return 0;
+                    }
+                    xsrc[x + u] = (ushort)w;
+                    u++;
+                }
+            }
+            if ((acc & (((uint)1 << acc_len) - 1)) != 0) {
+                return 0;
+            }
+            return in_len;
+        }
+
+        internal int trim_i16_encode(
+            byte[] outarrsrc, int outarr, int max_out_len,
+            short[] xsrc, int x, uint logn, uint bits)
+        {
+            int n, u, out_len;
+            int minv, maxv;
+            int buf;
+            uint acc, mask;
+            uint acc_len;
+
+            n = (int)1 << (int)logn;
+            maxv = (1 << (int)(bits - 1)) - 1;
+            minv = -maxv;
+            for (u = 0; u < n; u ++) {
+                if (xsrc[x+u] < minv || xsrc[x+u] > maxv) {
+                    return 0;
+                }
+            }
+            out_len = (int)((n * bits) + 7) >> 3;
+            if (outarrsrc == null) {
+                return out_len;
+            }
+            if (out_len > max_out_len) {
+                return 0;
+            }
+            buf = outarr;
+            acc = 0;
+            acc_len = 0;
+            mask = ((uint)1 << (int)bits) - 1;
+            for (u = 0; u < n; u ++) {
+                acc = (acc << (int)bits) | ((ushort)xsrc[x+u] & mask);
+                acc_len += bits;
+                while (acc_len >= 8) {
+                    acc_len -= 8;
+                    outarrsrc[buf ++] = (byte)(acc >> (int)acc_len);
+                }
+            }
+            if (acc_len > 0) {
+                outarrsrc[buf ++] = (byte)(acc << (int)(8 - acc_len));
+            }
+            return out_len;
+        }
+
+        internal int trim_i16_decode(
+            short[] xsrc, int x, uint logn, uint bits,
+            byte[] inarrsrc, int inarr, int max_in_len)
+        {
+            int n, in_len;
+            int buf;
+            int u;
+            uint acc, mask1, mask2;
+            uint acc_len;
+
+            n = (int)1 << (int)logn;
+            in_len = (int)((n * bits) + 7) >> 3;
+            if (in_len > max_in_len) {
+                return 0;
+            }
+            buf = inarr;
+            u = 0;
+            acc = 0;
+            acc_len = 0;
+            mask1 = ((uint)1 << (int)bits) - 1;
+            mask2 = (uint)1 << (int)(bits - 1);
+            while (u < n) {
+                acc = (acc << 8) | inarrsrc[buf ++];
+                acc_len += 8;
+                while (acc_len >= bits && u < n) {
+                    uint w;
+
+                    acc_len -= bits;
+                    w = (acc >> (int)acc_len) & mask1;
+                    w = (uint)(w | -(w & mask2));
+                    w |= (uint)(-(w & mask2));
+                    if (w == -mask2) {
+                        /*
+                        * The -2^(bits-1) value is forbidden.
+                        */
+                        return 0;
+                    }
+                    w |= (uint)(-(w & mask2));
+                    //xsrc[x + u] = (short)*(int *)&w;
+                    xsrc[x + u] = (short)(int)w;
+                    u++;
+                }
+            }
+            if ((acc & (((uint)1 << (int)acc_len) - 1)) != 0) {
+                /*
+                * Extra bits in the last byte must be zero.
+                */
+                return 0;
+            }
+            return in_len;
+        }
+
+        internal int trim_i8_encode(
+            byte[] outarrsrc, int outarr, int max_out_len,
+            sbyte[] xsrc, int x, uint logn, uint bits)
+        {
+            int n, u, out_len;
+            int minv, maxv;
+            int buf;
+            uint acc, mask;
+            uint acc_len;
+
+            n = (int)1 << (int)logn;
+            maxv = (1 << (int)(bits - 1)) - 1;
+            minv = -maxv;
+            for (u = 0; u < n; u ++) {
+                if (xsrc[x+u] < minv || xsrc[x+u] > maxv) {
+                    return 0;
+                }
+            }
+            out_len = (int)((n * bits) + 7) >> 3;
+            if (outarrsrc == null) {
+                return out_len;
+            }
+            if (out_len > max_out_len) {
+                return 0;
+            }
+            buf = outarr;
+            acc = 0;
+            acc_len = 0;
+            mask = ((uint)1 << (int)bits) - 1;
+            for (u = 0; u < n; u ++) {
+                acc = (acc << (int)bits) | ((byte)xsrc[x+u] & mask);
+                acc_len += bits;
+                while (acc_len >= 8) {
+                    acc_len -= 8;
+                    outarrsrc[buf ++] = (byte)(acc >> (int)acc_len);
+                }
+            }
+            if (acc_len > 0) {
+                outarrsrc[buf ++] = (byte)(acc << (int)(8 - acc_len));
+            }
+            return out_len;
+        }
+
+        internal int trim_i8_decode(
+            sbyte[] xsrc, int x, uint logn, uint bits,
+            byte[] inarrsrc, int inarr, int max_in_len)
+        {
+            int n, in_len;
+            int buf;
+            int u;
+            uint acc, mask1, mask2;
+            uint acc_len;
+
+            n = (int)1 << (int)logn;
+            in_len = (int)((n * bits) + 7) >> 3;
+            if (in_len > max_in_len) {
+                return 0;
+            }
+            buf = inarr;
+            u = 0;
+            acc = 0;
+            acc_len = 0;
+            mask1 = ((uint)1 << (int)bits) - 1;
+            mask2 = (uint)1 << (int)(bits - 1);
+            while (u < n) {
+                acc = (acc << 8) | inarrsrc[buf ++];
+                acc_len += 8;
+                while (acc_len >= bits && u < n) {
+                    uint w;
+
+                    acc_len -= bits;
+                    w = (acc >> (int)acc_len) & mask1;
+                    w |= (uint)(-(w & mask2));
+                    if (w == -mask2) {
+                        /*
+                        * The -2^(bits-1) value is forbidden.
+                        */
+                        return 0;
+                    }
+                    //xsrc[x + u] = (sbyte)*(int *)&w;
+                    xsrc[x + u] = (sbyte)(int)w;
+                    u++;
+                }
+            }
+            if ((acc & (((uint)1 << (int)acc_len) - 1)) != 0) {
+                /*
+                * Extra bits in the last byte must be zero.
+                */
+                return 0;
+            }
+            return in_len;
+        }
+
+        internal int comp_encode(
+            byte[] outarrsrc, int outarr, int max_out_len,
+            short[] xsrc, int x, uint logn)
+        {
+            int buf;
+            int n, u, v;
+            uint acc;
+            uint acc_len;
+
+            n = (int)1 << (int)logn;
+            buf = outarr;
+
+            /*
+            * Make sure that all values are within the -2047..+2047 range.
+            */
+            for (u = 0; u < n; u ++) {
+                if (xsrc[x+u] < -2047 || xsrc[x+u] > +2047) {
+                    return 0;
+                }
+            }
+
+            acc = 0;
+            acc_len = 0;
+            v = 0;
+            for (u = 0; u < n; u ++) {
+                int t;
+                uint w;
+
+                /*
+                * Get sign and absolute value of next integer; push the
+                * sign bit.
+                */
+                acc <<= 1;
+                t = xsrc[x+u];
+                if (t < 0) {
+                    t = -t;
+                    acc |= 1;
+                }
+                w = (uint)t;
+
+                /*
+                * Push the low 7 bits of the absolute value.
+                */
+                acc <<= 7;
+                acc |= w & 127u;
+                w >>= 7;
+
+                /*
+                * We pushed exactly 8 bits.
+                */
+                acc_len += 8;
+
+                /*
+                * Push as many zeros as necessary, then a one. Since the
+                * absolute value is at most 2047, w can only range up to
+                * 15 at this point, thus we will add at most 16 bits
+                * here. With the 8 bits above and possibly up to 7 bits
+                * from previous iterations, we may go up to 31 bits, which
+                * will fit in the accumulator, which is an uint.
+                */
+                acc <<= (int)(w + 1);
+                acc |= 1;
+                acc_len += w + 1;
+
+                /*
+                * Produce all full bytes.
+                */
+                while (acc_len >= 8) {
+                    acc_len -= 8;
+                    if (outarrsrc != null) {
+                        if (v >= max_out_len) {
+                            return 0;
+                        }
+                        outarrsrc[buf+v] = (byte)(acc >> (int)acc_len);
+                    }
+                    v ++;
+                }
+            }
+
+            /*
+            * Flush remaining bits (if any).
+            */
+            if (acc_len > 0) {
+                if (outarrsrc != null) {
+                    if (v >= max_out_len) {
+                        return 0;
+                    }
+                    outarrsrc[buf+v] = (byte)(acc << (int)(8 - acc_len));
+                }
+                v ++;
+            }
+
+            return v;
+        }
+
+        internal int comp_decode(
+            short[] xsrc, int x, uint logn,
+            byte[] inarrsrc, int inarr, int max_in_len)
+        {
+            int buf;
+            int n, u, v;
+            uint acc;
+            uint acc_len;
+
+            n = (int)1 << (int)logn;
+            buf = inarr;
+            acc = 0;
+            acc_len = 0;
+            v = 0;
+            for (u = 0; u < n; u ++) {
+                uint b, s, m;
+
+                /*
+                * Get next eight bits: sign and low seven bits of the
+                * absolute value.
+                */
+                if (v >= max_in_len) {
+                    return 0;
+                }
+                acc = (acc << 8) | (uint)inarrsrc[buf + v];
+                v++;
+                b = acc >> (int)acc_len;
+                s = b & 128;
+                m = b & 127;
+
+                /*
+                * Get next bits until a 1 is reached.
+                */
+                for (;;) {
+                    if (acc_len == 0) {
+                        if (v >= max_in_len) {
+                            return 0;
+                        }
+                        acc = (acc << 8) | (uint)inarrsrc[buf + v];
+                        v++;
+                        acc_len = 8;
+                    }
+                    acc_len --;
+                    if (((acc >> (int)acc_len) & 1) != 0) {
+                        break;
+                    }
+                    m += 128;
+                    if (m > 2047) {
+                        return 0;
+                    }
+                }
+
+                /*
+                * "-0" is forbidden.
+                */
+                if (s != 0 && m == 0) {
+                    return 0;
+                }
+
+                xsrc[x+u] = (short)(s != 0 ? -(int)m : (int)m);
+            }
+
+            /*
+            * Unused bits in the last byte must be zero.
+            */
+            if ((acc & ((1u << (int)acc_len) - 1u)) != 0) {
+                return 0;
+            }
+
+            return v;
+        }
+
+        /*
+        * Key elements and signatures are polynomials with small integer
+        * coefficients. Here are some statistics gathered over many
+        * generated key pairs (10000 or more for each degree):
+        *
+        *   log(n)     n   max(f,g)   std(f,g)   max(F,G)   std(F,G)
+        *      1       2     129       56.31       143       60.02
+        *      2       4     123       40.93       160       46.52
+        *      3       8      97       28.97       159       38.01
+        *      4      16     100       21.48       154       32.50
+        *      5      32      71       15.41       151       29.36
+        *      6      64      59       11.07       138       27.77
+        *      7     128      39        7.91       144       27.00
+        *      8     256      32        5.63       148       26.61
+        *      9     512      22        4.00       137       26.46
+        *     10    1024      15        2.84       146       26.41
+        *
+        * We want a compact storage format for private key, and, as part of
+        * key generation, we are allowed to reject some keys which would
+        * otherwise be fine (this does not induce any noticeable vulnerability
+        * as long as we reject only a small proportion of possible keys).
+        * Hence, we enforce at key generation time maximum values for the
+        * elements of f, g, F and G, so that their encoding can be expressed
+        * in fixed-width values. Limits have been chosen so that generated
+        * keys are almost always within bounds, thus not impacting neither
+        * security or performance.
+        *
+        * IMPORTANT: the code assumes that all coefficients of f, g, F and G
+        * ultimately fit in the -127..+127 range. Thus, none of the elements
+        * of max_fg_bits[] and max_FG_bits[] shall be greater than 8.
+        */
+
+        internal byte[] max_fg_bits = {
+            0, /* unused */
+            8,
+            8,
+            8,
+            8,
+            8,
+            7,
+            7,
+            6,
+            6,
+            5
+        };
+
+        internal byte[] max_FG_bits = {
+            0, /* unused */
+            8,
+            8,
+            8,
+            8,
+            8,
+            8,
+            8,
+            8,
+            8,
+            8
+        };
+
+        /*
+        * When generating a new key pair, we can always reject keys which
+        * feature an abnormally large coefficient. This can also be done for
+        * signatures, albeit with some care: in case the signature process is
+        * used in a derandomized setup (explicitly seeded with the message and
+        * private key), we have to follow the specification faithfully, and the
+        * specification only enforces a limit on the L2 norm of the signature
+        * vector. The limit on the L2 norm implies that the absolute value of
+        * a coefficient of the signature cannot be more than the following:
+        *
+        *   log(n)     n   max sig coeff (theoretical)
+        *      1       2       412
+        *      2       4       583
+        *      3       8       824
+        *      4      16      1166
+        *      5      32      1649
+        *      6      64      2332
+        *      7     128      3299
+        *      8     256      4665
+        *      9     512      6598
+        *     10    1024      9331
+        *
+        * However, the largest observed signature coefficients during our
+        * experiments was 1077 (in absolute value), hence we can assume that,
+        * with overwhelming probability, signature coefficients will fit
+        * in -2047..2047, i.e. 12 bits.
+        */
+
+        internal byte[] max_sig_bits = {
+            0, /* unused */
+            10,
+            11,
+            11,
+            12,
+            12,
+            12,
+            12,
+            12,
+            12,
+            12
+        };
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconCommon.cs b/crypto/src/pqc/crypto/falcon/FalconCommon.cs
new file mode 100644
index 000000000..e92237936
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconCommon.cs
@@ -0,0 +1,304 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconCommon
+    {
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+        internal void hash_to_point_vartime(
+            SHAKE256 sc,
+            ushort[] xsrc, int x, uint logn)
+        {
+            /*
+            * This is the straightforward per-the-spec implementation. It
+            * is not constant-time, thus it might reveal information on the
+            * plaintext (at least, enough to check the plaintext against a
+            * list of potential plaintexts) in a scenario where the
+            * attacker does not have access to the signature value or to
+            * the public key, but knows the nonce (without knowledge of the
+            * nonce, the hashed output cannot be matched against potential
+            * plaintexts).
+            */
+            int n;
+
+            n = (int)1 << (int)logn;
+            while (n > 0) {
+                byte[] buf = new byte[2];
+                uint w;
+                sc.i_shake256_extract(buf, 0, 2);
+                // inner_shake256_extract(sc, (void *)buf, sizeof buf);
+                w = ((uint)buf[0] << 8) | (uint)buf[1];
+                if (w < 61445) {
+                    while (w >= 12289) {
+                        w -= 12289;
+                    }
+                    xsrc[x ++] = (ushort)w;
+                    n --;
+                }
+            }
+        }
+
+        // void hash_to_point_ct(
+        //     SHAKE256 sc,
+        //     ushort[] xsrc, int x, uint logn, byte *tmp)
+        // {
+        //     /*
+        //     * Each 16-bit sample is a value in 0..65535. The value is
+        //     * kept if it falls in 0..61444 (because 61445 = 5*12289)
+        //     * and rejected otherwise; thus, each sample has probability
+        //     * about 0.93758 of being selected.
+        //     *
+        //     * We want to oversample enough to be sure that we will
+        //     * have enough values with probability at least 1 - 2^(-256).
+        //     * Depending on degree N, this leads to the following
+        //     * required oversampling:
+        //     *
+        //     *   logn     n  oversampling
+        //     *     1      2     65
+        //     *     2      4     67
+        //     *     3      8     71
+        //     *     4     16     77
+        //     *     5     32     86
+        //     *     6     64    100
+        //     *     7    128    122
+        //     *     8    256    154
+        //     *     9    512    205
+        //     *    10   1024    287
+        //     *
+        //     * If logn >= 7, then the provided temporary buffer is large
+        //     * enough. Otherwise, we use a stack buffer of 63 entries
+        //     * (i.e. 126 bytes) for the values that do not fit in tmp[].
+        //     */
+
+        //     const ushort[] overtab = {
+        //         0, /* unused */
+        //         65,
+        //         67,
+        //         71,
+        //         77,
+        //         86,
+        //         100,
+        //         122,
+        //         154,
+        //         205,
+        //         287
+        //     };
+
+        //     uint n, n2, u, m, p, over;
+        //     int tt1;
+        //     ushort[] tt2 = new ushort[63];
+
+        //     /*
+        //     * We first generate m 16-bit value. Values 0..n-1 go to x[].
+        //     * Values n..2*n-1 go to tt1[]. Values 2*n and later go to tt2[].
+        //     * We also reduce modulo q the values; rejected values are set
+        //     * to 0xFFFF.
+        //     */
+        //     n = 1U << logn;
+        //     n2 = n << 1;
+        //     over = overtab[logn];
+        //     m = n + over;
+        //     tt1 = tmp;
+        //     for (u = 0; u < m; u ++) {
+        //         byte[] buf = new byte[2];
+        //         uint w, wr;
+
+        //         // inner_shake256_extract(sc, buf, sizeof buf);
+        //         sc.i_shake256_extract(buf, 2);
+        //         w = ((uint)buf[0] << 8) | (uint)buf[1];
+        //         wr = w - ((uint)24578 & (((w - 24578) >> 31) - 1));
+        //         wr = wr - ((uint)24578 & (((wr - 24578) >> 31) - 1));
+        //         wr = wr - ((uint)12289 & (((wr - 12289) >> 31) - 1));
+        //         wr |= ((w - 61445) >> 31) - 1;
+        //         if (u < n) {
+        //             x[u] = (ushort)wr;
+        //         } else if (u < n2) {
+        //             tt1[u - n] = (ushort)wr;
+        //         } else {
+        //             tt2[u - n2] = (ushort)wr;
+        //         }
+        //     }
+
+        //     /*
+        //     * Now we must "squeeze out" the invalid values. We do this in
+        //     * a logarithmic sequence of passes; each pass computes where a
+        //     * value should go, and moves it down by 'p' slots if necessary,
+        //     * where 'p' uses an increasing powers-of-two scale. It can be
+        //     * shown that in all cases where the loop decides that a value
+        //     * has to be moved down by p slots, the destination slot is
+        //     * "free" (i.e. contains an invalid value).
+        //     */
+        //     for (p = 1; p <= over; p <<= 1) {
+        //         uint v;
+
+        //         /*
+        //         * In the loop below:
+        //         *
+        //         *   - v contains the index of the final destination of
+        //         *     the value; it is recomputed dynamically based on
+        //         *     whether values are valid or not.
+        //         *
+        //         *   - u is the index of the value we consider ("source");
+        //         *     its address is s.
+        //         *
+        //         *   - The loop may swap the value with the one at index
+        //         *     u-p. The address of the swap destination is d.
+        //         */
+        //         v = 0;
+        //         for (u = 0; u < m; u ++) {
+        //             ushort *s;
+        //             ushort *d;
+        //             uint j, sv, dv, mk;
+
+        //             if (u < n) {
+        //                 s = &x[u];
+        //             } else if (u < n2) {
+        //                 s = &tt1[u - n];
+        //             } else {
+        //                 s = &tt2[u - n2];
+        //             }
+        //             sv = *s;
+
+        //             /*
+        //             * The value in sv should ultimately go to
+        //             * address v, i.e. jump back by u-v slots.
+        //             */
+        //             j = u - v;
+
+        //             /*
+        //             * We increment v for the next iteration, but
+        //             * only if the source value is valid. The mask
+        //             * 'mk' is -1 if the value is valid, 0 otherwise,
+        //             * so we _subtract_ mk.
+        //             */
+        //             mk = (sv >> 15) - 1U;
+        //             v -= mk;
+
+        //             /*
+        //             * In this loop we consider jumps by p slots; if
+        //             * u < p then there is nothing more to do.
+        //             */
+        //             if (u < p) {
+        //                 continue;
+        //             }
+
+        //             /*
+        //             * Destination for the swap: value at address u-p.
+        //             */
+        //             if ((u - p) < n) {
+        //                 d = &x[u - p];
+        //             } else if ((u - p) < n2) {
+        //                 d = &tt1[(u - p) - n];
+        //             } else {
+        //                 d = &tt2[(u - p) - n2];
+        //             }
+        //             dv = *d;
+
+        //             /*
+        //             * The swap should be performed only if the source
+        //             * is valid AND the jump j has its 'p' bit set.
+        //             */
+        //             mk &= -(((j & p) + 0x1FF) >> 9);
+
+        //             *s = (ushort)(sv ^ (mk & (sv ^ dv)));
+        //             *d = (ushort)(dv ^ (mk & (sv ^ dv)));
+        //         }
+        //     }
+        // }
+
+        /*
+        * Acceptance bound for the (squared) l2-norm of the signature depends
+        * on the degree. This array is indexed by logn (1 to 10). These bounds
+        * are _inclusive_ (they are equal to floor(beta^2)).
+        */
+        internal uint[] l2bound = {
+            0,    /* unused */
+            101498,
+            208714,
+            428865,
+            892039,
+            1852696,
+            3842630,
+            7959734,
+            16468416,
+            34034726,
+            70265242
+        };
+
+        internal bool is_short(
+            short[] s1src, int s1, short[] s2src, int s2, uint logn)
+        {
+            /*
+            * We use the l2-norm. Code below uses only 32-bit operations to
+            * compute the square of the norm with saturation to 2^32-1 if
+            * the value exceeds 2^31-1.
+            */
+            int n, u;
+            uint s, ng;
+
+            n = (int)1 << (int)logn;
+            s = 0;
+            ng = 0;
+            for (u = 0; u < n; u ++) {
+                int z;
+
+                z = s1src[s1+u];
+                s += (uint)(z * z);
+                ng |= s;
+                z = s2src[s2+u];
+                s += (uint)(z * z);
+                ng |= s;
+            }
+            s |= (uint)(-(ng >> 31));
+
+            return s <= l2bound[logn];
+        }
+
+        internal bool is_short_half(
+            uint sqn, short[] s2src, int s2, uint logn)
+        {
+            int n, u;
+            uint ng;
+
+            n = (int)1 << (int)logn;
+            ng = (uint)(-(sqn >> 31));
+            for (u = 0; u < n; u ++) {
+                int z;
+
+                z = s2src[s2 + u];
+                sqn += (uint)(z * z);
+                ng |= sqn;
+            }
+            sqn |= (uint)(-(ng >> 31));
+
+            return sqn <= l2bound[logn];
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconConversions.cs b/crypto/src/pqc/crypto/falcon/FalconConversions.cs
new file mode 100644
index 000000000..36ef56fb4
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconConversions.cs
@@ -0,0 +1,66 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconConversions
+    {
+        internal FalconConversions(){}
+
+        internal byte[] int_to_bytes(int x)
+        {
+            byte[] res = new byte[4];
+            res[0] = (byte)(x >> 0);
+            res[1] = (byte)(x >> 8);
+            res[2] = (byte)(x >> 16);
+            res[3] = (byte)(x >> 24);
+            return res;
+        }
+        internal uint bytes_to_uint(byte[] src, int pos)
+        {
+            uint acc = 0;
+            acc =   ((uint)src[pos + 0]) << 0 |
+                    ((uint)src[pos + 1]) << 8 |
+                    ((uint)src[pos + 2]) << 16 |
+                    ((uint)src[pos + 3]) << 24;
+            return acc;
+        }
+
+        internal byte[] ulong_to_bytes(ulong x)
+        {
+            byte[] res = new byte[8];
+            res[0] = (byte)(x >> 0);
+            res[1] = (byte)(x >> 8);
+            res[2] = (byte)(x >> 16);
+            res[3] = (byte)(x >> 24);
+            res[4] = (byte)(x >> 32);
+            res[5] = (byte)(x >> 40);
+            res[6] = (byte)(x >> 48);
+            res[7] = (byte)(x >> 56);
+            return res;
+        }
+
+        internal ulong bytes_to_ulong(byte[] src, int pos)
+        {
+            ulong acc = 0;
+            acc = ((ulong)src[pos + 0]) << 0 |
+                ((ulong)src[pos + 1]) << 8 |
+                ((ulong)src[pos + 2]) << 16 |
+                ((ulong)src[pos + 3]) << 24 |
+                ((ulong)src[pos + 4]) << 32 |
+                ((ulong)src[pos + 5]) << 40 |
+                ((ulong)src[pos + 6]) << 48 |
+                ((ulong)src[pos + 7]) << 56;
+            return acc;
+        }
+
+        internal uint[] bytes_to_uint_array(byte[] src, int pos, int num)
+        {
+            uint[] res = new uint[num];
+            for (int i = 0; i < num; i++)
+            {
+                res[i] = bytes_to_uint(src, pos + (4 * i));
+            }
+            return res;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconFFT.cs b/crypto/src/pqc/crypto/falcon/FalconFFT.cs
new file mode 100644
index 000000000..aa862cc23
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconFFT.cs
@@ -0,0 +1,711 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconFFT
+    {
+        FPREngine fpre;
+        internal FalconFFT() {
+            fpre = new FPREngine();
+        }
+        internal FalconFFT(FPREngine fprengine) {
+            this.fpre = fprengine;
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+        /*
+        * Addition of two complex numbers (d = a + b).
+        */
+        internal FalconFPR[] FPC_ADD(FalconFPR a_re, FalconFPR a_im,
+                        FalconFPR b_re, FalconFPR b_im) {
+            FalconFPR fpct_re, fpct_im;
+            fpct_re = this.fpre.fpr_add(a_re, b_re);
+            fpct_im = this.fpre.fpr_add(a_im, b_im);
+            //d_re.Set(fpct_re);
+            //d_im.Set(fpct_im);
+            return new FalconFPR[] { fpct_re, fpct_im };
+        }
+
+        /*
+        * Subtraction of two complex numbers (d = a - b).
+        */
+        internal FalconFPR[] FPC_SUB(FalconFPR a_re, FalconFPR a_im,
+                        FalconFPR b_re, FalconFPR b_im) {
+            FalconFPR fpct_re, fpct_im;
+            fpct_re = this.fpre.fpr_sub(a_re, b_re);
+            fpct_im = this.fpre.fpr_sub(a_im, b_im);
+            return new FalconFPR[] { fpct_re, fpct_im }; 
+        }
+
+        /*
+        * Multplication of two complex numbers (d = a * b).
+        */
+        internal FalconFPR[] FPC_MUL(FalconFPR a_re, FalconFPR a_im,
+                        FalconFPR b_re, FalconFPR b_im) {
+            FalconFPR fpct_a_re, fpct_a_im;
+            FalconFPR fpct_b_re, fpct_b_im;
+            FalconFPR fpct_d_re, fpct_d_im;
+            fpct_a_re = a_re; 
+            fpct_a_im = a_im; 
+            fpct_b_re = b_re; 
+            fpct_b_im = b_im; 
+            fpct_d_re = this.fpre.fpr_sub( 
+                this.fpre.fpr_mul(fpct_a_re, fpct_b_re), 
+                this.fpre.fpr_mul(fpct_a_im, fpct_b_im)); 
+            fpct_d_im = this.fpre.fpr_add( 
+                this.fpre.fpr_mul(fpct_a_re, fpct_b_im), 
+                this.fpre.fpr_mul(fpct_a_im, fpct_b_re)); 
+            return new FalconFPR[] {fpct_d_re, fpct_d_im};
+        }
+
+        /*
+        * Squaring of a complex number (d = a * a).
+        */
+        internal FalconFPR[] FPC_SQR(FalconFPR d_re, FalconFPR d_im, 
+                        FalconFPR a_re, FalconFPR a_im) {
+            FalconFPR fpct_a_re, fpct_a_im; 
+            FalconFPR fpct_d_re, fpct_d_im; 
+            fpct_a_re = a_re; 
+            fpct_a_im = a_im; 
+            fpct_d_re = this.fpre.fpr_sub(this.fpre.fpr_sqr(fpct_a_re), this.fpre.fpr_sqr(fpct_a_im)); 
+            fpct_d_im = this.fpre.fpr_double(this.fpre.fpr_mul(fpct_a_re, fpct_a_im)); 
+            return new FalconFPR[] {fpct_d_re, fpct_d_im};
+        }
+
+        /*
+        * Inversion of a complex number (d = 1 / a).
+        */
+        internal FalconFPR[] FPC_INV(FalconFPR a_re, FalconFPR a_im) {
+            FalconFPR fpct_a_re, fpct_a_im; 
+            FalconFPR fpct_d_re, fpct_d_im; 
+            FalconFPR fpct_m; 
+            fpct_a_re = a_re; 
+            fpct_a_im = a_im; 
+            fpct_m = this.fpre.fpr_add(this.fpre.fpr_sqr(fpct_a_re), this.fpre.fpr_sqr(fpct_a_im)); 
+            fpct_m = this.fpre.fpr_inv(fpct_m); 
+            fpct_d_re = this.fpre.fpr_mul(fpct_a_re, fpct_m); 
+            fpct_d_im = this.fpre.fpr_mul(this.fpre.fpr_neg(fpct_a_im), fpct_m);
+            return new FalconFPR[] { fpct_d_re, fpct_d_im };
+        }
+        /*
+        * Division of complex numbers (d = a / b).
+        */
+        internal FalconFPR[] FPC_DIV(FalconFPR a_re, FalconFPR a_im,
+                        FalconFPR b_re, FalconFPR b_im) {
+            FalconFPR fpct_a_re, fpct_a_im; 
+            FalconFPR fpct_b_re, fpct_b_im; 
+            FalconFPR fpct_d_re, fpct_d_im; 
+            FalconFPR fpct_m; 
+            fpct_a_re = (a_re); 
+            fpct_a_im = (a_im); 
+            fpct_b_re = (b_re); 
+            fpct_b_im = (b_im); 
+            fpct_m = this.fpre.fpr_add(this.fpre.fpr_sqr(fpct_b_re), this.fpre.fpr_sqr(fpct_b_im)); 
+            fpct_m = this.fpre.fpr_inv(fpct_m); 
+            fpct_b_re = this.fpre.fpr_mul(fpct_b_re, fpct_m); 
+            fpct_b_im = this.fpre.fpr_mul(this.fpre.fpr_neg(fpct_b_im), fpct_m); 
+            fpct_d_re = this.fpre.fpr_sub( 
+                this.fpre.fpr_mul(fpct_a_re, fpct_b_re), 
+                this.fpre.fpr_mul(fpct_a_im, fpct_b_im)); 
+            fpct_d_im = this.fpre.fpr_add( 
+                this.fpre.fpr_mul(fpct_a_re, fpct_b_im), 
+                this.fpre.fpr_mul(fpct_a_im, fpct_b_re));
+            return new FalconFPR[] { fpct_d_re, fpct_d_im };
+        }
+
+        /*
+        * Let w = exp(i*pi/N); w is a primitive 2N-th root of 1. We define the
+        * values w_j = w^(2j+1) for all j from 0 to N-1: these are the roots
+        * of X^N+1 in the field of complex numbers. A crucial property is that
+        * w_{N-1-j} = conj(w_j) = 1/w_j for all j.
+        *
+        * FFT representation of a polynomial f (taken modulo X^N+1) is the
+        * set of values f(w_j). Since f is real, conj(f(w_j)) = f(conj(w_j)),
+        * thus f(w_{N-1-j}) = conj(f(w_j)). We thus store only half the values,
+        * for j = 0 to N/2-1; the other half can be recomputed easily when (if)
+        * needed. A consequence is that FFT representation has the same size
+        * as normal representation: N/2 complex numbers use N real numbers (each
+        * complex number is the combination of a real and an imaginary part).
+        *
+        * We use a specific ordering which makes computations easier. Let rev()
+        * be the bit-reversal function over log(N) bits. For j in 0..N/2-1, we
+        * store the real and imaginary parts of f(w_j) in slots:
+        *
+        *    Re(f(w_j)) -> slot rev(j)/2
+        *    Im(f(w_j)) -> slot rev(j)/2+N/2
+        *
+        * (Note that rev(j) is even for j < N/2.)
+        */
+
+        internal void FFT(FalconFPR[] fsrc, int f, uint logn)
+        {
+            /*
+            * FFT algorithm in bit-reversal order uses the following
+            * iterative algorithm:
+            *
+            *   t = N
+            *   for m = 1; m < N; m *= 2:
+            *       ht = t/2
+            *       for i1 = 0; i1 < m; i1 ++:
+            *           j1 = i1 * t
+            *           s = GM[m + i1]
+            *           for j = j1; j < (j1 + ht); j ++:
+            *               x = fsrc[f + j]
+            *               y = s * fsrc[f + j + ht]
+            *               fsrc[f + j] = x + y
+            *               fsrc[f + j + ht] = x - y
+            *       t = ht
+            *
+            * GM[k] contains w^rev(k) for primitive root w = exp(i*pi/N).
+            *
+            * In the description above, fsrc[f + ] is supposed to contain complex
+            * numbers. In our in-memory representation, the real and
+            * imaginary parts of fsrc[f + k] are in array slots k and k+N/2.
+            *
+            * We only keep the first half of the complex numbers. We can
+            * see that after the first iteration, the first and second halves
+            * of the array of complex numbers have separate lives, so we
+            * simply ignore the second part.
+            */
+
+            uint u;
+            int t, n, hn, m;
+
+            /*
+            * First iteration: compute fsrc[f + j] + i * fsrc[f + j+N/2] for all j < N/2
+            * (because GM[1] = w^rev(1) = w^(N/2) = i).
+            * In our chosen representation, this is a no-op: everything is
+            * already where it should be.
+            */
+
+            /*
+            * Subsequent iterations are truncated to use only the first
+            * half of values.
+            */
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            t = hn;
+            for (u = 1, m = 2; u < logn; u ++, m <<= 1) {
+                int ht, hm, i1, j1;
+
+                ht = t >> 1;
+                hm = m >> 1;
+                for (i1 = 0, j1 = 0; i1 < hm; i1 ++, j1 += t) {
+                    int j, j2;
+
+                    j2 = j1 + ht;
+                    FalconFPR s_re, s_im;
+
+                    s_re = this.fpre.fpr_gm_tab[((m + i1) << 1) + 0];
+                    s_im = this.fpre.fpr_gm_tab[((m + i1) << 1) + 1];
+                    for (j = j1; j < j2; j ++) {
+                        FalconFPR x_re, x_im, y_re, y_im;
+                        FalconFPR[] res;
+
+                        x_re = fsrc[f + j];
+                        x_im = fsrc[f + j + hn];
+                        y_re = fsrc[f + j + ht];
+                        y_im = fsrc[f + j + ht + hn];
+                        res = FPC_MUL(y_re, y_im, s_re, s_im);
+                        y_re = res[0]; y_im = res[1];
+                        res = FPC_ADD(x_re, x_im, y_re, y_im);
+                        fsrc[f + j] = res[0]; fsrc[f + j + hn] = res[1];
+                        res = FPC_SUB(x_re, x_im, y_re, y_im);
+                        fsrc[f + j + ht] = res[0]; fsrc[f + j + ht + hn] = res[1];
+                    }
+                }
+                t = ht;
+            }
+        }
+
+        internal void iFFT(FalconFPR[] fsrc, int f, uint logn)
+        {
+            /*
+            * Inverse FFT algorithm in bit-reversal order uses the following
+            * iterative algorithm:
+            *
+            *   t = 1
+            *   for m = N; m > 1; m /= 2:
+            *       hm = m/2
+            *       dt = t*2
+            *       for i1 = 0; i1 < hm; i1 ++:
+            *           j1 = i1 * dt
+            *           s = iGM[hm + i1]
+            *           for j = j1; j < (j1 + t); j ++:
+            *               x = fsrc[f + j]
+            *               y = fsrc[f + j + t]
+            *               fsrc[f + j] = x + y
+            *               fsrc[f + j + t] = s * (x - y)
+            *       t = dt
+            *   for i1 = 0; i1 < N; i1 ++:
+            *       fsrc[f + i1] = fsrc[f + i1] / N
+            *
+            * iGM[k] contains (1/w)^rev(k) for primitive root w = exp(i*pi/N)
+            * (actually, iGM[k] = 1/GM[k] = conj(GM[k])).
+            *
+            * In the main loop (not counting the final division loop), in
+            * all iterations except the last, the first and second half of fsrc[f + ]
+            * (as an array of complex numbers) are separate. In our chosen
+            * representation, we do not keep the second half.
+            *
+            * The last iteration recombines the recomputed half with the
+            * implicit half, and should yield only real numbers since the
+            * target polynomial is real; moreover, s = i at that step.
+            * Thus, when considering x and y:
+            *    y = conj(x) since the final fsrc[f + j] must be real
+            *    Therefore, fsrc[f + j] is filled with 2*Re(x), and fsrc[f + j + t] is
+            *    filled with 2*Im(x).
+            * But we already have Re(x) and Im(x) in array slots j and j+t
+            * in our chosen representation. That last iteration is thus a
+            * simple doubling of the values in all the array.
+            *
+            * We make the last iteration a no-op by tweaking the final
+            * division into a division by N/2, not N.
+            */
+            int u, n, hn, t, m;
+
+            n = (int)1 << (int)logn;
+            t = 1;
+            m = n;
+            hn = n >> 1;
+            for (u = (int)logn; u > 1; u --) {
+                int hm, dt, i1, j1;
+
+                hm = m >> 1;
+                dt = t << 1;
+                for (i1 = 0, j1 = 0; j1 < hn; i1 ++, j1 += dt) {
+                    int j, j2;
+
+                    j2 = j1 + t;
+                    FalconFPR s_re, s_im;
+
+                    s_re = this.fpre.fpr_gm_tab[((hm + i1) << 1) + 0];
+                    s_im = this.fpre.fpr_neg(this.fpre.fpr_gm_tab[((hm + i1) << 1) + 1]);
+                    for (j = j1; j < j2; j ++) {
+                        FalconFPR x_re, x_im, y_re, y_im;
+                        FalconFPR[] res;
+
+                        x_re = fsrc[f + j];
+                        x_im = fsrc[f + j + hn];
+                        y_re = fsrc[f + j + t];
+                        y_im = fsrc[f + j + t + hn];
+                        res = FPC_ADD(x_re, x_im, y_re, y_im);
+                        fsrc[f + j] = res[0]; fsrc[f + j + hn] = res[1];
+                            
+                        res = FPC_SUB(x_re, x_im, y_re, y_im);
+                        x_re = res[0]; x_im = res[1];
+                        res = FPC_MUL(x_re, x_im, s_re, s_im);
+                        fsrc[f + j + t] = res[0]; fsrc[f + j + t + hn] = res[1];
+                    }
+                }
+                t = dt;
+                m = hm;
+            }
+
+            /*
+            * Last iteration is a no-op, provided that we divide by N/2
+            * instead of N. We need to make a special case for logn = 0.
+            */
+            if (logn > 0) {
+                FalconFPR ni;
+
+                ni = this.fpre.fpr_p2_tab[logn];
+                for (u = 0; u < n; u ++) {
+                    fsrc[f+u] = this.fpre.fpr_mul(fsrc[f+u], ni);
+                }
+            }
+        }
+
+        internal void poly_add(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                asrc[a + u] = this.fpre.fpr_add(asrc[a + u], bsrc[b + u]);
+            }
+        }
+
+        internal void poly_sub(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                asrc[a + u] = this.fpre.fpr_sub(asrc[a + u], bsrc[b + u]);
+            }
+        }
+
+        internal void poly_neg(FalconFPR[] asrc, int a, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                asrc[a + u] = this.fpre.fpr_neg(asrc[a + u]);
+            }
+        }
+
+        internal void poly_adj_fft(FalconFPR[] asrc, int a, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = (n >> 1); u < n; u ++) {
+                asrc[a + u] = this.fpre.fpr_neg(asrc[a + u]);
+            }
+        }
+
+        internal void poly_mul_fft(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR a_re, a_im, b_re, b_im;
+                FalconFPR[] res;
+
+                a_re = asrc[a + u];
+                a_im = asrc[a + u + hn];
+                b_re = bsrc[b + u];
+                b_im = bsrc[b + u + hn];
+                res = FPC_MUL(a_re, a_im, b_re, b_im);
+                asrc[a + u] = res[0]; asrc[a + u + hn] = res[1];
+            }
+        }
+
+        internal void poly_muladj_fft(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR a_re, a_im, b_re, b_im;
+                FalconFPR[] res;
+
+                a_re = asrc[a + u];
+                a_im = asrc[a + u + hn];
+                b_re = bsrc[b + u];
+                b_im = this.fpre.fpr_neg(bsrc[b + u + hn]);
+                res = FPC_MUL(a_re, a_im, b_re, b_im);
+                asrc[a + u] = res[0]; asrc[a + u + hn] = res[1];
+            }
+        }
+
+        internal void poly_mulselfadj_fft(FalconFPR[] asrc, int a, uint logn)
+        {
+            /*
+            * Since each coefficient is multiplied with its own conjugate,
+            * the result contains only real values.
+            */
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR a_re, a_im;
+
+                a_re = asrc[a + u];
+                a_im = asrc[a + u + hn];
+                asrc[a + u] = this.fpre.fpr_add(this.fpre.fpr_sqr(a_re), this.fpre.fpr_sqr(a_im));
+                asrc[a + u + hn] = this.fpre.fpr_zero;
+            }
+        }
+
+        internal void poly_mulconst(FalconFPR[] asrc, int a, FalconFPR x, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                asrc[a + u] = this.fpre.fpr_mul(asrc[a + u], x);
+            }
+        }
+
+        internal void poly_div_fft(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR a_re, a_im, b_re, b_im;
+                FalconFPR[] res;
+
+                a_re = asrc[a + u];
+                a_im = asrc[a + u + hn];
+                b_re = bsrc[b + u];
+                b_im = bsrc[b + u + hn];
+                res = FPC_DIV(a_re, a_im, b_re, b_im);
+                asrc[a + u] = res[0]; asrc[a + u + hn] = res[1];
+            }
+        }
+
+        internal void poly_invnorm2_fft(FalconFPR[] dsrc, int d,
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR a_re, a_im;
+                FalconFPR b_re, b_im;
+
+                a_re = asrc[a + u];
+                a_im = asrc[a + u + hn];
+                b_re = bsrc[b + u];
+                b_im = bsrc[b + u + hn];
+                dsrc[d + u] = this.fpre.fpr_inv(this.fpre.fpr_add(
+                    this.fpre.fpr_add(this.fpre.fpr_sqr(a_re), this.fpre.fpr_sqr(a_im)),
+                    this.fpre.fpr_add(this.fpre.fpr_sqr(b_re), this.fpre.fpr_sqr(b_im))));
+            }
+        }
+
+        internal void poly_add_muladj_fft(FalconFPR[] dsrc, int d,
+            FalconFPR[] Fsrc, int F, FalconFPR[] Gsrc, int G,
+            FalconFPR[] fsrc, int f, FalconFPR[] gsrc, int g, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR F_re, F_im, G_re, G_im;
+                FalconFPR f_re, f_im, g_re, g_im;
+                FalconFPR a_re, a_im, b_re, b_im;
+                FalconFPR[] res;
+
+
+                F_re = Fsrc[F + u];
+                F_im = Fsrc[F + u + hn];
+                G_re = Gsrc[G + u];
+                G_im = Gsrc[G + u + hn];
+                f_re = fsrc[f + u];
+                f_im = fsrc[f + u + hn];
+                g_re = gsrc[g + u];
+                g_im = gsrc[g + u + hn];
+
+                res = FPC_MUL(F_re, F_im, f_re, this.fpre.fpr_neg(f_im));
+                a_re = res[0]; a_im = res[1];
+                res = FPC_MUL(G_re, G_im, g_re, this.fpre.fpr_neg(g_im));
+                b_re = res[0]; b_im = res[1];
+                dsrc[d + u] = this.fpre.fpr_add(a_re, b_re);
+                dsrc[d + u + hn] = this.fpre.fpr_add(a_im, b_im);
+            }
+        }
+
+        internal void poly_mul_autoadj_fft(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                asrc[a + u] = this.fpre.fpr_mul(asrc[a + u], bsrc[b + u]);
+                asrc[a + u + hn] = this.fpre.fpr_mul(asrc[a + u + hn], bsrc[b + u]);
+            }
+        }
+
+        internal void poly_div_autoadj_fft(
+            FalconFPR[] asrc, int a, FalconFPR[] bsrc, int b, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR ib;
+
+                ib = this.fpre.fpr_inv(bsrc[b + u]);
+                asrc[a + u] = this.fpre.fpr_mul(asrc[a + u], ib);
+                asrc[a + u + hn] = this.fpre.fpr_mul(asrc[a + u + hn], ib);
+            }
+        }
+
+        internal void poly_LDL_fft(
+            FalconFPR[] g00src, int g00,
+            FalconFPR[] g01src, int g01, FalconFPR[] g11src, int g11, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR g00_re, g00_im, g01_re, g01_im, g11_re, g11_im;
+                FalconFPR[] res;
+                FalconFPR mu_re, mu_im;
+
+                g00_re = g00src[g00 + u];
+                g00_im = g00src[g00 + u + hn];
+                g01_re = g01src[g01 + u];
+                g01_im = g01src[g01 + u + hn];
+                g11_re = g11src[g11 + u];
+                g11_im = g11src[g11 + u + hn];
+                res = FPC_DIV(g01_re, g01_im, g00_re, g00_im);
+                mu_re = res[0]; mu_im = res[1];
+                res = FPC_MUL(mu_re, mu_im, g01_re, this.fpre.fpr_neg(g01_im));
+                g01_re = res[0]; g01_im = res[1];
+                res = FPC_SUB(g11_re, g11_im, g01_re, g01_im);
+                g11src[g11 + u] = res[0]; g11src[g11 + u + hn] = res[1];
+                g01src[g01 + u] = mu_re;
+                g01src[g01 + u + hn] = this.fpre.fpr_neg(mu_im);
+            }
+        }
+
+        internal void poly_LDLmv_fft(
+            FalconFPR[] d11src, int d11, FalconFPR[] l10src, int l10,
+            FalconFPR[] g00src, int g00, FalconFPR[] g01src, int g01,
+            FalconFPR[] g11src, int g11, uint logn)
+        {
+            int n, hn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            for (u = 0; u < hn; u ++) {
+                FalconFPR g00_re, g00_im, g01_re, g01_im, g11_re, g11_im;
+                FalconFPR[] res;
+                FalconFPR mu_re, mu_im;
+
+                g00_re = g00src[g00 + u];
+                g00_im = g00src[g00 + u + hn];
+                g01_re = g01src[g01 + u];
+                g01_im = g01src[g01 + u + hn];
+                g11_re = g11src[g11 + u];
+                g11_im = g11src[g11 + u + hn];
+                res = FPC_DIV(g01_re, g01_im, g00_re, g00_im);
+                mu_re = res[0]; mu_im = res[1];
+                res = FPC_MUL(mu_re, mu_im, g01_re, this.fpre.fpr_neg(g01_im));
+                g01_re = res[0]; g01_im = res[1];
+                res = FPC_SUB(g11_re, g11_im, g01_re, g01_im);
+                d11src[d11 + u] = res[0]; d11src[d11 + u + hn] = res[1];
+                l10src[l10 + u] = mu_re;
+                l10src[l10 + u + hn] = this.fpre.fpr_neg(mu_im);
+            }
+        }
+
+        internal void poly_split_fft(
+            FalconFPR[] f0src, int f0, FalconFPR[] f1src, int f1,
+            FalconFPR[] fsrc, int f, uint logn)
+        {
+            /*
+            * The FFT representation we use is in bit-reversed order
+            * (element i contains f(w^(rev(i))), where rev() is the
+            * bit-reversal function over the ring degree. This changes
+            * indexes with regards to the Falcon specification.
+            */
+            int n, hn, qn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            qn = hn >> 1;
+
+            /*
+            * We process complex values by pairs. For logn = 1, there is only
+            * one complex value (the other one is the implicit conjugate),
+            * so we add the two lines below because the loop will be
+            * skipped.
+            */
+            f0src[f0 + 0] = fsrc[f + 0];
+            f1src[f1 + 0] = fsrc[f + hn];
+
+            for (u = 0; u < qn; u ++) {
+                FalconFPR a_re, a_im, b_re, b_im, t_re, t_im;
+                FalconFPR[] res;
+
+                a_re = fsrc[f + (u << 1) + 0];
+                a_im = fsrc[f + (u << 1) + 0 + hn];
+                b_re = fsrc[f + (u << 1) + 1];
+                b_im = fsrc[f + (u << 1) + 1 + hn];
+
+                res = FPC_ADD(a_re, a_im, b_re, b_im);
+                t_re = res[0]; t_im = res[1];
+                f0src[f0 + u] = this.fpre.fpr_half(t_re);
+                f0src[f0 + u + qn] = this.fpre.fpr_half(t_im);
+
+                res = FPC_SUB(a_re, a_im, b_re, b_im);
+                t_re = res[0]; t_im = res[1];
+                res = FPC_MUL(t_re, t_im,
+                    this.fpre.fpr_gm_tab[((u + hn) << 1) + 0],
+                    this.fpre.fpr_neg(this.fpre.fpr_gm_tab[((u + hn) << 1) + 1]));
+                t_re = res[0]; t_im = res[1];
+                f1src[f1 + u] = this.fpre.fpr_half(t_re);
+                f1src[f1 + u + qn] = this.fpre.fpr_half(t_im);
+            }
+        }
+
+        internal void poly_merge_fft(
+            FalconFPR[] fsrc, int f,
+            FalconFPR[] f0src, int f0, FalconFPR[] f1src, int f1, uint logn)
+        {
+            int n, hn, qn, u;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            qn = hn >> 1;
+
+            /*
+            * An extra copy to handle the special case logn = 1.
+            */
+            fsrc[f + 0] = f0src[f0 + 0];
+            fsrc[f + hn] = f1src[f1 + 0];
+
+            for (u = 0; u < qn; u ++) {
+                FalconFPR a_re, a_im, 
+                          b_re, b_im;
+                FalconFPR t_re, t_im;
+                FalconFPR[] res;
+
+                a_re = f0src[f0 + u];
+                a_im = f0src[f0 + u + qn];
+                res = FPC_MUL(f1src[f1 + u], f1src[f1 + u + qn],
+                    this.fpre.fpr_gm_tab[((u + hn) << 1) + 0],
+                    this.fpre.fpr_gm_tab[((u + hn) << 1) + 1]);
+                b_re = res[0]; b_im = res[1];
+                res = FPC_ADD(a_re, a_im, b_re, b_im);
+                t_re = res[0]; t_im = res[1];
+                fsrc[f + (u << 1) + 0] = t_re;
+                fsrc[f + (u << 1) + 0 + hn] = t_im;
+                res = FPC_SUB(a_re, a_im, b_re, b_im);
+                t_re = res[0]; t_im = res[1];
+                fsrc[f + (u << 1) + 1] = t_re;
+                fsrc[f + (u << 1) + 1 + hn] = t_im;
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconFPR.cs b/crypto/src/pqc/crypto/falcon/FalconFPR.cs
new file mode 100644
index 000000000..bab3d8fcb
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconFPR.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    internal struct FalconFPR
+    {
+        internal double v;
+
+        internal FalconFPR(double v)
+        {
+            this.v = v;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconKeyGenerationParameters.cs b/crypto/src/pqc/crypto/falcon/FalconKeyGenerationParameters.cs
new file mode 100644
index 000000000..24255f240
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconKeyGenerationParameters.cs
@@ -0,0 +1,22 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public class FalconKeyGenerationParameters
+        : KeyGenerationParameters
+    {
+        private FalconParameters parameters;
+
+        public FalconKeyGenerationParameters(SecureRandom random, FalconParameters parameters)
+            : base(random, 320)
+        {
+            this.parameters = parameters;
+        }
+
+        public FalconParameters Parameters
+        {
+            get { return this.parameters; }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconKeyPairGenerator.cs b/crypto/src/pqc/crypto/falcon/FalconKeyPairGenerator.cs
new file mode 100644
index 000000000..68f90c97f
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconKeyPairGenerator.cs
@@ -0,0 +1,51 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public class FalconKeyPairGenerator
+        : IAsymmetricCipherKeyPairGenerator
+    {
+        private FalconKeyGenerationParameters parameters;
+        private SecureRandom random;
+        private FalconNist nist;
+        private uint logn;
+        private uint noncelen;
+
+        private int pk_size;
+
+        public void Init(KeyGenerationParameters param)
+        {
+            this.parameters = (FalconKeyGenerationParameters)param;
+            this.random = param.Random;
+            this.logn = (uint)((FalconKeyGenerationParameters)param).Parameters.LogN;
+            this.noncelen = (uint)((FalconKeyGenerationParameters)param).Parameters.NonceLength;
+            this.nist = new FalconNist(random, logn, noncelen);
+            int n = 1 << (int)this.logn;
+            //int sk_coeff_size = 8;
+            //if (n == 1024)
+            //{
+            //    sk_coeff_size = 5;
+            //}
+            //else if (n == 256 || n == 512)
+            //{
+            //    sk_coeff_size = 6;
+            //}
+            //else if (n == 64 || n == 128)
+            //{
+            //    sk_coeff_size = 7;
+            //}
+
+            this.pk_size = 1 + (14 * n / 8);
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            nist.crypto_sign_keypair(out byte[] pk, out byte[] f, out byte[] g, out byte[] F);
+            FalconParameters p = this.parameters.Parameters;
+            FalconPrivateKeyParameters privk = new FalconPrivateKeyParameters(p, f, g, F, pk);
+            FalconPublicKeyParameters pubk = new FalconPublicKeyParameters(p, pk);
+            return new AsymmetricCipherKeyPair(pubk, privk);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconKeyParameters.cs b/crypto/src/pqc/crypto/falcon/FalconKeyParameters.cs
new file mode 100644
index 000000000..bb1252706
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconKeyParameters.cs
@@ -0,0 +1,22 @@
+using Org.BouncyCastle.Crypto;
+
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public abstract class FalconKeyParameters 
+        : AsymmetricKeyParameter
+    {
+        private FalconParameters parameters;
+
+        public FalconKeyParameters(bool isprivate, FalconParameters parameters)
+            : base(isprivate)
+        {
+            this.parameters = parameters;
+        }
+
+        public FalconParameters Parameters
+        {
+            get { return parameters; }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconKeygen.cs b/crypto/src/pqc/crypto/falcon/FalconKeygen.cs
new file mode 100644
index 000000000..7fe83056a
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconKeygen.cs
@@ -0,0 +1,3673 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconKeygen
+    {
+        FPREngine fpre;
+        FalconFFT ffte;
+        FalconSmallPrime[] PRIMES;
+        FalconCodec codec;
+        FalconVrfy vrfy;
+        internal FalconKeygen() {
+            this.fpre = new FPREngine();
+            this.PRIMES = new FalconSmallPrimes().PRIMES;
+            this.ffte = new FalconFFT(this.fpre);
+            this.codec = new FalconCodec();
+            this.vrfy = new FalconVrfy();
+        }
+        internal FalconKeygen(FalconCodec codec, FalconVrfy vrfy) {
+            this.fpre = new FPREngine();
+            this.PRIMES = new FalconSmallPrimes().PRIMES;
+            this.ffte = new FalconFFT();
+            this.codec = codec;
+            this.vrfy = vrfy;
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+
+        /*
+        * Reduce a small signed integer modulo a small prime. The source
+        * value x MUST be such that -p < x < p.
+        */
+        uint modp_set(int x, uint p)
+        {
+            uint w;
+
+            w = (uint)x;
+            w += (uint)(p & -(w >> 31));
+            return w;
+        }
+
+        /*
+        * Normalize a modular integer around 0.
+        */
+        int modp_norm(uint x, uint p)
+        {
+            return (int)(x - (p & (((x - ((p + 1) >> 1)) >> 31) - 1)));
+        }
+
+        /*
+        * Compute -1/p mod 2^31. This works for all odd integers p that fit
+        * on 31 bits.
+        */
+        uint modp_ninv31(uint p)
+        {
+            uint y;
+
+            y = 2 - p;
+            y *= 2 - p * y;
+            y *= 2 - p * y;
+            y *= 2 - p * y;
+            y *= 2 - p * y;
+            return (uint)(0x7FFFFFFF & -y);
+        }
+
+        /*
+        * Compute R = 2^31 mod p.
+        */
+        uint modp_R(uint p)
+        {
+            /*
+            * Since 2^30 < p < 2^31, we know that 2^31 mod p is simply
+            * 2^31 - p.
+            */
+            return ((uint)1 << 31) - p;
+        }
+
+        /*
+        * Addition modulo p.
+        */
+        uint modp_add(uint a, uint b, uint p)
+        {
+            uint d;
+
+            d = a + b - p;
+            d += (uint)(p & -(d >> 31));
+            return d;
+        }
+
+        /*
+        * Subtraction modulo p.
+        */
+        uint modp_sub(uint a, uint b, uint p)
+        {
+            uint d;
+
+            d = a - b;
+            d += (uint)(p & -(d >> 31));
+            return d;
+        }
+
+        /*
+        * Halving modulo p.
+        */
+        /* unused
+        uint modp_half(uint a, uint p)
+        {
+            a += p & -(a & 1);
+            return a >> 1;
+        }
+        */
+
+        /*
+        * Montgomery multiplication modulo p. The 'p0i' value is -1/p mod 2^31.
+        * It is required that p is an odd integer.
+        */
+        uint modp_montymul(uint a, uint b, uint p, uint p0i)
+        {
+            ulong z, w;
+            uint d;
+
+            z = (ulong)a * (ulong)b;
+            w = ((z * p0i) & (ulong)0x7FFFFFFF) * p;
+            d = (uint)((z + w) >> 31) - p;
+            d += (uint)(p & -(d >> 31));
+            return d;
+        }
+
+        /*
+        * Compute R2 = 2^62 mod p.
+        */
+        uint modp_R2(uint p, uint p0i)
+        {
+            uint z;
+
+            /*
+            * Compute z = 2^31 mod p (this is the value 1 in Montgomery
+            * representation), then double it with an addition.
+            */
+            z = modp_R(p);
+            z = modp_add(z, z, p);
+
+            /*
+            * Square it five times to obtain 2^32 in Montgomery representation
+            * (i.e. 2^63 mod p).
+            */
+            z = modp_montymul(z, z, p, p0i);
+            z = modp_montymul(z, z, p, p0i);
+            z = modp_montymul(z, z, p, p0i);
+            z = modp_montymul(z, z, p, p0i);
+            z = modp_montymul(z, z, p, p0i);
+
+            /*
+            * Halve the value mod p to get 2^62.
+            */
+            z = (uint)((z + (p & -(z & 1))) >> 1);
+            return z;
+        }
+
+        /*
+        * Compute 2^(31*x) modulo p. This works for integers x up to 2^11.
+        * p must be prime such that 2^30 < p < 2^31; p0i must be equal to
+        * -1/p mod 2^31; R2 must be equal to 2^62 mod p.
+        */
+        uint modp_Rx(uint x, uint p, uint p0i, uint R2)
+        {
+            int i;
+            uint r, z;
+
+            /*
+            * 2^(31*x) = (2^31)*(2^(31*(x-1))); i.e. we want the Montgomery
+            * representation of (2^31)^e mod p, where e = x-1.
+            * R2 is 2^31 in Montgomery representation.
+            */
+            x --;
+            r = R2;
+            z = modp_R(p);
+            for (i = 0; (1U << i) <= x; i ++) {
+                if ((x & (1U << i)) != 0) {
+                    z = modp_montymul(z, r, p, p0i);
+                }
+                r = modp_montymul(r, r, p, p0i);
+            }
+            return z;
+        }
+
+        /*
+        * Division modulo p. If the divisor (b) is 0, then 0 is returned.
+        * This function computes proper results only when p is prime.
+        * Parameters:
+        *   a     dividend
+        *   b     divisor
+        *   p     odd prime modulus
+        *   p0i   -1/p mod 2^31
+        *   R     2^31 mod R
+        */
+        uint modp_div(uint a, uint b, uint p, uint p0i, uint R)
+        {
+            uint z, e;
+            int i;
+
+            e = p - 2;
+            z = R;
+            for (i = 30; i >= 0; i --) {
+                uint z2;
+
+                z = modp_montymul(z, z, p, p0i);
+                z2 = modp_montymul(z, b, p, p0i);
+                z ^= (uint)((z ^ z2) & -(uint)((e >> i) & 1));
+            }
+
+            /*
+            * The loop above just assumed that b was in Montgomery
+            * representation, i.e. really contained b*R; under that
+            * assumption, it returns 1/b in Montgomery representation,
+            * which is R/b. But we gave it b in normal representation,
+            * so the loop really returned R/(b/R) = R^2/b.
+            *
+            * We want a/b, so we need one Montgomery multiplication with a,
+            * which also remove one of the R factors, and another such
+            * multiplication to remove the second R factor.
+            */
+            z = modp_montymul(z, 1, p, p0i);
+            return modp_montymul(a, z, p, p0i);
+        }
+
+        /*
+        * Bit-reversal index table.
+        */
+        ushort[] REV10 = {
+            0,  512,  256,  768,  128,  640,  384,  896,   64,  576,  320,  832,
+            192,  704,  448,  960,   32,  544,  288,  800,  160,  672,  416,  928,
+            96,  608,  352,  864,  224,  736,  480,  992,   16,  528,  272,  784,
+            144,  656,  400,  912,   80,  592,  336,  848,  208,  720,  464,  976,
+            48,  560,  304,  816,  176,  688,  432,  944,  112,  624,  368,  880,
+            240,  752,  496, 1008,    8,  520,  264,  776,  136,  648,  392,  904,
+            72,  584,  328,  840,  200,  712,  456,  968,   40,  552,  296,  808,
+            168,  680,  424,  936,  104,  616,  360,  872,  232,  744,  488, 1000,
+            24,  536,  280,  792,  152,  664,  408,  920,   88,  600,  344,  856,
+            216,  728,  472,  984,   56,  568,  312,  824,  184,  696,  440,  952,
+            120,  632,  376,  888,  248,  760,  504, 1016,    4,  516,  260,  772,
+            132,  644,  388,  900,   68,  580,  324,  836,  196,  708,  452,  964,
+            36,  548,  292,  804,  164,  676,  420,  932,  100,  612,  356,  868,
+            228,  740,  484,  996,   20,  532,  276,  788,  148,  660,  404,  916,
+            84,  596,  340,  852,  212,  724,  468,  980,   52,  564,  308,  820,
+            180,  692,  436,  948,  116,  628,  372,  884,  244,  756,  500, 1012,
+            12,  524,  268,  780,  140,  652,  396,  908,   76,  588,  332,  844,
+            204,  716,  460,  972,   44,  556,  300,  812,  172,  684,  428,  940,
+            108,  620,  364,  876,  236,  748,  492, 1004,   28,  540,  284,  796,
+            156,  668,  412,  924,   92,  604,  348,  860,  220,  732,  476,  988,
+            60,  572,  316,  828,  188,  700,  444,  956,  124,  636,  380,  892,
+            252,  764,  508, 1020,    2,  514,  258,  770,  130,  642,  386,  898,
+            66,  578,  322,  834,  194,  706,  450,  962,   34,  546,  290,  802,
+            162,  674,  418,  930,   98,  610,  354,  866,  226,  738,  482,  994,
+            18,  530,  274,  786,  146,  658,  402,  914,   82,  594,  338,  850,
+            210,  722,  466,  978,   50,  562,  306,  818,  178,  690,  434,  946,
+            114,  626,  370,  882,  242,  754,  498, 1010,   10,  522,  266,  778,
+            138,  650,  394,  906,   74,  586,  330,  842,  202,  714,  458,  970,
+            42,  554,  298,  810,  170,  682,  426,  938,  106,  618,  362,  874,
+            234,  746,  490, 1002,   26,  538,  282,  794,  154,  666,  410,  922,
+            90,  602,  346,  858,  218,  730,  474,  986,   58,  570,  314,  826,
+            186,  698,  442,  954,  122,  634,  378,  890,  250,  762,  506, 1018,
+            6,  518,  262,  774,  134,  646,  390,  902,   70,  582,  326,  838,
+            198,  710,  454,  966,   38,  550,  294,  806,  166,  678,  422,  934,
+            102,  614,  358,  870,  230,  742,  486,  998,   22,  534,  278,  790,
+            150,  662,  406,  918,   86,  598,  342,  854,  214,  726,  470,  982,
+            54,  566,  310,  822,  182,  694,  438,  950,  118,  630,  374,  886,
+            246,  758,  502, 1014,   14,  526,  270,  782,  142,  654,  398,  910,
+            78,  590,  334,  846,  206,  718,  462,  974,   46,  558,  302,  814,
+            174,  686,  430,  942,  110,  622,  366,  878,  238,  750,  494, 1006,
+            30,  542,  286,  798,  158,  670,  414,  926,   94,  606,  350,  862,
+            222,  734,  478,  990,   62,  574,  318,  830,  190,  702,  446,  958,
+            126,  638,  382,  894,  254,  766,  510, 1022,    1,  513,  257,  769,
+            129,  641,  385,  897,   65,  577,  321,  833,  193,  705,  449,  961,
+            33,  545,  289,  801,  161,  673,  417,  929,   97,  609,  353,  865,
+            225,  737,  481,  993,   17,  529,  273,  785,  145,  657,  401,  913,
+            81,  593,  337,  849,  209,  721,  465,  977,   49,  561,  305,  817,
+            177,  689,  433,  945,  113,  625,  369,  881,  241,  753,  497, 1009,
+            9,  521,  265,  777,  137,  649,  393,  905,   73,  585,  329,  841,
+            201,  713,  457,  969,   41,  553,  297,  809,  169,  681,  425,  937,
+            105,  617,  361,  873,  233,  745,  489, 1001,   25,  537,  281,  793,
+            153,  665,  409,  921,   89,  601,  345,  857,  217,  729,  473,  985,
+            57,  569,  313,  825,  185,  697,  441,  953,  121,  633,  377,  889,
+            249,  761,  505, 1017,    5,  517,  261,  773,  133,  645,  389,  901,
+            69,  581,  325,  837,  197,  709,  453,  965,   37,  549,  293,  805,
+            165,  677,  421,  933,  101,  613,  357,  869,  229,  741,  485,  997,
+            21,  533,  277,  789,  149,  661,  405,  917,   85,  597,  341,  853,
+            213,  725,  469,  981,   53,  565,  309,  821,  181,  693,  437,  949,
+            117,  629,  373,  885,  245,  757,  501, 1013,   13,  525,  269,  781,
+            141,  653,  397,  909,   77,  589,  333,  845,  205,  717,  461,  973,
+            45,  557,  301,  813,  173,  685,  429,  941,  109,  621,  365,  877,
+            237,  749,  493, 1005,   29,  541,  285,  797,  157,  669,  413,  925,
+            93,  605,  349,  861,  221,  733,  477,  989,   61,  573,  317,  829,
+            189,  701,  445,  957,  125,  637,  381,  893,  253,  765,  509, 1021,
+            3,  515,  259,  771,  131,  643,  387,  899,   67,  579,  323,  835,
+            195,  707,  451,  963,   35,  547,  291,  803,  163,  675,  419,  931,
+            99,  611,  355,  867,  227,  739,  483,  995,   19,  531,  275,  787,
+            147,  659,  403,  915,   83,  595,  339,  851,  211,  723,  467,  979,
+            51,  563,  307,  819,  179,  691,  435,  947,  115,  627,  371,  883,
+            243,  755,  499, 1011,   11,  523,  267,  779,  139,  651,  395,  907,
+            75,  587,  331,  843,  203,  715,  459,  971,   43,  555,  299,  811,
+            171,  683,  427,  939,  107,  619,  363,  875,  235,  747,  491, 1003,
+            27,  539,  283,  795,  155,  667,  411,  923,   91,  603,  347,  859,
+            219,  731,  475,  987,   59,  571,  315,  827,  187,  699,  443,  955,
+            123,  635,  379,  891,  251,  763,  507, 1019,    7,  519,  263,  775,
+            135,  647,  391,  903,   71,  583,  327,  839,  199,  711,  455,  967,
+            39,  551,  295,  807,  167,  679,  423,  935,  103,  615,  359,  871,
+            231,  743,  487,  999,   23,  535,  279,  791,  151,  663,  407,  919,
+            87,  599,  343,  855,  215,  727,  471,  983,   55,  567,  311,  823,
+            183,  695,  439,  951,  119,  631,  375,  887,  247,  759,  503, 1015,
+            15,  527,  271,  783,  143,  655,  399,  911,   79,  591,  335,  847,
+            207,  719,  463,  975,   47,  559,  303,  815,  175,  687,  431,  943,
+            111,  623,  367,  879,  239,  751,  495, 1007,   31,  543,  287,  799,
+            159,  671,  415,  927,   95,  607,  351,  863,  223,  735,  479,  991,
+            63,  575,  319,  831,  191,  703,  447,  959,  127,  639,  383,  895,
+            255,  767,  511, 1023
+        };
+
+        /*
+        * Compute the roots for NTT and inverse NTT (binary case). Input
+        * parameter g is a primitive 2048-th root of 1 modulo p (i.e. g^1024 =
+        * -1 mod p). This fills gm[] and igm[] with powers of g and 1/g:
+        *   gm[rev(i)] = g^i mod p
+        *   igm[rev(i)] = (1/g)^i mod p
+        * where rev() is the "bit reversal" function over 10 bits. It fills
+        * the arrays only up to N = 2^logn values.
+        *
+        * The values stored in gm[] and igm[] are in Montgomery representation.
+        *
+        * p must be a prime such that p = 1 mod 2048.
+        */
+        void modp_mkgm2(uint[] gmsrc, int gm, uint[] igmsrc, int igm, uint logn,
+            uint g, uint p, uint p0i)
+        {
+            int u, n;
+            uint k;
+            uint ig, x1, x2, R2;
+
+            n = (int)1 << (int)logn;
+
+            /*
+            * We want g such that g^(2N) = 1 mod p, but the provided
+            * generator has order 2048. We must square it a few times.
+            */
+            R2 = modp_R2(p, p0i);
+            g = modp_montymul(g, R2, p, p0i);
+            for (k = logn; k < 10; k ++) {
+                g = modp_montymul(g, g, p, p0i);
+            }
+
+            ig = modp_div(R2, g, p, p0i, modp_R(p));
+            k = 10 - logn;
+            x1 = x2 = modp_R(p);
+            for (u = 0; u < n; u ++) {
+                int v;
+
+                v = REV10[u << (int)k];
+                gmsrc[gm+v] = x1;
+                igmsrc[igm+v] = x2;
+                x1 = modp_montymul(x1, g, p, p0i);
+                x2 = modp_montymul(x2, ig, p, p0i);
+            }
+        }
+
+        /*
+        * Compute the NTT over a polynomial (binary case). Polynomial elements
+        * are a[0], a[stride], a[2 * stride]...
+        */
+        void modp_NTT2_ext(uint[] asrc, int a, int stride, uint[] gmsrc, int gm, uint logn,
+            uint p, uint p0i)
+        {
+            int t, m, n;
+
+            if (logn == 0) {
+                return;
+            }
+            n = (int)1 << (int)logn;
+            t = n;
+            for (m = 1; m < n; m <<= 1) {
+                int ht, u, v1;
+
+                ht = t >> 1;
+                for (u = 0, v1 = 0; u < m; u ++, v1 += t) {
+                    uint s;
+                    int v;
+                    int r1;
+                    int r2;
+
+                    s = gmsrc[gm+m + u];
+                    r1 = a + v1 * stride;
+                    r2 = r1 + ht * stride;
+                    for (v = 0; v < ht; v ++, r1 += stride, r2 += stride) {
+                        uint x, y;
+
+                        x = asrc[r1];
+                        y = modp_montymul(asrc[r2], s, p, p0i);
+                        asrc[r1] = modp_add(x, y, p);
+                        asrc[r2] = modp_sub(x, y, p);
+                    }
+                }
+                t = ht;
+            }
+        }
+
+        /*
+        * Compute the inverse NTT over a polynomial (binary case).
+        */
+        void modp_iNTT2_ext(uint[] asrc, int a, int stride, uint[] igmsrc, int igm, uint logn,
+            uint p, uint p0i)
+        {
+            int t, m, n, k;
+            uint ni;
+            int r;
+
+            if (logn == 0) {
+                return;
+            }
+            n = (int)1 << (int)logn;
+            t = 1;
+            for (m = n; m > 1; m >>= 1) {
+                int hm, dt, u, v1;
+
+                hm = m >> 1;
+                dt = t << 1;
+                for (u = 0, v1 = 0; u < hm; u ++, v1 += dt) {
+                    uint s;
+                    int v;
+                    int r1;
+                    int r2;
+
+                    s = igmsrc[igm+hm + u];
+                    r1 = a + v1 * stride;
+                    r2 = r1 + t * stride;
+                    for (v = 0; v < t; v ++, r1 += stride, r2 += stride) {
+                        uint x, y;
+
+                        x = asrc[r1];
+                        y = asrc[r2];
+                        asrc[r1] = modp_add(x, y, p);
+                        asrc[r2] = modp_montymul(
+                            modp_sub(x, y, p), s, p, p0i);;
+                    }
+                }
+                t = dt;
+            }
+
+            /*
+            * We need 1/n in Montgomery representation, i.e. R/n. Since
+            * 1 <= logn <= 10, R/n is an integer; morever, R/n <= 2^30 < p,
+            * thus a simple shift will do.
+            */
+            ni = (uint)1 << (int)(31 - logn);
+            for (k = 0, r = a; k < n; k ++, r += stride) {
+                asrc[r] = modp_montymul(asrc[r], ni, p, p0i);
+            }
+        }
+
+        /*
+        * Simplified macros for NTT and iNTT (binary case) when the elements
+        * are consecutive in RAM.
+        */
+        void modp_NTT2(uint[] asrc, int a, uint[] gmsrc, int gm, uint logn, uint p, uint p0i) {
+            this.modp_NTT2_ext(asrc, a, 1, gmsrc, gm, logn, p, p0i);
+        }
+        void modp_iNTT2(uint[] asrc, int a, uint[] igmsrc, int igm, uint logn, uint p, uint p0i) {
+            this.modp_iNTT2_ext(asrc, a, 1, igmsrc, igm, logn, p, p0i);
+        }
+
+        /*
+        * Given polynomial f in NTT representation modulo p, compute f' of degree
+        * less than N/2 such that f' = f0^2 - X*f1^2, where f0 and f1 are
+        * polynomials of degree less than N/2 such that f = f0(X^2) + X*f1(X^2).
+        *
+        * The new polynomial is written "in place" over the first N/2 elements
+        * of f.
+        *
+        * If applied logn times successively on a given polynomial, the resulting
+        * degree-0 polynomial is the resultant of f and X^N+1 modulo p.
+        *
+        * This function applies only to the binary case; it is invoked from
+        * solve_NTRU_binary_depth1().
+        */
+        void modp_poly_rec_res(uint[] fsrc, int f, uint logn,
+            uint p, uint p0i, uint R2)
+        {
+            int hn, u;
+
+            hn = (int)1 << (int)(logn - 1);
+            for (u = 0; u < hn; u ++) {
+                uint w0, w1;
+
+                w0 = fsrc[f + (u << 1) + 0];
+                w1 = fsrc[f + (u << 1) + 1];
+                fsrc[f + u] = modp_montymul(modp_montymul(w0, w1, p, p0i), R2, p, p0i);
+            }
+        }
+
+        /* ==================================================================== */
+        /*
+        * Custom bignum implementation.
+        *
+        * This is a very reduced set of functionalities. We need to do the
+        * following operations:
+        *
+        *  - Rebuild the resultant and the polynomial coefficients from their
+        *    values modulo small primes (of length 31 bits each).
+        *
+        *  - Compute an extended GCD between the two computed resultants.
+        *
+        *  - Extract top bits and add scaled values during the successive steps
+        *    of Babai rounding.
+        *
+        * When rebuilding values using CRT, we must also recompute the product
+        * of the small prime factors. We always do it one small factor at a
+        * time, so the "complicated" operations can be done modulo the small
+        * prime with the modp_* functions. CRT coefficients (inverses) are
+        * precomputed.
+        *
+        * All values are positive until the last step: when the polynomial
+        * coefficients have been rebuilt, we normalize them around 0. But then,
+        * only additions and subtractions on the upper few bits are needed
+        * afterwards.
+        *
+        * We keep big integers as arrays of 31-bit words (in uint values);
+        * the top bit of each uint is kept equal to 0. Using 31-bit words
+        * makes it easier to keep track of carries. When negative values are
+        * used, two's complement is used.
+        */
+
+        /*
+        * Subtract integer b from integer a. Both integers are supposed to have
+        * the same size. The carry (0 or 1) is returned. Source arrays a and b
+        * MUST be distinct.
+        *
+        * The operation is performed as described above if ctr = 1. If
+        * ctl = 0, the value a[] is unmodified, but all memory accesses are
+        * still performed, and the carry is computed and returned.
+        */
+        uint zint_sub(uint[] asrc, int a, uint[] bsrc, int b, int len,
+            uint ctl)
+        {
+            int u;
+            uint cc, m;
+
+            cc = 0;
+            m = (uint)(-ctl);
+            for (u = 0; u < len; u ++) {
+                uint aw, w;
+
+                aw = asrc[a + u];
+                w = aw - bsrc[b + u] - cc;
+                cc = w >> 31;
+                aw ^= ((w & 0x7FFFFFFF) ^ aw) & m;
+                asrc[a + u] = aw;
+            }
+            return cc;
+        }
+
+        /*
+        * Mutiply the provided big integer m with a small value x.
+        * This function assumes that x < 2^31. The carry word is returned.
+        */
+        uint zint_mul_small(uint[] msrc, int m, int mlen, uint x)
+        {
+            int u;
+            uint cc;
+
+            cc = 0;
+            for (u = 0; u < mlen; u ++) {
+                ulong z;
+
+                z = (ulong)msrc[m+u] * (ulong)x + cc;
+                msrc[m+u] = (uint)z & 0x7FFFFFFF;
+                cc = (uint)(z >> 31);
+            }
+            return cc;
+        }
+
+        /*
+        * Reduce a big integer d modulo a small integer p.
+        * Rules:
+        *  d is uint
+        *  p is prime
+        *  2^30 < p < 2^31
+        *  p0i = -(1/p) mod 2^31
+        *  R2 = 2^62 mod p
+        */
+        uint zint_mod_small_uint(uint[] dsrc, int d, int dlen,
+            uint p, uint p0i, uint R2)
+        {
+            uint x;
+            int u;
+
+            /*
+            * Algorithm: we inject words one by one, starting with the high
+            * word. Each step is:
+            *  - multiply x by 2^31
+            *  - add new word
+            */
+            x = 0;
+            u = dlen;
+            while (u -- > 0) {
+                uint w;
+
+                x = modp_montymul(x, R2, p, p0i);
+                w = dsrc[d+u] - p;
+                w += (uint)(p & -(w >> 31));
+                x = modp_add(x, w, p);
+            }
+            return x;
+        }
+
+        /*
+        * Similar to zint_mod_small_uint(), except that d may be signed.
+        * Extra parameter is Rx = 2^(31*dlen) mod p.
+        */
+        uint zint_mod_small_signed(uint[] dsrc, int d, int dlen,
+            uint p, uint p0i, uint R2, uint Rx)
+        {
+            uint z;
+
+            if (dlen == 0) {
+                return 0;
+            }
+            z = zint_mod_small_uint(dsrc, d, dlen, p, p0i, R2);
+            z = modp_sub(z, (uint)(Rx & -(dsrc[d + dlen - 1] >> 30)), p);
+            return z;
+        }
+
+        /*
+        * Add y*s to x. x and y initially have length 'len' words; the new x
+        * has length 'len+1' words. 's' must fit on 31 bits. x[] and y[] must
+        * not overlap.
+        */
+        void zint_add_mul_small(uint[] xsrc, int x,
+            uint[] ysrc, int y, int len, uint s)
+        {
+            int u;
+            uint cc;
+
+            cc = 0;
+            for (u = 0; u < len; u ++) {
+                uint xw, yw;
+                ulong z;
+
+                xw = xsrc[x+u];
+                yw = ysrc[y+u];
+                z = (ulong)yw * (ulong)s + (ulong)xw + (ulong)cc;
+                xsrc[x+u] = (uint)z & 0x7FFFFFFF;
+                cc = (uint)(z >> 31);
+            }
+            xsrc[x+len] = cc;
+        }
+
+        /*
+        * Normalize a modular integer around 0: if x > p/2, then x is replaced
+        * with x - p (signed encoding with two's complement); otherwise, x is
+        * untouched. The two integers x and p are encoded over the same length.
+        */
+        void zint_norm_zero(uint[] xsrc, int x, uint[] psrc, int p, int len)
+        {
+            int u;
+            uint r, bb;
+
+            /*
+            * Compare x with p/2. We use the shifted version of p, and p
+            * is odd, so we really compare with (p-1)/2; we want to perform
+            * the subtraction if and only if x > (p-1)/2.
+            */
+            r = 0;
+            bb = 0;
+            u = len;
+            while (u -- > 0) {
+                uint wx, wp, cc;
+
+                /*
+                * Get the two words to compare in wx and wp (both over
+                * 31 bits exactly).
+                */
+                wx = xsrc[x+u];
+                wp = (psrc[p+u] >> 1) | (bb << 30);
+                bb = psrc[p+u] & 1;
+
+                /*
+                * We set cc to -1, 0 or 1, depending on whether wp is
+                * lower than, equal to, or greater than wx.
+                */
+                cc = wp - wx;
+                cc = (uint)(((uint)(-cc) >> 31) | (uint)-(cc >> 31));
+
+                /*
+                * If r != 0 then it is either 1 or -1, and we keep its
+                * value. Otherwise, if r = 0, then we replace it with cc.
+                */
+                r |= cc & ((r & 1) - 1);
+            }
+
+            /*
+            * At this point, r = -1, 0 or 1, depending on whether (p-1)/2
+            * is lower than, equal to, or greater than x. We thus want to
+            * do the subtraction only if r = -1.
+            */
+            zint_sub(xsrc, x, psrc, p, len, r >> 31);
+        }
+
+        /*
+        * Rebuild integers from their RNS representation. There are 'num'
+        * integers, and each consists in 'xlen' words. 'xx' points at that
+        * first word of the first integer; subsequent integers are accessed
+        * by adding 'xstride' repeatedly.
+        *
+        * The words of an integer are the RNS representation of that integer,
+        * using the provided 'primes' are moduli. This function replaces
+        * each integer with its multi-word value (little-endian order).
+        *
+        * If "normalize_signed" is non-zero, then the returned value is
+        * normalized to the -m/2..m/2 interval (where m is the product of all
+        * small prime moduli); two's complement is used for negative values.
+        */
+        void zint_rebuild_CRT(uint[] xxsrc, int xx, int xlen, int xstride,
+            int num, FalconSmallPrime[] primes, int normalize_signed,
+            uint[] tmpsrc, int tmp)
+        {
+            int u;
+            int x;
+
+            tmpsrc[tmp + 0] = primes[0].p;
+            for (u = 1; u < xlen; u ++) {
+                /*
+                * At the entry of each loop iteration:
+                *  - the first u words of each array have been
+                *    reassembled;
+                *  - the first u words of tmp[] contains the
+                * product of the prime moduli processed so far.
+                *
+                * We call 'q' the product of all previous primes.
+                */
+                uint p, p0i, s, R2;
+                int v;
+
+                p = primes[u].p;
+                s = primes[u].s;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+
+                for (v = 0, x = xx; v < num; v ++, x += xstride) {
+                    uint xp, xq, xr;
+                    /*
+                    * xp = the integer x modulo the prime p for this
+                    *      iteration
+                    * xq = (x mod q) mod p
+                    */
+                    xp = xxsrc[x + u];
+                    xq = zint_mod_small_uint(xxsrc, x, u, p, p0i, R2);
+
+                    /*
+                    * New value is (x mod q) + q * (s * (xp - xq) mod p)
+                    */
+                    xr = modp_montymul(s, modp_sub(xp, xq, p), p, p0i);
+                    zint_add_mul_small(xxsrc, x, tmpsrc, tmp, u, xr);
+                }
+
+                /*
+                * Update product of primes in tmp[].
+                */
+                tmpsrc[tmp + u] = zint_mul_small(tmpsrc, tmp, u, p);
+            }
+
+            /*
+            * Normalize the reconstructed values around 0.
+            */
+            if (normalize_signed != 0) {
+                for (u = 0, x = xx; u < num; u ++, x += xstride) {
+                    zint_norm_zero(xxsrc, x, tmpsrc, tmp, xlen);
+                }
+            }
+        }
+
+        /*
+        * Negate a big integer conditionally: value a is replaced with -a if
+        * and only if ctl = 1. Control value ctl must be 0 or 1.
+        */
+        void zint_negate(uint[] asrc, int a, int len, uint ctl)
+        {
+            int u;
+            uint cc, m;
+
+            /*
+            * If ctl = 1 then we flip the bits of a by XORing with
+            * 0x7FFFFFFF, and we add 1 to the value. If ctl = 0 then we XOR
+            * with 0 and add 0, which leaves the value unchanged.
+            */
+            cc = ctl;
+            m = ((uint)-ctl >> 1);
+            for (u = 0; u < len; u ++) {
+                uint aw;
+
+                aw = asrc[a+u];
+                aw = (aw ^ m) + cc;
+                asrc[a+u] = aw & 0x7FFFFFFF;
+                cc = aw >> 31;
+            }
+        }
+
+        /*
+        * Replace a with (a*xa+b*xb)/(2^31) and b with (a*ya+b*yb)/(2^31).
+        * The low bits are dropped (the caller should compute the coefficients
+        * such that these dropped bits are all zeros). If either or both
+        * yields a negative value, then the value is negated.
+        *
+        * Returned value is:
+        *  0  both values were positive
+        *  1  new a had to be negated
+        *  2  new b had to be negated
+        *  3  both new a and new b had to be negated
+        *
+        * Coefficients xa, xb, ya and yb may use the full signed 32-bit range.
+        */
+        uint zint_co_reduce(uint[] asrc, int a, uint[] bsrc, int b, int len,
+            long xa, long xb, long ya, long yb)
+        {
+            int u;
+            long cca, ccb;
+            uint nega, negb;
+
+            cca = 0;
+            ccb = 0;
+            for (u = 0; u < len; u ++) {
+                uint wa, wb;
+                ulong za, zb;
+
+                wa = asrc[a + u];
+                wb = bsrc[b + u];
+                za = (ulong)((long)wa * xa + (long)wb * xb + cca);
+                zb = (ulong)((long)wa * ya + (long)wb * yb + ccb);
+                if (u > 0) {
+                    asrc[a + u - 1] = (uint)za & 0x7FFFFFFF;
+                    bsrc[b + u - 1] = (uint)zb & 0x7FFFFFFF;
+                }
+                //cca = *(long *)&za >> 31;
+                cca = (long)za >> 31;
+                ccb = (long)zb >> 31;
+                //ccb = *(long *)&zb >> 31;
+            }
+            asrc[a + len - 1] = (uint)cca;
+            bsrc[b + len - 1] = (uint)ccb;
+
+            nega = (uint)((ulong)cca >> 63);
+            negb = (uint)((ulong)ccb >> 63);
+            zint_negate(asrc, a, len, nega);
+            zint_negate(bsrc, b, len, negb);
+            return nega | (negb << 1);
+        }
+
+        /*
+        * Finish modular reduction. Rules on input parameters:
+        *
+        *   if neg = 1, then -m <= a < 0
+        *   if neg = 0, then 0 <= a < 2*m
+        *
+        * If neg = 0, then the top word of a[] is allowed to use 32 bits.
+        *
+        * Modulus m must be odd.
+        */
+        void zint_finish_mod(uint[] asrc, int a, int len, uint[] msrc, int m, uint neg)
+        {
+            int u;
+            uint cc, xm, ym;
+
+            /*
+            * First pass: compare a (assumed nonnegative) with m. Note that
+            * if the top word uses 32 bits, subtracting m must yield a
+            * value less than 2^31 since a < 2*m.
+            */
+            cc = 0;
+            for (u = 0; u < len; u ++) {
+                cc = (asrc[a+u] - msrc[m+u] - cc) >> 31;
+            }
+
+            /*
+            * If neg = 1 then we must add m (regardless of cc)
+            * If neg = 0 and cc = 0 then we must subtract m
+            * If neg = 0 and cc = 1 then we must do nothing
+            *
+            * In the loop below, we conditionally subtract either m or -m
+            * from a. Word xm is a word of m (if neg = 0) or -m (if neg = 1);
+            * but if neg = 0 and cc = 1, then ym = 0 and it forces mw to 0.
+            */
+            xm = ((uint)-neg >> 1);
+            ym = (uint)(-(neg | (1 - cc)));
+            cc = neg;
+            for (u = 0; u < len; u ++) {
+                uint aw, mw;
+
+                aw = asrc[a+u];
+                mw = (msrc[m+u] ^ xm) & ym;
+                aw = aw - mw - cc;
+                asrc[a+u] = aw & 0x7FFFFFFF;
+                cc = aw >> 31;
+            }
+        }
+
+        /*
+        * Replace a with (a*xa+b*xb)/(2^31) mod m, and b with
+        * (a*ya+b*yb)/(2^31) mod m. Modulus m must be odd; m0i = -1/m[0] mod 2^31.
+        */
+        void zint_co_reduce_mod(uint[] asrc, int a, uint[] bsrc, int b, uint[] msrc, int m, int len,
+            uint m0i, long xa, long xb, long ya, long yb)
+        {
+            int u;
+            long cca, ccb;
+            uint fa, fb;
+
+            /*
+            * These are actually four combined Montgomery multiplications.
+            */
+            cca = 0;
+            ccb = 0;
+            fa = ((asrc[a + 0] * (uint)xa + bsrc[b + 0] * (uint)xb) * m0i) & 0x7FFFFFFF;
+            fb = ((asrc[a + 0] * (uint)ya + bsrc[b + 0] * (uint)yb) * m0i) & 0x7FFFFFFF;
+            for (u = 0; u < len; u ++) {
+                uint wa, wb;
+                ulong za, zb;
+
+                wa = asrc[a + u];
+                wb = bsrc[b + u];
+                //za = wa * (ulong)xa + wb * (ulong)xb
+                //    + msrc[m + u] * (ulong)fa + (ulong)cca;
+                //zb = wa * (ulong)ya + wb * (ulong)yb
+                //    + msrc[m + u] * (ulong)fb + (ulong)ccb;
+                za = (ulong)((long)wa * xa + (long)wb * xb
+                        + (long)msrc[m + u] * fa + cca);
+                zb = (ulong)((long)wa * ya + (long)wb * yb
+                        + (long)msrc[m + u] * fb + ccb);
+                if (u > 0) {
+                    asrc[a + u - 1] = (uint)za & 0x7FFFFFFF;
+                    bsrc[b + u - 1] = (uint)zb & 0x7FFFFFFF;
+                }
+                //cca = *(long *)&za >> 31;
+                //ccb = *(long *)&zb >> 31;
+                cca = (long)za >> 31;
+                ccb = (long)zb >> 31;
+            }
+            asrc[a + len - 1] = (uint)cca;
+            bsrc[b + len - 1] = (uint)ccb;
+
+            /*
+            * At this point:
+            *   -m <= a < 2*m
+            *   -m <= b < 2*m
+            * (this is a case of Montgomery reduction)
+            * The top words of 'a' and 'b' may have a 32-th bit set.
+            * We want to add or subtract the modulus, as required.
+            */
+            zint_finish_mod(asrc, a, len, msrc, m, (uint)((ulong)cca >> 63));
+            zint_finish_mod(bsrc, b, len, msrc, m, (uint)((ulong)ccb >> 63));
+        }
+
+        /*
+        * Compute a GCD between two positive big integers x and y. The two
+        * integers must be odd. Returned value is 1 if the GCD is 1, 0
+        * otherwise. When 1 is returned, arrays u and v are filled with values
+        * such that:
+        *   0 <= u <= y
+        *   0 <= v <= x
+        *   x*u - y*v = 1
+        * x[] and y[] are unmodified. Both input values must have the same
+        * encoded length. Temporary array must be large enough to accommodate 4
+        * extra values of that length. Arrays u, v and tmp may not overlap with
+        * each other, or with either x or y.
+        */
+        int zint_bezout(uint[] usrc, int u, uint[] vsrc, int v,
+            uint[] xsrc, int x, uint[] ysrc, int y,
+            int len, uint[] tmpsrc, int tmp)
+        {
+            /*
+            * Algorithm is an extended binary GCD. We maintain 6 values
+            * a, b, u0, u1, v0 and v1 with the following invariants:
+            *
+            *  a = x*u0 - y*v0
+            *  b = x*u1 - y*v1
+            *  0 <= a <= x
+            *  0 <= b <= y
+            *  0 <= u0 < y
+            *  0 <= v0 < x
+            *  0 <= u1 <= y
+            *  0 <= v1 < x
+            *
+            * Initial values are:
+            *
+            *  a = x   u0 = 1   v0 = 0
+            *  b = y   u1 = y   v1 = x-1
+            *
+            * Each iteration reduces either a or b, and maintains the
+            * invariants. Algorithm stops when a = b, at which point their
+            * common value is GCD(a,b) and (u0,v0) (or (u1,v1)) contains
+            * the values (u,v) we want to return.
+            *
+            * The formal definition of the algorithm is a sequence of steps:
+            *
+            *  - If a is even, then:
+            *        a <- a/2
+            *        u0 <- u0/2 mod y
+            *        v0 <- v0/2 mod x
+            *
+            *  - Otherwise, if b is even, then:
+            *        b <- b/2
+            *        u1 <- u1/2 mod y
+            *        v1 <- v1/2 mod x
+            *
+            *  - Otherwise, if a > b, then:
+            *        a <- (a-b)/2
+            *        u0 <- (u0-u1)/2 mod y
+            *        v0 <- (v0-v1)/2 mod x
+            *
+            *  - Otherwise:
+            *        b <- (b-a)/2
+            *        u1 <- (u1-u0)/2 mod y
+            *        v1 <- (v1-v0)/2 mod y
+            *
+            * We can show that the operations above preserve the invariants:
+            *
+            *  - If a is even, then u0 and v0 are either both even or both
+            *    odd (since a = x*u0 - y*v0, and x and y are both odd).
+            *    If u0 and v0 are both even, then (u0,v0) <- (u0/2,v0/2).
+            *    Otherwise, (u0,v0) <- ((u0+y)/2,(v0+x)/2). Either way,
+            *    the a = x*u0 - y*v0 invariant is preserved.
+            *
+            *  - The same holds for the case where b is even.
+            *
+            *  - If a and b are odd, and a > b, then:
+            *
+            *      a-b = x*(u0-u1) - y*(v0-v1)
+            *
+            *    In that situation, if u0 < u1, then x*(u0-u1) < 0, but
+            *    a-b > 0; therefore, it must be that v0 < v1, and the
+            *    first part of the update is: (u0,v0) <- (u0-u1+y,v0-v1+x),
+            *    which preserves the invariants. Otherwise, if u0 > u1,
+            *    then u0-u1 >= 1, thus x*(u0-u1) >= x. But a <= x and
+            *    b >= 0, hence a-b <= x. It follows that, in that case,
+            *    v0-v1 >= 0. The first part of the update is then:
+            *    (u0,v0) <- (u0-u1,v0-v1), which again preserves the
+            *    invariants.
+            *
+            *    Either way, once the subtraction is done, the new value of
+            *    a, which is the difference of two odd values, is even,
+            *    and the remaining of this step is a subcase of the
+            *    first algorithm case (i.e. when a is even).
+            *
+            *  - If a and b are odd, and b > a, then the a similar
+            *    argument holds.
+            *
+            * The values a and b start at x and y, respectively. Since x
+            * and y are odd, their GCD is odd, and it is easily seen that
+            * all steps conserve the GCD (GCD(a-b,b) = GCD(a, b);
+            * GCD(a/2,b) = GCD(a,b) if GCD(a,b) is odd). Moreover, either a
+            * or b is reduced by at least one bit at each iteration, so
+            * the algorithm necessarily converges on the case a = b, at
+            * which point the common value is the GCD.
+            *
+            * In the algorithm expressed above, when a = b, the fourth case
+            * applies, and sets b = 0. Since a contains the GCD of x and y,
+            * which are both odd, a must be odd, and subsequent iterations
+            * (if any) will simply divide b by 2 repeatedly, which has no
+            * consequence. Thus, the algorithm can run for more iterations
+            * than necessary; the final GCD will be in a, and the (u,v)
+            * coefficients will be (u0,v0).
+            *
+            *
+            * The presentation above is bit-by-bit. It can be sped up by
+            * noticing that all decisions are taken based on the low bits
+            * and high bits of a and b. We can extract the two top words
+            * and low word of each of a and b, and compute reduction
+            * parameters pa, pb, qa and qb such that the new values for
+            * a and b are:
+            *    a' = (a*pa + b*pb) / (2^31)
+            *    b' = (a*qa + b*qb) / (2^31)
+            * the two divisions being exact. The coefficients are obtained
+            * just from the extracted words, and may be slightly off, requiring
+            * an optional correction: if a' < 0, then we replace pa with -pa
+            * and pb with -pb. Each such step will reduce the total length
+            * (sum of lengths of a and b) by at least 30 bits at each
+            * iteration.
+            */
+            int u0, u1, v0, v1, a, b;
+            uint x0i, y0i;
+            uint num, rc;
+            int j;
+
+            if (len == 0) {
+                return 0;
+            }
+
+            /*
+            * u0 and v0 are the u and v result buffers; the four other
+            * values (u1, v1, a and b) are taken from tmp[].
+            */
+            u0 = u; // usrc
+            v0 = v; // vsrc
+            u1 = tmp; // tmpsrc
+            v1 = u1 + len; // tmpsrc
+            a = v1 + len; // tmpsrc
+            b = a + len; // tmpsrc
+
+            /*
+            * We'll need the Montgomery reduction coefficients.
+            */
+            x0i = modp_ninv31(xsrc[x + 0]);
+            y0i = modp_ninv31(ysrc[y + 0]);
+
+            /*
+            * Initialize a, b, u0, u1, v0 and v1.
+            *  a = x   u0 = 1   v0 = 0
+            *  b = y   u1 = y   v1 = x-1
+            * Note that x is odd, so computing x-1 is easy.
+            */
+            // memcpy(a, x, len * sizeof *x);
+            Array.Copy(xsrc, x, tmpsrc, a, len);
+            // memcpy(b, y, len * sizeof *y);
+            Array.Copy(ysrc, y, tmpsrc, b, len);
+            usrc[u0+0] = 1;
+            // memset(u0 + 1, 0, (len - 1) * sizeof *u0);
+            // memset(v0, 0, len * sizeof *v0);
+            for (int i = 1; i < len; i++) {
+                usrc[u0+i] = 0;
+                vsrc[v0+i] = 0;
+            }
+            vsrc[v0+0] = 0;
+            // memcpy(u1, y, len * sizeof *u1);
+            Array.Copy(ysrc, y, tmpsrc, u1, len);
+            // memcpy(v1, x, len * sizeof *v1);
+            Array.Copy(xsrc, x, tmpsrc, v1, len);
+            tmpsrc[v1+0] --;
+
+            /*
+            * Each input operand may be as large as 31*len bits, and we
+            * reduce the total length by at least 30 bits at each iteration.
+            */
+            for (num = 62 * (uint)len + 30; num >= 30; num -= 30) {
+                uint c0, c1;
+                uint a0, a1, b0, b1;
+                ulong a_hi, b_hi;
+                uint a_lo, b_lo;
+                long pa, pb, qa, qb;
+                int i;
+                uint r;
+
+                /*
+                * Extract the top words of a and b. If j is the highest
+                * index >= 1 such that a[j] != 0 or b[j] != 0, then we
+                * want (a[j] << 31) + a[j-1] and (b[j] << 31) + b[j-1].
+                * If a and b are down to one word each, then we use
+                * a[0] and b[0].
+                */
+                //c0 = (uint)-1;
+                //c1 = (uint)-1;
+                c0 = uint.MaxValue;
+                c1 = uint.MaxValue;
+                a0 = 0;
+                a1 = 0;
+                b0 = 0;
+                b1 = 0;
+                j = len;
+                while (j -- > 0) {
+                    uint aw, bw;
+
+                    aw = tmpsrc[a+j];
+                    bw = tmpsrc[b+j];
+                    a0 ^= (a0 ^ aw) & c0;
+                    a1 ^= (a1 ^ aw) & c1;
+                    b0 ^= (b0 ^ bw) & c0;
+                    b1 ^= (b1 ^ bw) & c1;
+                    c1 = c0;
+                    c0 &= (((aw | bw) + 0x7FFFFFFF) >> 31) - (uint)1;
+                }
+
+                /*
+                * If c1 = 0, then we grabbed two words for a and b.
+                * If c1 != 0 but c0 = 0, then we grabbed one word. It
+                * is not possible that c1 != 0 and c0 != 0, because that
+                * would mean that both integers are zero.
+                */
+                a1 |= a0 & c1;
+                a0 &= ~c1;
+                b1 |= b0 & c1;
+                b0 &= ~c1;
+                a_hi = ((ulong)a0 << 31) + a1;
+                b_hi = ((ulong)b0 << 31) + b1;
+                a_lo = tmpsrc[a+0];
+                b_lo = tmpsrc[b+0];
+
+                /*
+                * Compute reduction factors:
+                *
+                *   a' = a*pa + b*pb
+                *   b' = a*qa + b*qb
+                *
+                * such that a' and b' are both multiple of 2^31, but are
+                * only marginally larger than a and b.
+                */
+                pa = 1;
+                pb = 0;
+                qa = 0;
+                qb = 1;
+                for (i = 0; i < 31; i ++) {
+                    /*
+                    * At each iteration:
+                    *
+                    *   a <- (a-b)/2 if: a is odd, b is odd, a_hi > b_hi
+                    *   b <- (b-a)/2 if: a is odd, b is odd, a_hi <= b_hi
+                    *   a <- a/2 if: a is even
+                    *   b <- b/2 if: a is odd, b is even
+                    *
+                    * We multiply a_lo and b_lo by 2 at each
+                    * iteration, thus a division by 2 really is a
+                    * non-multiplication by 2.
+                    */
+                    uint rt, oa, ob, cAB, cBA, cA;
+                    ulong rz;
+
+                    /*
+                    * rt = 1 if a_hi > b_hi, 0 otherwise.
+                    */
+                    rz = b_hi - a_hi;
+                    rt = (uint)((rz ^ ((a_hi ^ b_hi)
+                        & (a_hi ^ rz))) >> 63);
+
+                    /*
+                    * cAB = 1 if b must be subtracted from a
+                    * cBA = 1 if a must be subtracted from b
+                    * cA = 1 if a must be divided by 2
+                    *
+                    * Rules:
+                    *
+                    *   cAB and cBA cannot both be 1.
+                    *   If a is not divided by 2, b is.
+                    */
+                    oa = (a_lo >> i) & 1;
+                    ob = (b_lo >> i) & 1;
+                    cAB = oa & ob & rt;
+                    cBA = (uint)(oa & ob & ~(int)rt);
+                    cA = cAB | (oa ^ 1);
+
+                    /*
+                    * Conditional subtractions.
+                    */
+                    a_lo -= (uint)(b_lo & -cAB);
+                    a_hi -= b_hi & (ulong)-(long)cAB;
+                    pa -= (qa & -(long)cAB);
+                    pb -= (qb & -(long)cAB);
+                    b_lo -= (uint)(a_lo & -cBA);
+                    b_hi -= a_hi & (ulong)-(long)cBA;
+                    qa -= pa & -(long)cBA;
+                    qb -= pb & -(long)cBA;
+
+                    /*
+                    * Shifting.
+                    */
+                    a_lo += a_lo & (cA - 1);
+                    pa += pa & ((long)cA - 1);
+                    pb += pb & ((long)cA - 1);
+                    a_hi ^= (a_hi ^ (a_hi >> 1)) & (ulong)-(long)cA;
+                    b_lo += (uint)(b_lo & -cA);
+                    qa += qa & -(long)cA;
+                    qb += qb & -(long)cA;
+                    b_hi ^= (b_hi ^ (b_hi >> 1)) & ((ulong)cA - 1);
+                }
+
+                /*
+                * Apply the computed parameters to our values. We
+                * may have to correct pa and pb depending on the
+                * returned value of zint_co_reduce() (when a and/or b
+                * had to be negated).
+                */
+                r = zint_co_reduce(tmpsrc, a, tmpsrc, b, len, pa, pb, qa, qb);
+                pa -= (pa + pa) & -(long)(r & 1);
+                pb -= (pb + pb) & -(long)(r & 1);
+                qa -= (qa + qa) & -(long)(r >> 1);
+                qb -= (qb + qb) & -(long)(r >> 1);
+                zint_co_reduce_mod(usrc, u0, tmpsrc, u1, ysrc, y, len, y0i, pa, pb, qa, qb);
+                zint_co_reduce_mod(vsrc, v0, tmpsrc, v1, xsrc, x, len, x0i, pa, pb, qa, qb);
+            }
+
+            /*
+            * At that point, array a[] should contain the GCD, and the
+            * results (u,v) should already be set. We check that the GCD
+            * is indeed 1. We also check that the two operands x and y
+            * are odd.
+            */
+            rc = tmpsrc[a+0] ^ 1;
+            for (j = 1; j < len; j ++) {
+                rc |= tmpsrc[a+j];
+            }
+            return (int)((1 - ((rc | -rc) >> 31)) & xsrc[x+0] & ysrc[y+0]);
+        }
+
+        /*
+        * Add k*y*2^sc to x. The result is assumed to fit in the array of
+        * size xlen (truncation is applied if necessary).
+        * Scale factor 'sc' is provided as sch and scl, such that:
+        *   sch = sc / 31
+        *   scl = sc % 31
+        * xlen MUST NOT be lower than ylen.
+        *
+        * x[] and y[] are both signed integers, using two's complement for
+        * negative values.
+        */
+        void zint_add_scaled_mul_small(uint[] xsrc, int x, int xlen,
+            uint[] ysrc, int y, int ylen, int k,
+            uint sch, uint scl)
+        {
+            int u;
+            uint ysign, tw;
+            int cc;
+
+            if (ylen == 0) {
+                return;
+            }
+
+            ysign = ((uint)-(ysrc[y + ylen - 1] >> 30) >> 1);
+            tw = 0;
+            cc = 0;
+            for (u = (int)sch; u < xlen; u ++) {
+                int v;
+                uint wy, wys, ccu;
+                ulong z;
+
+                /*
+                * Get the next word of y (scaled).
+                */
+                v = u - (int)sch;
+                wy = v < ylen ? ysrc[y + v] : ysign;
+                wys = ((wy << (int)scl) & 0x7FFFFFFF) | tw;
+                tw = wy >> (31 - (int)scl);
+
+                /*
+                * The expression below does not overflow.
+                */
+                z = (ulong)((long)wys * (long)k + (long)xsrc[x+u] + cc);
+                xsrc[x+u] = (uint)z & 0x7FFFFFFF;
+
+                /*
+                * Right-shifting the signed value z would yield
+                * implementation-defined results (arithmetic shift is
+                * not guaranteed). However, we can cast to uint,
+                * and get the next carry as an uint word. We can
+                * then convert it back to signed by using the guaranteed
+                * fact that 'int' uses two's complement with no
+                * trap representation or padding bit, and with a layout
+                * compatible with that of 'uint'.
+                */
+                ccu = (uint)(z >> 31);
+                //cc = *(int *)&ccu;
+                cc = (int)ccu;
+            }
+        }
+
+        /*
+        * Subtract y*2^sc from x. The result is assumed to fit in the array of
+        * size xlen (truncation is applied if necessary).
+        * Scale factor 'sc' is provided as sch and scl, such that:
+        *   sch = sc / 31
+        *   scl = sc % 31
+        * xlen MUST NOT be lower than ylen.
+        *
+        * x[] and y[] are both signed integers, using two's complement for
+        * negative values.
+        */
+        void zint_sub_scaled(uint[] xsrc, int x, int xlen,
+            uint[] ysrc, int y, int ylen, uint sch, uint scl)
+        {
+            int u;
+            uint ysign, tw;
+            uint cc;
+
+            if (ylen == 0) {
+                return;
+            }
+
+            ysign = (uint)(-(ysrc[y + ylen - 1] >> 30) >> 1);
+            tw = 0;
+            cc = 0;
+            for (u = (int)sch; u < xlen; u ++) {
+                int v;
+                uint w, wy, wys;
+
+                /*
+                * Get the next word of y (scaled).
+                */
+                v = u - (int)sch;
+                wy = v < ylen ? ysrc[y + v] : ysign;
+                wys = ((wy << (int)scl) & 0x7FFFFFFF) | tw;
+                tw = wy >> (int)(31 - scl);
+
+                w = xsrc[x+u] - wys - cc;
+                xsrc[x+u] = w & 0x7FFFFFFF;
+                cc = w >> 31;
+            }
+        }
+
+        /*
+        * Convert a one-word signed big integer into a signed value.
+        */
+        int zint_one_to_plain(uint[] xsrc, int x)
+        {
+            uint w;
+
+            w = xsrc[x+0];
+            w |= (w & 0x40000000) << 1;
+            //return *(int *)&w;
+            return (int)w;
+        }
+
+        /* ==================================================================== */
+
+        /*
+        * Convert a polynomial to floating-point values.
+        *
+        * Each coefficient has length flen words, and starts fstride words after
+        * the previous.
+        *
+        * IEEE-754 binary64 values can represent values in a finite range,
+        * roughly 2^(-1023) to 2^(+1023); thus, if coefficients are too large,
+        * they should be "trimmed" by pointing not to the lowest word of each,
+        * but upper.
+        */
+        void poly_big_to_fp(FalconFPR[] dsrc, int d, uint[] fsrc, int f, int flen, int fstride,
+            uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            if (flen == 0) {
+                for (u = 0; u < n; u ++) {
+                    dsrc[d + u] = this.fpre.fpr_zero;
+                }
+                return;
+            }
+            for (u = 0; u < n; u ++, f += fstride) {
+                int v;
+                uint neg, cc, xm;
+                FalconFPR x, fsc;
+
+                /*
+                * Get sign of the integer; if it is negative, then we
+                * will load its absolute value instead, and negate the
+                * result.
+                */
+                neg = (uint)(-(fsrc[f + flen - 1] >> 30));
+                xm = neg >> 1;
+                cc = neg & 1;
+                x = this.fpre.fpr_zero;
+                fsc = this.fpre.fpr_one;
+                for (v = 0; v < flen; v ++, fsc = this.fpre.fpr_mul(fsc, this.fpre.fpr_ptwo31)) {
+                    uint w;
+
+                    w = (fsrc[f + v] ^ xm) + cc;
+                    cc = w >> 31;
+                    w &= 0x7FFFFFFF;
+                    w -= (w << 1) & neg;
+                    //x = this.fpre.fpr_add(x, this.fpre.fpr_mul(this.fpre.fpr_of(*(int*)&w), fsc));
+                    x = this.fpre.fpr_add(x, this.fpre.fpr_mul(this.fpre.fpr_of((int)w), fsc));
+                }
+                dsrc[d + u] = x;
+            }
+        }
+
+        /*
+        * Convert a polynomial to small integers. Source values are supposed
+        * to be one-word integers, signed over 31 bits. Returned value is 0
+        * if any of the coefficients exceeds the provided limit (in absolute
+        * value), or 1 on success.
+        *
+        * This is not constant-time; this is not a problem here, because on
+        * any failure, the NTRU-solving process will be deemed to have failed
+        * and the (f,g) polynomials will be discarded.
+        */
+        int poly_big_to_small(sbyte[] dsrc, int d, uint[] ssrc, int s, int lim, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                int z;
+
+                z = zint_one_to_plain(ssrc, s + u);
+                if (z < -lim || z > lim) {
+                    return 0;
+                }
+                dsrc[d+u] = (sbyte)z;
+            }
+            return 1;
+        }
+
+        /*
+        * Subtract k*f from F, where F, f and k are polynomials modulo X^N+1.
+        * Coefficients of polynomial k are small integers (signed values in the
+        * -2^31..2^31 range) scaled by 2^sc. Value sc is provided as sch = sc / 31
+        * and scl = sc % 31.
+        *
+        * This function implements the basic quadratic multiplication algorithm,
+        * which is efficient in space (no extra buffer needed) but slow at
+        * high degree.
+        */
+        void poly_sub_scaled(uint[] Fsrc, int F, int Flen, int Fstride,
+            uint[] fsrc, int f, int flen, int fstride,
+            int[] ksrc, int k, uint sch, uint scl, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                int kf;
+                int v;
+                int x;
+                int y;
+
+                kf = -ksrc[k+u];
+                x = F + u * Fstride;
+                y = f;
+                for (v = 0; v < n; v ++) {
+                    zint_add_scaled_mul_small(
+                        Fsrc, x, Flen, fsrc, y, flen, kf, sch, scl);
+                    if (u + v == n - 1) {
+                        x = F;
+                        kf = -kf;
+                    } else {
+                        x += Fstride;
+                    }
+                    y += fstride;
+                }
+            }
+        }
+
+        /*
+        * Subtract k*f from F. Coefficients of polynomial k are small integers
+        * (signed values in the -2^31..2^31 range) scaled by 2^sc. This function
+        * assumes that the degree is large, and integers relatively small.
+        * The value sc is provided as sch = sc / 31 and scl = sc % 31.
+        */
+        void poly_sub_scaled_ntt(uint[] Fsrc, int F, int Flen, int Fstride,
+            uint[] fsrc, int f, int flen, int fstride,
+            int[] ksrc, int k, uint sch, uint scl, uint logn,
+            uint[] tmpsrc, int tmp)
+        {
+            int gm, igm, fk, t1, x;
+            int y;
+            int n, u, tlen;
+            FalconSmallPrime[] primes;
+
+            n = (int)1 << (int)logn;
+            tlen = flen + 1;
+            gm = tmp;
+            igm = gm + n;
+            fk = igm + n;
+            t1 = fk + n * tlen;
+
+            primes = this.PRIMES;
+
+            /*
+            * Compute k*f in fk[], in RNS notation.
+            */
+            for (u = 0; u < tlen; u ++) {
+                uint p, p0i, R2, Rx;
+                int v;
+
+                p = primes[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+                Rx = modp_Rx((uint)flen, p, p0i, R2);
+                modp_mkgm2(tmpsrc, gm, tmpsrc, igm, logn, primes[u].g, p, p0i);
+
+                for (v = 0; v < n; v ++) {
+                    tmpsrc[t1+v] = modp_set(ksrc[k+v], p);
+                }
+                modp_NTT2(tmpsrc, t1, tmpsrc, gm, logn, p, p0i);
+                for (v = 0, y = f, x = fk + u;
+                    v < n; v ++, y += fstride, x += tlen)
+                {
+                    tmpsrc[x] = zint_mod_small_signed(tmpsrc, y, flen, p, p0i, R2, Rx);
+                }
+                modp_NTT2_ext(tmpsrc, fk + u, tlen, tmpsrc, gm, logn, p, p0i);
+                for (v = 0, x = fk + u; v < n; v ++, x += tlen) {
+                    tmpsrc[x] = modp_montymul(
+                        modp_montymul(tmpsrc[t1+v], tmpsrc[x], p, p0i), R2, p, p0i);
+                }
+                modp_iNTT2_ext(tmpsrc, fk + u, tlen, tmpsrc, igm, logn, p, p0i);
+            }
+
+            /*
+            * Rebuild k*f.
+            */
+            zint_rebuild_CRT(tmpsrc, fk, tlen, tlen, n, primes, 1, tmpsrc, t1);
+
+            /*
+            * Subtract k*f, scaled, from F.
+            */
+            for (u = 0, x = F, y = fk; u < n; u ++, x += Fstride, y += tlen) {
+                zint_sub_scaled(tmpsrc, x, Flen, tmpsrc, y, tlen, sch, scl);
+            }
+        }
+
+        /* ==================================================================== */
+
+        /*
+        * Get a random 8-byte integer from a SHAKE-based RNG. This function
+        * ensures consistent interpretation of the SHAKE output so that
+        * the same values will be obtained over different platforms, in case
+        * a known seed is used.
+        */
+        ulong get_rng_u64(SHAKE256 rng)
+        {
+            /*
+            * We enforce little-endian representation.
+            */
+
+            byte[] tmp = new byte[8];
+
+            rng.i_shake256_extract(tmp, 0, 8);
+            return (ulong)tmp[0]
+                | ((ulong)tmp[1] << 8)
+                | ((ulong)tmp[2] << 16)
+                | ((ulong)tmp[3] << 24)
+                | ((ulong)tmp[4] << 32)
+                | ((ulong)tmp[5] << 40)
+                | ((ulong)tmp[6] << 48)
+                | ((ulong)tmp[7] << 56);
+        }
+
+
+        /*
+        * Table below incarnates a discrete Gaussian distribution:
+        *    D(x) = exp(-(x^2)/(2*sigma^2))
+        * where sigma = 1.17*sqrt(q/(2*N)), q = 12289, and N = 1024.
+        * Element 0 of the table is P(x = 0).
+        * For k > 0, element k is P(x >= k+1 | x > 0).
+        * Probabilities are scaled up by 2^63.
+        */
+        ulong[] gauss_1024_12289 = {
+            1283868770400643928u,  6416574995475331444u,  4078260278032692663u,
+            2353523259288686585u,  1227179971273316331u,   575931623374121527u,
+            242543240509105209u,    91437049221049666u,    30799446349977173u,
+                9255276791179340u,     2478152334826140u,      590642893610164u,
+                125206034929641u,       23590435911403u,        3948334035941u,
+                    586753615614u,          77391054539u,           9056793210u,
+                    940121950u,             86539696u,              7062824u,
+                        510971u,                32764u,                 1862u,
+                            94u,                    4u,                    0u
+        };
+
+        /*
+        * Generate a random value with a Gaussian distribution centered on 0.
+        * The RNG must be ready for extraction (already flipped).
+        *
+        * Distribution has standard deviation 1.17*sqrt(q/(2*N)). The
+        * precomputed table is for N = 1024. Since the sum of two independent
+        * values of standard deviation sigma has standard deviation
+        * sigma*sqrt(2), then we can just generate more values and add them
+        * together for lower dimensions.
+        */
+        int mkgauss(SHAKE256 rng, uint logn)
+        {
+            uint u, g;
+            int val;
+
+            g = 1U << (int)(10 - logn);
+            val = 0;
+            for (u = 0; u < g; u ++) {
+                /*
+                * Each iteration generates one value with the
+                * Gaussian distribution for N = 1024.
+                *
+                * We use two random 64-bit values. First value
+                * decides on whether the generated value is 0, and,
+                * if not, the sign of the value. Second random 64-bit
+                * word is used to generate the non-zero value.
+                *
+                * For constant-time code we have to read the complete
+                * table. This has negligible cost, compared with the
+                * remainder of the keygen process (solving the NTRU
+                * equation).
+                */
+                ulong r;
+                uint f, v, k, neg;
+
+                /*
+                * First value:
+                *  - flag 'neg' is randomly selected to be 0 or 1.
+                *  - flag 'f' is set to 1 if the generated value is zero,
+                *    or set to 0 otherwise.
+                */
+                r = get_rng_u64(rng);
+                neg = (uint)(r >> 63);
+                r &= ~((ulong)1 << 63);
+                f = (uint)((r - gauss_1024_12289[0]) >> 63);
+
+                /*
+                * We produce a new random 63-bit integer r, and go over
+                * the array, starting at index 1. We store in v the
+                * index of the first array element which is not greater
+                * than r, unless the flag f was already 1.
+                */
+                v = 0;
+                r = get_rng_u64(rng);
+                r &= ~((ulong)1 << 63);
+                for (k = 1; k < gauss_1024_12289.Length; k ++)
+                {
+                    uint t;
+
+                    t = (uint)((r - gauss_1024_12289[k]) >> 63) ^ 1;
+                    v |= (uint)(k & -(t & (f ^ 1)));
+                    f |= t;
+                }
+
+                /*
+                * We apply the sign ('neg' flag). If the value is zero,
+                * the sign has no effect.
+                */
+                v = (uint)((v ^ -neg) + neg);
+
+                /*
+                * Generated value is added to val.
+                */
+                //val += *(int *)&v;
+                val += (int)v;
+            }
+            return val;
+        }
+
+        /*
+        * The MAX_BL_SMALL[] and MAX_BL_LARGE[] contain the lengths, in 31-bit
+        * words, of intermediate values in the computation:
+        *
+        *   MAX_BL_SMALL[depth]: length for the input f and g at that depth
+        *   MAX_BL_LARGE[depth]: length for the unreduced F and G at that depth
+        *
+        * Rules:
+        *
+        *  - Within an array, values grow.
+        *
+        *  - The 'SMALL' array must have an entry for maximum depth, corresponding
+        *    to the size of values used in the binary GCD. There is no such value
+        *    for the 'LARGE' array (the binary GCD yields already reduced
+        *    coefficients).
+        *
+        *  - MAX_BL_LARGE[depth] >= MAX_BL_SMALL[depth + 1].
+        *
+        *  - Values must be large enough to handle the common cases, with some
+        *    margins.
+        *
+        *  - Values must not be "too large" either because we will convert some
+        *    integers into floating-point values by considering the top 10 words,
+        *    i.e. 310 bits; hence, for values of length more than 10 words, we
+        *    should take care to have the length centered on the expected size.
+        *
+        * The following average lengths, in bits, have been measured on thousands
+        * of random keys (fg = max length of the absolute value of coefficients
+        * of f and g at that depth; FG = idem for the unreduced F and G; for the
+        * maximum depth, F and G are the output of binary GCD, multiplied by q;
+        * for each value, the average and standard deviation are provided).
+        *
+        * Binary case:
+        *    depth: 10    fg: 6307.52 (24.48)    FG: 6319.66 (24.51)
+        *    depth:  9    fg: 3138.35 (12.25)    FG: 9403.29 (27.55)
+        *    depth:  8    fg: 1576.87 ( 7.49)    FG: 4703.30 (14.77)
+        *    depth:  7    fg:  794.17 ( 4.98)    FG: 2361.84 ( 9.31)
+        *    depth:  6    fg:  400.67 ( 3.10)    FG: 1188.68 ( 6.04)
+        *    depth:  5    fg:  202.22 ( 1.87)    FG:  599.81 ( 3.87)
+        *    depth:  4    fg:  101.62 ( 1.02)    FG:  303.49 ( 2.38)
+        *    depth:  3    fg:   50.37 ( 0.53)    FG:  153.65 ( 1.39)
+        *    depth:  2    fg:   24.07 ( 0.25)    FG:   78.20 ( 0.73)
+        *    depth:  1    fg:   10.99 ( 0.08)    FG:   39.82 ( 0.41)
+        *    depth:  0    fg:    4.00 ( 0.00)    FG:   19.61 ( 0.49)
+        *
+        * Integers are actually represented either in binary notation over
+        * 31-bit words (signed, using two's complement), or in RNS, modulo
+        * many small primes. These small primes are close to, but slightly
+        * lower than, 2^31. Use of RNS loses less than two bits, even for
+        * the largest values.
+        *
+        * IMPORTANT: if these values are modified, then the temporary buffer
+        * sizes (FALCON_KEYGEN_TEMP_*, in inner.h) must be recomputed
+        * accordingly.
+        */
+
+        int[] MAX_BL_SMALL = {
+            1, 1, 2, 2, 4, 7, 14, 27, 53, 106, 209
+        };
+
+        int[] MAX_BL_LARGE = {
+            2, 2, 5, 7, 12, 21, 40, 78, 157, 308
+        };
+
+        /*
+        * Average and standard deviation for the maximum size (in bits) of
+        * coefficients of (f,g), depending on depth. These values are used
+        * to compute bounds for Babai's reduction.
+        */
+        int[] BITLENGTH_avg = { // BITLENGTH[i][0] = avg, [i][1] = std
+                4,
+               11,
+               24,
+               50,
+              102,
+              202,
+              401,
+              794,
+             1577,
+             3138,
+             6308,
+        };
+        int[] BITLENGTH_std = { // BITLENGTH[i][0] = avg, [i][1] = std
+              0,
+              1,
+              1,
+              1,
+              1,
+              2,
+              4,
+              5,
+              8,
+             13,
+             25
+        };
+
+        /*
+        * Minimal recursion depth at which we rebuild intermediate values
+        * when reconstructing f and g.
+        */
+        const int DEPTH_INT_FG = 4;
+
+        /*
+        * Compute squared norm of a short vector. Returned value is saturated to
+        * 2^32-1 if it is not lower than 2^31.
+        */
+        uint poly_small_sqnorm(sbyte[] fsrc, int f, uint logn)
+        {
+            int n, u;
+            uint s, ng;
+
+            n = (int)1 << (int)logn;
+            s = 0;
+            ng = 0;
+            for (u = 0; u < n; u ++) {
+                int z;
+
+                z = fsrc[f+u];
+                s += (uint)(z * z);
+                ng |= s;
+            }
+            return (uint)(s | -(ng >> 31));
+        }
+
+        /*
+        * Convert a small vector to floating point.
+        */
+        void poly_small_to_fp(FalconFPR[] xsrc, int x, sbyte[] fsrc, int f, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                xsrc[x + u] = this.fpre.fpr_of(fsrc[f + u]);
+            }
+        }
+
+        /*
+        * Input: f,g of degree N = 2^logn; 'depth' is used only to get their
+        * individual length.
+        *
+        * Output: f',g' of degree N/2, with the length for 'depth+1'.
+        *
+        * Values are in RNS; input and/or output may also be in NTT.
+        */
+        void make_fg_step(uint[] datasrc, int data, uint logn, uint depth,
+            int in_ntt, int out_ntt)
+        {
+            int n, hn, u;
+            int slen, tlen;
+            int fd, gd;
+            int fs, gs;
+            int gm, igm, t1;
+            FalconSmallPrime[] primes;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            slen = MAX_BL_SMALL[depth];
+            tlen = MAX_BL_SMALL[depth + 1];
+            primes = this.PRIMES;
+
+            /*
+            * Prepare room for the result.
+            */
+            fd = data;
+            gd = fd + hn * tlen;
+            fs = gd + hn * tlen;
+            gs = fs + n * slen;
+            gm = gs + n * slen;
+            igm = gm + n;
+            t1 = igm + n;
+            // memmove(fs, data, 2 * n * slen * sizeof *data);
+            Array.Copy(datasrc, data, datasrc, fs, 2 * n * slen);
+
+            /*
+            * First slen words: we use the input values directly, and apply
+            * inverse NTT as we go.
+            */
+            for (u = 0; u < slen; u ++) {
+                uint p, p0i, R2;
+                int v;
+                int x;
+
+                p = primes[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+                modp_mkgm2(datasrc, gm, datasrc, igm, logn, primes[u].g, p, p0i);
+
+                for (v = 0, x = fs + u; v < n; v ++, x += slen) {
+                    datasrc[t1 + v] = datasrc[x];
+                }
+                if (in_ntt == 0) {
+                    modp_NTT2(datasrc, t1, datasrc, gm, logn, p, p0i);
+                }
+                for (v = 0, x = fd + u; v < hn; v ++, x += tlen) {
+                    uint w0, w1;
+
+                    w0 = datasrc[t1 + (v << 1) + 0];
+                    w1 = datasrc[t1 + (v << 1) + 1];
+                    datasrc[x] = modp_montymul(
+                        modp_montymul(w0, w1, p, p0i), R2, p, p0i);
+                }
+                if (in_ntt != 0) {
+                    modp_iNTT2_ext(datasrc, fs + u, slen, datasrc, igm, logn, p, p0i);
+                }
+
+                for (v = 0, x = gs + u; v < n; v ++, x += slen) {
+                    datasrc[t1 + v] = datasrc[x];
+                }
+                if (in_ntt == 0) {
+                    modp_NTT2(datasrc, t1, datasrc, gm, logn, p, p0i);
+                }
+                for (v = 0, x = gd + u; v < hn; v ++, x += tlen) {
+                    uint w0, w1;
+
+                    w0 = datasrc[t1 + (v << 1) + 0];
+                    w1 = datasrc[t1 + (v << 1) + 1];
+                    datasrc[x] = modp_montymul(
+                        modp_montymul(w0, w1, p, p0i), R2, p, p0i);
+                }
+                if (in_ntt != 0) {
+                    modp_iNTT2_ext(datasrc, gs + u, slen, datasrc, igm, logn, p, p0i);
+                }
+
+                if (out_ntt == 0) {
+                    modp_iNTT2_ext(datasrc, fd + u, tlen, datasrc, igm, logn - 1, p, p0i);
+                    modp_iNTT2_ext(datasrc, gd + u, tlen, datasrc, igm, logn - 1, p, p0i);
+                }
+            }
+
+            /*
+            * Since the fs and gs words have been de-NTTized, we can use the
+            * CRT to rebuild the values.
+            */
+            zint_rebuild_CRT(datasrc, fs, slen, slen, n, primes, 1, datasrc, gm);
+            zint_rebuild_CRT(datasrc, gs, slen, slen, n, primes, 1, datasrc, gm);
+
+            /*
+            * Remaining words: use modular reductions to extract the values.
+            */
+            for (u = slen; u < tlen; u ++) {
+                uint p, p0i, R2, Rx;
+                int v;
+                int x;
+
+                p = primes[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+                Rx = modp_Rx((uint)slen, p, p0i, R2);
+                modp_mkgm2(datasrc, gm, datasrc, igm, logn, primes[u].g, p, p0i);
+                for (v = 0, x = fs; v < n; v ++, x += slen) {
+                    datasrc[t1 + v] = zint_mod_small_signed(datasrc, x, slen, p, p0i, R2, Rx);
+                }
+                modp_NTT2(datasrc, t1, datasrc, gm, logn, p, p0i);
+                for (v = 0, x = fd + u; v < hn; v ++, x += tlen) {
+                    uint w0, w1;
+
+                    w0 = datasrc[t1 + (v << 1) + 0];
+                    w1 = datasrc[t1 + (v << 1) + 1];
+                    datasrc[x] = modp_montymul(
+                        modp_montymul(w0, w1, p, p0i), R2, p, p0i);
+                }
+                for (v = 0, x = gs; v < n; v ++, x += slen) {
+                    datasrc[t1 + v] = zint_mod_small_signed(datasrc, x, slen, p, p0i, R2, Rx);
+                }
+                modp_NTT2(datasrc, t1, datasrc, gm, logn, p, p0i);
+                for (v = 0, x = gd + u; v < hn; v ++, x += tlen) {
+                    uint w0, w1;
+
+                    w0 = datasrc[t1 + (v << 1) + 0];
+                    w1 = datasrc[t1 + (v << 1) + 1];
+                    datasrc[x] = modp_montymul(
+                        modp_montymul(w0, w1, p, p0i), R2, p, p0i);
+                }
+
+                if (out_ntt == 0) {
+                    modp_iNTT2_ext(datasrc, fd + u, tlen, datasrc, igm, logn - 1, p, p0i);
+                    modp_iNTT2_ext(datasrc, gd + u, tlen, datasrc, igm, logn - 1, p, p0i);
+                }
+            }
+        }
+
+        /*
+        * Compute f and g at a specific depth, in RNS notation.
+        *
+        * Returned values are stored in the data[] array, at slen words per integer.
+        *
+        * Conditions:
+        *   0 <= depth <= logn
+        *
+        * Space use in data[]: enough room for any two successive values (f', g',
+        * f and g).
+        */
+        void make_fg(uint[] datasrc, int data, sbyte[] fsrc, int f, sbyte[] gsrc, int g,
+            uint logn, uint depth, int out_ntt)
+        {
+            int n, u;
+            int ft, gt; 
+            uint p0;
+            uint d;
+            FalconSmallPrime[] primes;
+
+            n = (int)1 << (int)logn;
+            ft = data;
+            gt = ft + n;
+            primes = this.PRIMES;
+            p0 = primes[0].p;
+            for (u = 0; u < n; u ++) {
+                datasrc[ft + u] = modp_set(fsrc[f+u], p0);
+                datasrc[gt + u] = modp_set(gsrc[g+u], p0);
+            }
+
+            if (depth == 0 && out_ntt != 0) {
+                int gm, igm;
+                uint p, p0i;
+
+                p = primes[0].p;
+                p0i = modp_ninv31(p);
+                gm = gt + n;
+                igm = gm + n;
+                modp_mkgm2(datasrc, gm, datasrc, igm, logn, primes[0].g, p, p0i);
+                modp_NTT2(datasrc, ft, datasrc, gm, logn, p, p0i);
+                modp_NTT2(datasrc, gt, datasrc, gm, logn, p, p0i);
+                return;
+            }
+
+            for (d = 0; d < depth; d ++) {
+                make_fg_step(datasrc, data, logn - d, d,
+                    d != 0 ? 1 : 0, ((d + 1) < depth || out_ntt != 0)? 1 : 0);
+            }
+        }
+
+        /*
+        * Solving the NTRU equation, deepest level: compute the resultants of
+        * f and g with X^N+1, and use binary GCD. The F and G values are
+        * returned in tmp[].
+        *
+        * Returned value: 1 on success, 0 on error.
+        */
+        int solve_NTRU_deepest(uint logn_top,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, uint[] tmpsrc, int tmp)
+        {
+            int len;
+            int Fp, Gp; 
+            int fp, gp; 
+            int t1; 
+            uint q;
+            FalconSmallPrime[] primes;
+
+            len = MAX_BL_SMALL[logn_top];
+            primes = this.PRIMES;
+
+            Fp = tmp;
+            Gp = Fp + len;
+            fp = Gp + len;
+            gp = fp + len;
+            t1 = gp + len;
+
+            make_fg(tmpsrc, fp, fsrc, f, gsrc, g, logn_top, logn_top, 0);
+
+            /*
+            * We use the CRT to rebuild the resultants as big integers.
+            * There are two such big integers. The resultants are always
+            * nonnegative.
+            */
+            zint_rebuild_CRT(tmpsrc, fp, len, len, 2, primes, 0, tmpsrc, t1);
+
+            /*
+            * Apply the binary GCD. The zint_bezout() function works only
+            * if both inputs are odd.
+            *
+            * We can test on the result and return 0 because that would
+            * imply failure of the NTRU solving equation, and the (f,g)
+            * values will be abandoned in that case.
+            */
+            if (zint_bezout(tmpsrc, Gp, tmpsrc, Fp, tmpsrc, fp, tmpsrc, gp, len, tmpsrc, t1) == 0) {
+                return 0;
+            }
+
+            /*
+            * Multiply the two values by the target value q. Values must
+            * fit in the destination arrays.
+            * We can again test on the returned words: a non-zero output
+            * of zint_mul_small() means that we exceeded our array
+            * capacity, and that implies failure and rejection of (f,g).
+            */
+            q = 12289;
+            if (zint_mul_small(tmpsrc, Fp, len, q) != 0
+                || zint_mul_small(tmpsrc, Gp, len, q) != 0)
+            {
+                return 0;
+            }
+
+            return 1;
+        }
+
+        /*
+        * Solving the NTRU equation, intermediate level. Upon entry, the F and G
+        * from the previous level should be in the tmp[] array.
+        * This function MAY be invoked for the top-level (in which case depth = 0).
+        *
+        * Returned value: 1 on success, 0 on error.
+        */
+        int solve_NTRU_intermediate(uint logn_top,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, uint depth, uint[] tmpsrc, int tmp)
+        {
+            /*
+            * In this function, 'logn' is the log2 of the degree for
+            * this step. If N = 2^logn, then:
+            *  - the F and G values already in fk->tmp (from the deeper
+            *    levels) have degree N/2;
+            *  - this function should return F and G of degree N.
+            */
+            uint logn;
+            int n, hn, slen, dlen, llen, rlen, FGlen, u;
+            int Fd, Gd;
+            int Ft, Gt;
+            int ft, gt, t1;
+            FalconFPR[] rt1; FalconFPR[] rt2; 
+            FalconFPR[] rt3; FalconFPR[] rt4; FalconFPR[] rt5;
+            int scale_fg, minbl_fg, maxbl_fg, maxbl_FG, scale_k;
+            int x, y;
+            int[] k;
+            FalconSmallPrime[] primes;
+
+            logn = logn_top - depth;
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+
+            /*
+            * slen = size for our input f and g; also size of the reduced
+            *        F and G we return (degree N)
+            *
+            * dlen = size of the F and G obtained from the deeper level
+            *        (degree N/2 or N/3)
+            *
+            * llen = size for intermediary F and G before reduction (degree N)
+            *
+            * We build our non-reduced F and G as two independent halves each,
+            * of degree N/2 (F = F0 + X*F1, G = G0 + X*G1).
+            */
+            slen = MAX_BL_SMALL[depth];
+            dlen = MAX_BL_SMALL[depth + 1];
+            llen = MAX_BL_LARGE[depth];
+            primes = this.PRIMES;
+
+            /*
+            * Fd and Gd are the F and G from the deeper level.
+            */
+            Fd = tmp;
+            Gd = Fd + dlen * hn;
+
+            /*
+            * Compute the input f and g for this level. Note that we get f
+            * and g in RNS + NTT representation.
+            */
+            ft = Gd + dlen * hn;
+            make_fg(tmpsrc, ft, fsrc, f, gsrc, g, logn_top, depth, 1);
+
+            /*
+            * Move the newly computed f and g to make room for our candidate
+            * F and G (unreduced).
+            */
+            Ft = tmp;
+            Gt = Ft + n * llen;
+            t1 = Gt + n * llen;
+            // memmove(t1, ft, 2 * n * slen * sizeof *ft);
+            Array.Copy(tmpsrc, ft, tmpsrc, t1, 2 * n * slen);
+            ft = t1;
+            gt = ft + slen * n;
+            t1 = gt + slen * n;
+
+            /*
+            * Move Fd and Gd _after_ f and g.
+            */
+            // memmove(t1, Fd, 2 * hn * dlen * sizeof *Fd);
+            Array.Copy(tmpsrc, Fd, tmpsrc, t1, 2 * hn * dlen);
+            Fd = t1;
+            Gd = Fd + hn * dlen;
+
+            /*
+            * We reduce Fd and Gd modulo all the small primes we will need,
+            * and store the values in Ft and Gt (only n/2 values in each).
+            */
+            for (u = 0; u < llen; u ++) {
+                uint p, p0i, R2, Rx;
+                int v;
+                int xs, ys, xd, yd;
+
+                p = primes[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+                Rx = modp_Rx((uint)dlen, p, p0i, R2);
+                for (v = 0, xs = Fd, ys = Gd, xd = Ft + u, yd = Gt + u;
+                    v < hn;
+                    v ++, xs += dlen, ys += dlen, xd += llen, yd += llen)
+                {
+                    tmpsrc[xd] = zint_mod_small_signed(tmpsrc, xs, dlen, p, p0i, R2, Rx);
+                    tmpsrc[yd] = zint_mod_small_signed(tmpsrc, ys, dlen, p, p0i, R2, Rx);
+                }
+            }
+
+            /*
+            * We do not need Fd and Gd after that point.
+            */
+
+            /*
+            * Compute our F and G modulo sufficiently many small primes.
+            */
+            for (u = 0; u < llen; u ++) {
+                uint p, p0i, R2;
+                int gm, igm;
+                int fx, gx;
+                int Fp, Gp;
+                int v;
+
+                /*
+                * All computations are done modulo p.
+                */
+                p = primes[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+
+                /*
+                * If we processed slen words, then f and g have been
+                * de-NTTized, and are in RNS; we can rebuild them.
+                */
+                if (u == slen) {
+                    zint_rebuild_CRT(tmpsrc, ft, slen, slen, n, primes, 1, tmpsrc, t1);
+                    zint_rebuild_CRT(tmpsrc, gt, slen, slen, n, primes, 1, tmpsrc, t1);
+                }
+
+                gm = t1;
+                igm = gm + n;
+                fx = igm + n;
+                gx = fx + n;
+
+                modp_mkgm2(tmpsrc, gm, tmpsrc, igm, logn, primes[u].g, p, p0i);
+
+                if (u < slen) {
+                    for (v = 0, x = ft + u, y = gt + u;
+                        v < n; v ++, x += slen, y += slen)
+                    {
+                        tmpsrc[fx+v] = tmpsrc[x];
+                        tmpsrc[gx+v] = tmpsrc[y];
+                    }
+                    modp_iNTT2_ext(tmpsrc, ft + u, slen, tmpsrc, igm, logn, p, p0i);
+                    modp_iNTT2_ext(tmpsrc, gt + u, slen, tmpsrc, igm, logn, p, p0i);
+                } else {
+                    uint Rx;
+
+                    Rx = modp_Rx((uint)slen, p, p0i, R2);
+                    for (v = 0, x = ft, y = gt;
+                        v < n; v ++, x += slen, y += slen)
+                    {
+                        tmpsrc[fx+v] = zint_mod_small_signed(tmpsrc, x, slen,
+                            p, p0i, R2, Rx);
+                        tmpsrc[gx+v] = zint_mod_small_signed(tmpsrc, y, slen,
+                            p, p0i, R2, Rx);
+                    }
+                    modp_NTT2(tmpsrc, fx, tmpsrc, gm, logn, p, p0i);
+                    modp_NTT2(tmpsrc, gx, tmpsrc, gm, logn, p, p0i);
+                }
+
+                /*
+                * Get F' and G' modulo p and in NTT representation
+                * (they have degree n/2). These values were computed in
+                * a previous step, and stored in Ft and Gt.
+                */
+                Fp = gx + n;
+                Gp = Fp + hn;
+                for (v = 0, x = Ft + u, y = Gt + u;
+                    v < hn; v ++, x += llen, y += llen)
+                {
+                    tmpsrc[Fp+v] = tmpsrc[x];
+                    tmpsrc[Gp+v] = tmpsrc[y];
+                }
+                modp_NTT2(tmpsrc, Fp, tmpsrc, gm, logn - 1, p, p0i);
+                modp_NTT2(tmpsrc, Gp, tmpsrc, gm, logn - 1, p, p0i);
+
+                /*
+                * Compute our F and G modulo p.
+                *
+                * General case:
+                *
+                *   we divide degree by d = 2 or 3
+                *   f'(x^d) = N(f)(x^d) = f * adj(f)
+                *   g'(x^d) = N(g)(x^d) = g * adj(g)
+                *   f'*G' - g'*F' = q
+                *   F = F'(x^d) * adj(g)
+                *   G = G'(x^d) * adj(f)
+                *
+                * We compute things in the NTT. We group roots of phi
+                * such that all roots x in a group share the same x^d.
+                * If the roots in a group are x_1, x_2... x_d, then:
+                *
+                *   N(f)(x_1^d) = f(x_1)*f(x_2)*...*f(x_d)
+                *
+                * Thus, we have:
+                *
+                *   G(x_1) = f(x_2)*f(x_3)*...*f(x_d)*G'(x_1^d)
+                *   G(x_2) = f(x_1)*f(x_3)*...*f(x_d)*G'(x_1^d)
+                *   ...
+                *   G(x_d) = f(x_1)*f(x_2)*...*f(x_{d-1})*G'(x_1^d)
+                *
+                * In all cases, we can thus compute F and G in NTT
+                * representation by a few simple multiplications.
+                * Moreover, in our chosen NTT representation, roots
+                * from the same group are consecutive in RAM.
+                */
+                for (v = 0, x = Ft + u, y = Gt + u; v < hn;
+                    v ++, x += (llen << 1), y += (llen << 1))
+                {
+                    uint ftA, ftB, gtA, gtB;
+                    uint mFp, mGp;
+
+                    ftA = tmpsrc[fx + (v << 1) + 0];
+                    ftB = tmpsrc[fx + (v << 1) + 1];
+                    gtA = tmpsrc[gx + (v << 1) + 0];
+                    gtB = tmpsrc[gx + (v << 1) + 1];
+                    mFp = modp_montymul(tmpsrc[Fp+v], R2, p, p0i);
+                    mGp = modp_montymul(tmpsrc[Gp+v], R2, p, p0i);
+                    tmpsrc[x+0] = modp_montymul(gtB, mFp, p, p0i);
+                    tmpsrc[x+llen] = modp_montymul(gtA, mFp, p, p0i);
+                    tmpsrc[y+0] = modp_montymul(ftB, mGp, p, p0i);
+                    tmpsrc[y+llen] = modp_montymul(ftA, mGp, p, p0i);
+                }
+                modp_iNTT2_ext(tmpsrc, Ft + u, llen, tmpsrc, igm, logn, p, p0i);
+                modp_iNTT2_ext(tmpsrc, Gt + u, llen, tmpsrc, igm, logn, p, p0i);
+            }
+
+            /*
+            * Rebuild F and G with the CRT.
+            */
+            zint_rebuild_CRT(tmpsrc, Ft, llen, llen, n, primes, 1, tmpsrc, t1);
+            zint_rebuild_CRT(tmpsrc, Gt, llen, llen, n, primes, 1, tmpsrc, t1);
+
+            /*
+            * At that point, Ft, Gt, ft and gt are consecutive in RAM (in that
+            * order).
+            */
+
+            /*
+            * Apply Babai reduction to bring back F and G to size slen.
+            *
+            * We use the FFT to compute successive approximations of the
+            * reduction coefficient. We first isolate the top bits of
+            * the coefficients of f and g, and convert them to floating
+            * point; with the FFT, we compute adj(f), adj(g), and
+            * 1/(f*adj(f)+g*adj(g)).
+            *
+            * Then, we repeatedly apply the following:
+            *
+            *   - Get the top bits of the coefficients of F and G into
+            *     floating point, and use the FFT to compute:
+            *        (F*adj(f)+G*adj(g))/(f*adj(f)+g*adj(g))
+            *
+            *   - Convert back that value into normal representation, and
+            *     round it to the nearest integers, yielding a polynomial k.
+            *     Proper scaling is applied to f, g, F and G so that the
+            *     coefficients fit on 32 bits (signed).
+            *
+            *   - Subtract k*f from F and k*g from G.
+            *
+            * Under normal conditions, this process reduces the size of F
+            * and G by some bits at each iteration. For constant-time
+            * operation, we do not want to measure the actual length of
+            * F and G; instead, we do the following:
+            *
+            *   - f and g are converted to floating-point, with some scaling
+            *     if necessary to keep values in the representable range.
+            *
+            *   - For each iteration, we _assume_ a maximum size for F and G,
+            *     and use the values at that size. If we overreach, then
+            *     we get zeros, which is harmless: the resulting coefficients
+            *     of k will be 0 and the value won't be reduced.
+            *
+            *   - We conservatively assume that F and G will be reduced by
+            *     at least 25 bits at each iteration.
+            *
+            * Even when reaching the bottom of the reduction, reduction
+            * coefficient will remain low. If it goes out-of-range, then
+            * something wrong occurred and the whole NTRU solving fails.
+            */
+
+            /*
+            * Memory layout:
+            *  - We need to compute and keep adj(f), adj(g), and
+            *    1/(f*adj(f)+g*adj(g)) (sizes N, N and N/2 fp numbers,
+            *    respectively).
+            *  - At each iteration we need two extra fp buffer (N fp values),
+            *    and produce a k (N 32-bit words). k will be shared with one
+            *    of the fp buffers.
+            *  - To compute k*f and k*g efficiently (with the NTT), we need
+            *    some extra room; we reuse the space of the temporary buffers.
+            *
+            * Arrays of 'fpr' are obtained from the temporary array itself.
+            * We ensure that the base is at a properly aligned offset (the
+            * source array tmp[] is supposed to be already aligned).
+            */
+
+            // rt3 = align_fpr(tmp, t1);
+            rt1 = new FalconFPR[n];
+            rt2 = new FalconFPR[n];
+            rt3 = new FalconFPR[n];
+            rt4 = new FalconFPR[n];
+            rt5 = new FalconFPR[n >> 1];
+            k = new int[n];
+            /*
+            * Get f and g into rt3 and rt4 as floating-point approximations.
+            *
+            * We need to "scale down" the floating-point representation of
+            * coefficients when they are too big. We want to keep the value
+            * below 2^310 or so. Thus, when values are larger than 10 words,
+            * we consider only the top 10 words. Array lengths have been
+            * computed so that average maximum length will fall in the
+            * middle or the upper half of these top 10 words.
+            */
+            rlen = (slen > 10) ? 10 : slen;
+            poly_big_to_fp(rt3, 0, tmpsrc, ft + slen - rlen, rlen, slen, logn);
+            poly_big_to_fp(rt4, 0, tmpsrc, gt + slen - rlen, rlen, slen, logn);
+
+            /*
+            * Values in rt3 and rt4 are downscaled by 2^(scale_fg).
+            */
+            scale_fg = 31 * (int)(slen - rlen);
+
+            /*
+            * Estimated boundaries for the maximum size (in bits) of the
+            * coefficients of (f,g). We use the measured average, and
+            * allow for a deviation of at most six times the standard
+            * deviation.
+            */
+            minbl_fg = BITLENGTH_avg[depth] - 6 * BITLENGTH_std[depth];
+            maxbl_fg = BITLENGTH_avg[depth] + 6 * BITLENGTH_std[depth];
+
+            /*
+            * Compute 1/(f*adj(f)+g*adj(g)) in rt5. We also keep adj(f)
+            * and adj(g) in rt3 and rt4, respectively.
+            */
+            this.ffte.FFT(rt3, 0, logn);
+            this.ffte.FFT(rt4, 0, logn);
+            this.ffte.poly_invnorm2_fft(rt5, 0, rt3, 0, rt4, 0, logn);
+            this.ffte.poly_adj_fft(rt3, 0, logn);
+            this.ffte.poly_adj_fft(rt4, 0, logn);
+
+            /*
+            * Reduce F and G repeatedly.
+            *
+            * The expected maximum bit length of coefficients of F and G
+            * is kept in maxbl_FG, with the corresponding word length in
+            * FGlen.
+            */
+            FGlen = llen;
+            maxbl_FG = 31 * (int)llen;
+
+            /*
+            * Each reduction operation computes the reduction polynomial
+            * "k". We need that polynomial to have coefficients that fit
+            * on 32-bit signed integers, with some scaling; thus, we use
+            * a descending sequence of scaling values, down to zero.
+            *
+            * The size of the coefficients of k is (roughly) the difference
+            * between the size of the coefficients of (F,G) and the size
+            * of the coefficients of (f,g). Thus, the maximum size of the
+            * coefficients of k is, at the start, maxbl_FG - minbl_fg;
+            * this is our starting scale value for k.
+            *
+            * We need to estimate the size of (F,G) during the execution of
+            * the algorithm; we are allowed some overestimation but not too
+            * much (poly_big_to_fp() uses a 310-bit window). Generally
+            * speaking, after applying a reduction with k scaled to
+            * scale_k, the size of (F,G) will be size(f,g) + scale_k + dd,
+            * where 'dd' is a few bits to account for the fact that the
+            * reduction is never perfect (intuitively, dd is on the order
+            * of sqrt(N), so at most 5 bits; we here allow for 10 extra
+            * bits).
+            *
+            * The size of (f,g) is not known exactly, but maxbl_fg is an
+            * upper bound.
+            */
+            scale_k = maxbl_FG - minbl_fg;
+
+            for (;;) {
+                int scale_FG, dc, new_maxbl_FG;
+                uint scl, sch;
+                FalconFPR pdc, pt;
+
+                /*
+                * Convert current F and G into floating-point. We apply
+                * scaling if the current length is more than 10 words.
+                */
+                rlen = (FGlen > 10) ? 10 : FGlen;
+                scale_FG = 31 * (int)(FGlen - rlen);
+                poly_big_to_fp(rt1, 0, tmpsrc, Ft + FGlen - rlen, rlen, llen, logn);
+                poly_big_to_fp(rt2, 0, tmpsrc, Gt + FGlen - rlen, rlen, llen, logn);
+
+                /*
+                * Compute (F*adj(f)+G*adj(g))/(f*adj(f)+g*adj(g)) in rt2.
+                */
+                this.ffte.FFT(rt1, 0, logn);
+                this.ffte.FFT(rt2, 0, logn);
+                this.ffte.poly_mul_fft(rt1, 0, rt3, 0, logn);
+                this.ffte.poly_mul_fft(rt2, 0, rt4, 0, logn);
+                this.ffte.poly_add(rt2, 0, rt1, 0, logn);
+                this.ffte.poly_mul_autoadj_fft(rt2, 0, rt5, 0, logn);
+                this.ffte.iFFT(rt2, 0, logn);
+
+                /*
+                * (f,g) are scaled by 'scale_fg', meaning that the
+                * numbers in rt3/rt4 should be multiplied by 2^(scale_fg)
+                * to have their true mathematical value.
+                *
+                * (F,G) are similarly scaled by 'scale_FG'. Therefore,
+                * the value we computed in rt2 is scaled by
+                * 'scale_FG-scale_fg'.
+                *
+                * We want that value to be scaled by 'scale_k', hence we
+                * apply a corrective scaling. After scaling, the values
+                * should fit in -2^31-1..+2^31-1.
+                */
+                dc = scale_k - scale_FG + scale_fg;
+
+                /*
+                * We will need to multiply values by 2^(-dc). The value
+                * 'dc' is not secret, so we can compute 2^(-dc) with a
+                * non-constant-time process.
+                * (We could use ldexp(), but we prefer to avoid any
+                * dependency on libm. When using FP emulation, we could
+                * use our this.fpre.fpr_ldexp(), which is constant-time.)
+                */
+                if (dc < 0) {
+                    dc = -dc;
+                    pt = this.fpre.fpr_two;
+                } else {
+                    pt = this.fpre.fpr_onehalf;
+                }
+                pdc = this.fpre.fpr_one;
+                while (dc != 0) {
+                    if ((dc & 1) != 0) {
+                        pdc = this.fpre.fpr_mul(pdc, pt);
+                    }
+                    dc >>= 1;
+                    pt = this.fpre.fpr_sqr(pt);
+                }
+
+                for (u = 0; u < n; u ++) {
+                    FalconFPR xv;
+
+                    xv = this.fpre.fpr_mul(rt2[u], pdc);
+
+                    /*
+                    * Sometimes the values can be out-of-bounds if
+                    * the algorithm fails; we must not call
+                    * this.fpre.fpr_rint() (and cast to int) if the value
+                    * is not in-bounds. Note that the test does not
+                    * break constant-time discipline, since any
+                    * failure here implies that we discard the current
+                    * secret key (f,g).
+                    */
+                    if (!this.fpre.fpr_lt(this.fpre.fpr_mtwo31m1, xv)
+                        || !this.fpre.fpr_lt(xv, this.fpre.fpr_ptwo31m1))
+                    {
+                        return 0;
+                    }
+                    k[u] = (int)this.fpre.fpr_rint(xv);
+                }
+
+                /*
+                * Values in k[] are integers. They really are scaled
+                * down by maxbl_FG - minbl_fg bits.
+                *
+                * If we are at low depth, then we use the NTT to
+                * compute k*f and k*g.
+                */
+                sch = (uint)(scale_k / 31);
+                scl = (uint)(scale_k % 31);
+                if (depth <= DEPTH_INT_FG) {
+                    poly_sub_scaled_ntt(tmpsrc, Ft, FGlen, llen, tmpsrc, ft, slen, slen,
+                        k, 0, sch, scl, logn, tmpsrc, t1);
+                    poly_sub_scaled_ntt(tmpsrc, Gt, FGlen, llen, tmpsrc, gt, slen, slen,
+                        k, 0, sch, scl, logn, tmpsrc, t1);
+                } else {
+                    poly_sub_scaled(tmpsrc, Ft, FGlen, llen, tmpsrc, ft, slen, slen,
+                        k, 0, sch, scl, logn);
+                    poly_sub_scaled(tmpsrc, Gt, FGlen, llen, tmpsrc, gt, slen, slen,
+                        k, 0, sch, scl, logn);
+                }
+
+                /*
+                * We compute the new maximum size of (F,G), assuming that
+                * (f,g) has _maximal_ length (i.e. that reduction is
+                * "late" instead of "early". We also adjust FGlen
+                * accordingly.
+                */
+                new_maxbl_FG = scale_k + maxbl_fg + 10;
+                if (new_maxbl_FG < maxbl_FG) {
+                    maxbl_FG = new_maxbl_FG;
+                    if ((int)FGlen * 31 >= maxbl_FG + 31) {
+                        FGlen --;
+                    }
+                }
+
+                /*
+                * We suppose that scaling down achieves a reduction by
+                * at least 25 bits per iteration. We stop when we have
+                * done the loop with an unscaled k.
+                */
+                if (scale_k <= 0) {
+                    break;
+                }
+                scale_k -= 25;
+                if (scale_k < 0) {
+                    scale_k = 0;
+                }
+            }
+
+            /*
+            * If (F,G) length was lowered below 'slen', then we must take
+            * care to re-extend the sign.
+            */
+            if (FGlen < slen) {
+                for (u = 0; u < n; u ++, Ft += llen, Gt += llen) {
+                    int v;
+                    uint sw;
+
+                    sw = ((uint)-(tmpsrc[Ft+FGlen - 1] >> 30) >> 1);
+                    for (v = FGlen; v < slen; v ++) {
+                        tmpsrc[Ft+v] = sw;
+                    }
+                    sw = ((uint)-(tmpsrc[Gt+FGlen - 1] >> 30) >> 1);
+                    for (v = FGlen; v < slen; v ++) {
+                        tmpsrc[Gt+v] = sw;
+                    }
+                }
+            }
+
+            /*
+            * Compress encoding of all values to 'slen' words (this is the
+            * expected output format).
+            */
+            for (u = 0, x = tmp, y = tmp;
+                u < (n << 1); u ++, x += slen, y += llen)
+            {
+                // memmove(x, y, slen * sizeof *y);
+                Array.Copy(tmpsrc, y, tmpsrc, x, slen);
+            }
+            return 1;
+        }
+
+        /*
+        * Solving the NTRU equation, binary case, depth = 1. Upon entry, the
+        * F and G from the previous level should be in the tmp[] array.
+        *
+        * Returned value: 1 on success, 0 on error.
+        */
+        int solve_NTRU_binary_depth1(uint logn_top,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, uint[] tmpsrc, int tmp)
+        {
+            /*
+            * The first half of this function is a copy of the corresponding
+            * part in solve_NTRU_intermediate(), for the reconstruction of
+            * the unreduced F and G. The second half (Babai reduction) is
+            * done differently, because the unreduced F and G fit in 53 bits
+            * of precision, allowing a much simpler process with lower RAM
+            * usage.
+            */
+            uint depth, logn;
+            int n_top, n, hn, slen, dlen, llen, u;
+            int Fd, Gd, Ft, Gt;
+            int ft, gt, t1;
+            FalconFPR[] rt1; FalconFPR[] rt2; FalconFPR[] rt3; 
+            FalconFPR[] rt4; FalconFPR[] rt5; FalconFPR[] rt6;
+            int x, y;
+
+            depth = 1;
+            n_top = (int)1 << (int)logn_top;
+            logn = logn_top - depth;
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+
+            /*
+            * Equations are:
+            *
+            *   f' = f0^2 - X^2*f1^2
+            *   g' = g0^2 - X^2*g1^2
+            *   F' and G' are a solution to f'G' - g'F' = q (from deeper levels)
+            *   F = F'*(g0 - X*g1)
+            *   G = G'*(f0 - X*f1)
+            *
+            * f0, f1, g0, g1, f', g', F' and G' are all "compressed" to
+            * degree N/2 (their odd-indexed coefficients are all zero).
+            */
+
+            /*
+            * slen = size for our input f and g; also size of the reduced
+            *        F and G we return (degree N)
+            *
+            * dlen = size of the F and G obtained from the deeper level
+            *        (degree N/2)
+            *
+            * llen = size for intermediary F and G before reduction (degree N)
+            *
+            * We build our non-reduced F and G as two independent halves each,
+            * of degree N/2 (F = F0 + X*F1, G = G0 + X*G1).
+            */
+            slen = MAX_BL_SMALL[depth];
+            dlen = MAX_BL_SMALL[depth + 1];
+            llen = MAX_BL_LARGE[depth];
+
+            /*
+            * Fd and Gd are the F and G from the deeper level. Ft and Gt
+            * are the destination arrays for the unreduced F and G.
+            */
+            Fd = tmp;
+            Gd = Fd + dlen * hn;
+            Ft = Gd + dlen * hn;
+            Gt = Ft + llen * n;
+
+            /*
+            * We reduce Fd and Gd modulo all the small primes we will need,
+            * and store the values in Ft and Gt.
+            */
+            for (u = 0; u < llen; u ++) {
+                uint p, p0i, R2, Rx;
+                int v;
+                int xs, ys;
+                int xd, yd;
+
+                p = this.PRIMES[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+                Rx = modp_Rx((uint)dlen, p, p0i, R2);
+                for (v = 0, xs = Fd, ys = Gd, xd = Ft + u, yd = Gt + u;
+                    v < hn;
+                    v ++, xs += dlen, ys += dlen, xd += llen, yd += llen)
+                {
+                    tmpsrc[xd] = zint_mod_small_signed(tmpsrc, xs, dlen, p, p0i, R2, Rx);
+                    tmpsrc[yd] = zint_mod_small_signed(tmpsrc, ys, dlen, p, p0i, R2, Rx);
+                }
+            }
+
+            /*
+            * Now Fd and Gd are not needed anymore; we can squeeze them out.
+            */
+            // memmove(tmp, Ft, llen * n * sizeof(uint));
+            Array.Copy(tmpsrc, Ft, tmpsrc, tmp, llen * n);
+            Ft = tmp;
+            // memmove(Ft + llen * n, Gt, llen * n * sizeof(uint));
+            Array.Copy(tmpsrc, Gt, tmpsrc, Ft + llen * n, llen * n);
+            Gt = Ft + llen * n;
+            ft = Gt + llen * n;
+            gt = ft + slen * n;
+
+            t1 = gt + slen * n;
+
+            /*
+            * Compute our F and G modulo sufficiently many small primes.
+            */
+            for (u = 0; u < llen; u ++) {
+                uint p, p0i, R2;
+                int gm, igm;
+                int fx, gx;
+                int Fp, Gp;
+                uint e;
+                int v;
+
+                /*
+                * All computations are done modulo p.
+                */
+                p = this.PRIMES[u].p;
+                p0i = modp_ninv31(p);
+                R2 = modp_R2(p, p0i);
+
+                /*
+                * We recompute things from the source f and g, of full
+                * degree. However, we will need only the n first elements
+                * of the inverse NTT table (igm); the call to modp_mkgm()
+                * below will fill n_top elements in igm[] (thus overflowing
+                * into fx[]) but later code will overwrite these extra
+                * elements.
+                */
+                gm = t1;
+                igm = gm + n_top;
+                fx = igm + n;
+                gx = fx + n_top;
+                modp_mkgm2(tmpsrc, gm, tmpsrc, igm, logn_top, this.PRIMES[u].g, p, p0i);
+
+                /*
+                * Set ft and gt to f and g modulo p, respectively.
+                */
+                for (v = 0; v < n_top; v ++) {
+                    tmpsrc[fx+v] = modp_set(fsrc[f+v], p);
+                    tmpsrc[gx+v] = modp_set(gsrc[g+v], p);
+                }
+
+                /*
+                * Convert to NTT and compute our f and g.
+                */
+                modp_NTT2(tmpsrc, fx, tmpsrc, gm, logn_top, p, p0i);
+                modp_NTT2(tmpsrc, gx, tmpsrc, gm, logn_top, p, p0i);
+                for (e = logn_top; e > logn; e --) {
+                    modp_poly_rec_res(tmpsrc, fx, e, p, p0i, R2);
+                    modp_poly_rec_res(tmpsrc, gx, e, p, p0i, R2);
+                }
+
+                /*
+                * From that point onward, we only need tables for
+                * degree n, so we can save some space.
+                */
+                if (depth > 0) { /* always true */
+                    // memmove(gm + n, igm, n * sizeof *igm);
+                    Array.Copy(tmpsrc, igm, tmpsrc, gm + n, n);
+                    igm = gm + n;
+                    // memmove(igm + n, fx, n * sizeof *ft);
+                    Array.Copy(tmpsrc, fx, tmpsrc, igm + n, n);
+                    fx = igm + n;
+                    // memmove(fx + n, gx, n * sizeof *gt);
+                    Array.Copy(tmpsrc, gx, tmpsrc, fx + n, n);
+                    gx = fx + n;
+                }
+
+                /*
+                * Get F' and G' modulo p and in NTT representation
+                * (they have degree n/2). These values were computed
+                * in a previous step, and stored in Ft and Gt.
+                */
+                Fp = gx + n;
+                Gp = Fp + hn;
+                for (v = 0, x = Ft + u, y = Gt + u;
+                    v < hn; v ++, x += llen, y += llen)
+                {
+                    tmpsrc[Fp+v] = tmpsrc[x];
+                    tmpsrc[Gp+v] = tmpsrc[y];
+                }
+                modp_NTT2(tmpsrc, Fp, tmpsrc, gm, logn - 1, p, p0i);
+                modp_NTT2(tmpsrc, Gp, tmpsrc, gm, logn - 1, p, p0i);
+
+                /*
+                * Compute our F and G modulo p.
+                *
+                * Equations are:
+                *
+                *   f'(x^2) = N(f)(x^2) = f * adj(f)
+                *   g'(x^2) = N(g)(x^2) = g * adj(g)
+                *
+                *   f'*G' - g'*F' = q
+                *
+                *   F = F'(x^2) * adj(g)
+                *   G = G'(x^2) * adj(f)
+                *
+                * The NTT representation of f is f(w) for all w which
+                * are roots of phi. In the binary case, as well as in
+                * the ternary case for all depth except the deepest,
+                * these roots can be grouped in pairs (w,-w), and we
+                * then have:
+                *
+                *   f(w) = adj(f)(-w)
+                *   f(-w) = adj(f)(w)
+                *
+                * and w^2 is then a root for phi at the half-degree.
+                *
+                * At the deepest level in the ternary case, this still
+                * holds, in the following sense: the roots of x^2-x+1
+                * are (w,-w^2) (for w^3 = -1, and w != -1), and we
+                * have:
+                *
+                *   f(w) = adj(f)(-w^2)
+                *   f(-w^2) = adj(f)(w)
+                *
+                * In all case, we can thus compute F and G in NTT
+                * representation by a few simple multiplications.
+                * Moreover, the two roots for each pair are consecutive
+                * in our bit-reversal encoding.
+                */
+                for (v = 0, x = Ft + u, y = Gt + u;
+                    v < hn; v ++, x += (llen << 1), y += (llen << 1))
+                {
+                    uint ftA, ftB, gtA, gtB;
+                    uint mFp, mGp;
+
+                    ftA = tmpsrc[fx + (v << 1) + 0];
+                    ftB = tmpsrc[fx + (v << 1) + 1];
+                    gtA = tmpsrc[gx + (v << 1) + 0];
+                    gtB = tmpsrc[gx + (v << 1) + 1];
+                    mFp = modp_montymul(tmpsrc[Fp+v], R2, p, p0i);
+                    mGp = modp_montymul(tmpsrc[Gp+v], R2, p, p0i);
+                    tmpsrc[x+0] = modp_montymul(gtB, mFp, p, p0i);
+                    tmpsrc[x+llen] = modp_montymul(gtA, mFp, p, p0i);
+                    tmpsrc[y+0] = modp_montymul(ftB, mGp, p, p0i);
+                    tmpsrc[y+llen] = modp_montymul(ftA, mGp, p, p0i);
+                }
+                modp_iNTT2_ext(tmpsrc, Ft + u, llen, tmpsrc, igm, logn, p, p0i);
+                modp_iNTT2_ext(tmpsrc, Gt + u, llen, tmpsrc, igm, logn, p, p0i);
+
+                /*
+                * Also save ft and gt (only up to size slen).
+                */
+                if (u < slen) {
+                    modp_iNTT2(tmpsrc, fx, tmpsrc, igm, logn, p, p0i);
+                    modp_iNTT2(tmpsrc, gx, tmpsrc, igm, logn, p, p0i);
+                    for (v = 0, x = ft + u, y = gt + u;
+                        v < n; v ++, x += slen, y += slen)
+                    {
+                        tmpsrc[x] = tmpsrc[fx+v];
+                        tmpsrc[y] = tmpsrc[gx+v];
+                    }
+                }
+            }
+
+            /*
+            * Rebuild f, g, F and G with the CRT. Note that the elements of F
+            * and G are consecutive, and thus can be rebuilt in a single
+            * loop; similarly, the elements of f and g are consecutive.
+            */
+            zint_rebuild_CRT(tmpsrc, Ft, llen, llen, n << 1, this.PRIMES, 1, tmpsrc, t1);
+            zint_rebuild_CRT(tmpsrc, ft, slen, slen, n << 1, this.PRIMES, 1, tmpsrc, t1);
+
+            /*
+            * Here starts the Babai reduction, specialized for depth = 1.
+            *
+            * Candidates F and G (from Ft and Gt), and base f and g (ft and gt),
+            * are converted to floating point. There is no scaling, and a
+            * single pass is sufficient.
+            */
+
+            /*
+            * Convert F and G into floating point (rt1 and rt2).
+            */
+            rt1 = new FalconFPR[n];
+            rt2 = new FalconFPR[n];
+            poly_big_to_fp(rt1, 0, tmpsrc, Ft, llen, llen, logn);
+            poly_big_to_fp(rt2, 0, tmpsrc, Gt, llen, llen, logn);
+
+            /*
+            * Integer representation of F and G is no longer needed, we
+            * can remove it.
+            */
+            // memmove(tmp, ft, 2 * slen * n * sizeof *ft);
+            Array.Copy(tmpsrc, ft, tmpsrc, tmp, 2 * slen * n);
+            ft = tmp;
+            gt = ft + slen * n;
+            rt3 = new FalconFPR[n];
+            rt4 = new FalconFPR[n];
+
+            /*
+            * Convert f and g into floating point (rt3 and rt4).
+            */
+            poly_big_to_fp(rt3, 0, tmpsrc, ft, slen, slen, logn);
+            poly_big_to_fp(rt4, 0, tmpsrc, gt, slen, slen, logn);
+
+            /*
+            * We now have:
+            *   rt1 = F
+            *   rt2 = G
+            *   rt3 = f
+            *   rt4 = g
+            * in that order in RAM. We convert all of them to FFT.
+            */
+            this.ffte.FFT(rt1, 0, logn);
+            this.ffte.FFT(rt2, 0, logn);
+            this.ffte.FFT(rt3, 0, logn);
+            this.ffte.FFT(rt4, 0, logn);
+
+            /*
+            * Compute:
+            *   rt5 = F*adj(f) + G*adj(g)
+            *   rt6 = 1 / (f*adj(f) + g*adj(g))
+            * (Note that rt6 is half-length.)
+            */
+            rt5 = new FalconFPR[n];
+            rt6 = new FalconFPR[n];
+            this.ffte.poly_add_muladj_fft(rt5, 0, rt1, 0, rt2, 0, rt3, 0, rt4, 0, logn);
+            this.ffte.poly_invnorm2_fft(rt6, 0, rt3, 0, rt4, 0, logn);
+
+            /*
+            * Compute:
+            *   rt5 = (F*adj(f)+G*adj(g)) / (f*adj(f)+g*adj(g))
+            */
+            this.ffte.poly_mul_autoadj_fft(rt5, 0, rt6, 0, logn);
+
+            /*
+            * Compute k as the rounded version of rt5. Check that none of
+            * the values is larger than 2^63-1 (in absolute value)
+            * because that would make the this.fpre.fpr_rint() do something undefined;
+            * note that any out-of-bounds value here implies a failure and
+            * (f,g) will be discarded, so we can make a simple test.
+            */
+            this.ffte.iFFT(rt5, 0, logn);
+            for (u = 0; u < n; u ++) {
+                FalconFPR z;
+
+                z = rt5[u];
+                if (!this.fpre.fpr_lt(z, this.fpre.fpr_ptwo63m1) || !this.fpre.fpr_lt(this.fpre.fpr_mtwo63m1, z)) {
+                    return 0;
+                }
+                rt5[u] = this.fpre.fpr_of(this.fpre.fpr_rint(z));
+            }
+            this.ffte.FFT(rt5, 0, logn);
+
+            /*
+            * Subtract k*f from F, and k*g from G.
+            */
+            this.ffte.poly_mul_fft(rt3, 0, rt5, 0, logn);
+            this.ffte.poly_mul_fft(rt4, 0, rt5, 0, logn);
+            this.ffte.poly_sub(rt1, 0, rt3, 0, logn);
+            this.ffte.poly_sub(rt2, 0, rt4, 0, logn);
+            this.ffte.iFFT(rt1, 0, logn);
+            this.ffte.iFFT(rt2, 0, logn);
+
+            /*
+            * Convert back F and G to integers, and return.
+            */
+            Ft = tmp;
+            Gt = Ft + n;
+            for (u = 0; u < n; u ++) {
+                tmpsrc[Ft+u] = (uint)this.fpre.fpr_rint(rt1[u]);
+                tmpsrc[Gt+u] = (uint)this.fpre.fpr_rint(rt2[u]);
+            }
+
+            return 1;
+        }
+
+        /*
+        * Solving the NTRU equation, top level. Upon entry, the F and G
+        * from the previous level should be in the tmp[] array.
+        *
+        * Returned value: 1 on success, 0 on error.
+        */
+        int solve_NTRU_binary_depth0(uint logn,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, uint[] tmpsrc, int tmp)
+        {
+            int n, hn, u;
+            uint p, p0i, R2;
+            int Fp, Gp; 
+            int t1, t2, t3, t4, t5;
+            int gm, igm, ft, gt;
+            int rt2,  rt3;
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+
+            /*
+            * Equations are:
+            *
+            *   f' = f0^2 - X^2*f1^2
+            *   g' = g0^2 - X^2*g1^2
+            *   F' and G' are a solution to f'G' - g'F' = q (from deeper levels)
+            *   F = F'*(g0 - X*g1)
+            *   G = G'*(f0 - X*f1)
+            *
+            * f0, f1, g0, g1, f', g', F' and G' are all "compressed" to
+            * degree N/2 (their odd-indexed coefficients are all zero).
+            *
+            * Everything should fit in 31-bit integers, hence we can just use
+            * the first small prime p = 2147473409.
+            */
+            p = this.PRIMES[0].p;
+            p0i = modp_ninv31(p);
+            R2 = modp_R2(p, p0i);
+
+            Fp = tmp;
+            Gp = Fp + hn;
+            ft = Gp + hn;
+            gt = ft + n;
+            gm = gt + n;
+            igm = gm + n;
+
+            modp_mkgm2(tmpsrc, gm, tmpsrc, igm, logn, PRIMES[0].g, p, p0i);
+
+            /*
+            * Convert F' anf G' in NTT representation.
+            */
+            for (u = 0; u < hn; u ++) {
+                tmpsrc[Fp+u] = modp_set(zint_one_to_plain(tmpsrc, Fp + u), p);
+                tmpsrc[Gp+u] = modp_set(zint_one_to_plain(tmpsrc, Gp + u), p);
+            }
+            modp_NTT2(tmpsrc, Fp, tmpsrc, gm, logn - 1, p, p0i);
+            modp_NTT2(tmpsrc, Gp, tmpsrc, gm, logn - 1, p, p0i);
+
+            /*
+            * Load f and g and convert them to NTT representation.
+            */
+            for (u = 0; u < n; u ++) {
+                tmpsrc[ft+u] = modp_set(fsrc[f+u], p);
+                tmpsrc[gt+u] = modp_set(gsrc[g+u], p);
+            }
+            modp_NTT2(tmpsrc, ft, tmpsrc, gm, logn, p, p0i);
+            modp_NTT2(tmpsrc, gt, tmpsrc, gm, logn, p, p0i);
+
+            /*
+            * Build the unreduced F,G in ft and gt.
+            */
+            for (u = 0; u < n; u += 2) {
+                uint ftA, ftB, gtA, gtB;
+                uint mFp, mGp;
+
+                ftA = tmpsrc[ft + u + 0];
+                ftB = tmpsrc[ft + u + 1];
+                gtA = tmpsrc[gt + u + 0];
+                gtB = tmpsrc[gt + u + 1];
+                mFp = modp_montymul(tmpsrc[Fp + (u >> 1)], R2, p, p0i);
+                mGp = modp_montymul(tmpsrc[Gp + (u >> 1)], R2, p, p0i);
+                tmpsrc[ft + u + 0] = modp_montymul(gtB, mFp, p, p0i);
+                tmpsrc[ft + u + 1] = modp_montymul(gtA, mFp, p, p0i);
+                tmpsrc[gt + u + 0] = modp_montymul(ftB, mGp, p, p0i);
+                tmpsrc[gt + u + 1] = modp_montymul(ftA, mGp, p, p0i);
+            }
+            modp_iNTT2(tmpsrc, ft, tmpsrc, igm, logn, p, p0i);
+            modp_iNTT2(tmpsrc, gt, tmpsrc, igm, logn, p, p0i);
+
+            Gp = Fp + n;
+            t1 = Gp + n;
+            // memmove(Fp, ft, 2 * n * sizeof *ft);
+            Array.Copy(tmpsrc, ft, tmpsrc, Fp, 2 * n);
+
+            /*
+            * We now need to apply the Babai reduction. At that point,
+            * we have F and G in two n-word arrays.
+            *
+            * We can compute F*adj(f)+G*adj(g) and f*adj(f)+g*adj(g)
+            * modulo p, using the NTT. We still move memory around in
+            * order to save RAM.
+            */
+            t2 = t1 + n;
+            t3 = t2 + n;
+            t4 = t3 + n;
+            t5 = t4 + n;
+
+            /*
+            * Compute the NTT tables in t1 and t2. We do not keep t2
+            * (we'll recompute it later on).
+            */
+            modp_mkgm2(tmpsrc, t1, tmpsrc, t2, logn, PRIMES[0].g, p, p0i);
+
+            /*
+            * Convert F and G to NTT.
+            */
+            modp_NTT2(tmpsrc, Fp, tmpsrc, t1, logn, p, p0i);
+            modp_NTT2(tmpsrc, Gp, tmpsrc, t1, logn, p, p0i);
+
+            /*
+            * Load f and adj(f) in t4 and t5, and convert them to NTT
+            * representation.
+            */
+            tmpsrc[t4+0] = tmpsrc[t5+0] = modp_set(fsrc[f + 0], p);
+            for (u = 1; u < n; u ++) {
+                tmpsrc[t4+u] = modp_set(fsrc[f + u], p);
+                tmpsrc[t5+n - u] = modp_set(-fsrc[f + u], p);
+            }
+            modp_NTT2(tmpsrc, t4, tmpsrc, t1, logn, p, p0i);
+            modp_NTT2(tmpsrc, t5, tmpsrc, t1, logn, p, p0i);
+
+            /*
+            * Compute F*adj(f) in t2, and f*adj(f) in t3.
+            */
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = modp_montymul(tmpsrc[t5+u], R2, p, p0i);
+                tmpsrc[t2+u] = modp_montymul(w, tmpsrc[Fp+u], p, p0i);
+                tmpsrc[t3+u] = modp_montymul(w, tmpsrc[t4+u], p, p0i);
+            }
+
+            /*
+            * Load g and adj(g) in t4 and t5, and convert them to NTT
+            * representation.
+            */
+            tmpsrc[t4+0] = tmpsrc[t5+0] = modp_set(gsrc[g + 0], p);
+            for (u = 1; u < n; u ++) {
+                tmpsrc[t4+u] = modp_set(gsrc[g + u], p);
+                tmpsrc[t5+n - u] = modp_set(-gsrc[g + u], p);
+            }
+            modp_NTT2(tmpsrc, t4, tmpsrc, t1, logn, p, p0i);
+            modp_NTT2(tmpsrc, t5, tmpsrc, t1, logn, p, p0i);
+
+            /*
+            * Add G*adj(g) to t2, and g*adj(g) to t3.
+            */
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = modp_montymul(tmpsrc[t5+u], R2, p, p0i);
+                tmpsrc[t2+u] = modp_add(tmpsrc[t2+u],
+                    modp_montymul(w, tmpsrc[Gp+u], p, p0i), p);
+                tmpsrc[t3+u] = modp_add(tmpsrc[t3+u],
+                    modp_montymul(w, tmpsrc[t4+u], p, p0i), p);
+            }
+
+            /*
+            * Convert back t2 and t3 to normal representation (normalized
+            * around 0), and then
+            * move them to t1 and t2. We first need to recompute the
+            * inverse table for NTT.
+            */
+            modp_mkgm2(tmpsrc, t1, tmpsrc, t4, logn, this.PRIMES[0].g, p, p0i);
+            modp_iNTT2(tmpsrc, t2, tmpsrc, t4, logn, p, p0i);
+            modp_iNTT2(tmpsrc, t3, tmpsrc, t4, logn, p, p0i);
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t1+u] = (uint)modp_norm(tmpsrc[t2+u], p);
+                tmpsrc[t2+u] = (uint)modp_norm(tmpsrc[t3+u], p);
+            }
+
+            /*
+            * At that point, array contents are:
+            *
+            *   F (NTT representation) (Fp)
+            *   G (NTT representation) (Gp)
+            *   F*adj(f)+G*adj(g) (t1)
+            *   f*adj(f)+g*adj(g) (t2)
+            *
+            * We want to divide t1 by t2. The result is not integral; it
+            * must be rounded. We thus need to use the FFT.
+            */
+
+            /*
+            * Get f*adj(f)+g*adj(g) in FFT representation. Since this
+            * polynomial is auto-adjoint, all its coordinates in FFT
+            * representation are actually real, so we can truncate off
+            * the imaginary parts.
+            */
+            FalconFPR[] rtmp = new FalconFPR[2 * n];
+            rt3 = n;
+            for (u = 0; u < n; u ++) {
+                rtmp[rt3+u] = this.fpre.fpr_of((int)tmpsrc[t2+u]);
+            }
+            this.ffte.FFT(rtmp, rt3, logn);
+            rt2 = 0;
+            // memmove(rt2, rt3, hn * sizeof *rt3);
+            Array.Copy(rtmp, rt3, rtmp, rt2, hn);
+
+            /*
+            * Convert F*adj(f)+G*adj(g) in FFT representation.
+            */
+            rt3 = rt2 + hn;
+            for (u = 0; u < n; u ++) {
+                rtmp[rt3+u] = this.fpre.fpr_of((int)tmpsrc[t1 + u]);
+            }
+            this.ffte.FFT(rtmp, rt3, logn);
+
+            /*
+            * Compute (F*adj(f)+G*adj(g))/(f*adj(f)+g*adj(g)) and get
+            * its rounded normal representation in t1.
+            */
+            this.ffte.poly_div_autoadj_fft(rtmp, rt3, rtmp, rt2, logn);
+            this.ffte.iFFT(rtmp, rt3, logn);
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t1+u] = modp_set((int)this.fpre.fpr_rint(rtmp[rt3+u]), p);
+            }
+
+            /*
+            * RAM contents are now:
+            *
+            *   F (NTT representation) (Fp)
+            *   G (NTT representation) (Gp)
+            *   k (t1)
+            *
+            * We want to compute F-k*f, and G-k*g.
+            */
+            t2 = t1 + n;
+            t3 = t2 + n;
+            t4 = t3 + n;
+            t5 = t4 + n;
+            modp_mkgm2(tmpsrc, t2, tmpsrc, t3, logn, this.PRIMES[0].g, p, p0i);
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t4+u] = modp_set(fsrc[f+u], p);
+                tmpsrc[t5+u] = modp_set(gsrc[g+u], p);
+            }
+            modp_NTT2(tmpsrc, t1, tmpsrc, t2, logn, p, p0i);
+            modp_NTT2(tmpsrc, t4, tmpsrc, t2, logn, p, p0i);
+            modp_NTT2(tmpsrc, t5, tmpsrc, t2, logn, p, p0i);
+            for (u = 0; u < n; u ++) {
+                uint kw;
+
+                kw = modp_montymul(tmpsrc[t1+u], R2, p, p0i);
+                tmpsrc[Fp+u] = modp_sub(tmpsrc[Fp+u],
+                    modp_montymul(kw, tmpsrc[t4+u], p, p0i), p);
+                tmpsrc[Gp+u] = modp_sub(tmpsrc[Gp+u],
+                    modp_montymul(kw, tmpsrc[t5+u], p, p0i), p);
+            }
+            modp_iNTT2(tmpsrc, Fp, tmpsrc, t3, logn, p, p0i);
+            modp_iNTT2(tmpsrc, Gp, tmpsrc, t3, logn, p, p0i);
+            for (u = 0; u < n; u ++) {
+                tmpsrc[Fp+u] = (uint)modp_norm(tmpsrc[Fp+u], p);
+                tmpsrc[Gp+u] = (uint)modp_norm(tmpsrc[Gp+u], p);
+            }
+
+            return 1;
+        }
+
+        /*
+        * Solve the NTRU equation. Returned value is 1 on success, 0 on error.
+        * G can be NULL, in which case that value is computed but not returned.
+        * If any of the coefficients of F and G exceeds lim (in absolute value),
+        * then 0 is returned.
+        */
+        int solve_NTRU(uint logn, sbyte[] Fsrc, int F, sbyte[] Gsrc, int G,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, int lim, uint[] tmpsrc, int tmp)
+        {
+            int n, u;
+            int ft, gt, Ft, Gt, gm;
+            uint p, p0i, r;
+            FalconSmallPrime[] primes;
+
+            n = (int)1 << (int)logn;
+
+            if (solve_NTRU_deepest(logn, fsrc, f, gsrc, g, tmpsrc, tmp) == 0) {
+                return 0;
+            }
+
+            /*
+            * For logn <= 2, we need to use solve_NTRU_intermediate()
+            * directly, because coefficients are a bit too large and
+            * do not fit the hypotheses in solve_NTRU_binary_depth0().
+            */
+            if (logn <= 2) {
+                uint depth;
+
+                depth = logn;
+                while (depth -- > 0) {
+                    if (solve_NTRU_intermediate(logn, fsrc, f, gsrc, g, depth, tmpsrc, tmp) == 0) {
+                        return 0;
+                    }
+                }
+            } else {
+                uint depth;
+
+                depth = logn;
+                while (depth -- > 2) {
+                    // TODO check what causes this to fail
+                    if (solve_NTRU_intermediate(logn, fsrc, f, gsrc, g, depth, tmpsrc, tmp) == 0) {
+                        return 0;
+                    }
+                }
+                if (solve_NTRU_binary_depth1(logn, fsrc, f, gsrc, g, tmpsrc, tmp) == 0) {
+                    return 0;
+                }
+                if (solve_NTRU_binary_depth0(logn, fsrc, f, gsrc, g, tmpsrc, tmp) == 0) {
+                    return 0;
+                }
+            }
+
+            /*
+            * If no buffer has been provided for G, use a temporary one.
+            */
+            if (Gsrc == null) {
+                G = 0;
+                Gsrc = new sbyte[n];
+            }
+
+            /*
+            * Final F and G are in fk->tmp, one word per coefficient
+            * (signed value over 31 bits).
+            */
+            if (poly_big_to_small(Fsrc, F, tmpsrc, tmp, lim, logn) == 0
+                || poly_big_to_small(Gsrc, G, tmpsrc, tmp + n, lim, logn) == 0)
+            {
+                return 0;
+            }
+
+            /*
+            * Verify that the NTRU equation is fulfilled. Since all elements
+            * have short lengths, verifying modulo a small prime p works, and
+            * allows using the NTT.
+            *
+            * We put Gt[] first in tmp[], and process it first, so that it does
+            * not overlap with G[] in case we allocated it ourselves.
+            */
+            Gt = tmp;
+            ft = Gt + n;
+            gt = ft + n;
+            Ft = gt + n;
+            gm = Ft + n;
+
+            primes = this.PRIMES;
+            p = primes[0].p;
+            p0i = modp_ninv31(p);
+            modp_mkgm2(tmpsrc, gm, tmpsrc, tmp, logn, primes[0].g, p, p0i);
+            for (u = 0; u < n; u ++) {
+                tmpsrc[Gt+u] = modp_set(Gsrc[G+u], p);
+            }
+            for (u = 0; u < n; u ++) {
+                tmpsrc[ft+u] = modp_set(fsrc[f+u], p);
+                tmpsrc[gt+u] = modp_set(gsrc[g+u], p);
+                tmpsrc[Ft+u] = modp_set(Fsrc[F+u], p);
+            }
+            modp_NTT2(tmpsrc, ft, tmpsrc, gm, logn, p, p0i);
+            modp_NTT2(tmpsrc, gt, tmpsrc, gm, logn, p, p0i);
+            modp_NTT2(tmpsrc, Ft, tmpsrc, gm, logn, p, p0i);
+            modp_NTT2(tmpsrc, Gt, tmpsrc, gm, logn, p, p0i);
+            r = modp_montymul(12289, 1, p, p0i);
+            for (u = 0; u < n; u ++) {
+                uint z;
+
+                z = modp_sub(modp_montymul(tmpsrc[ft+u], tmpsrc[Gt+u], p, p0i),
+                    modp_montymul(tmpsrc[gt+u], tmpsrc[Ft+u], p, p0i), p);
+                if (z != r) {
+                    return 0;
+                }
+            }
+
+            return 1;
+        }
+
+        /*
+        * Generate a random polynomial with a Gaussian distribution. This function
+        * also makes sure that the resultant of the polynomial with phi is odd.
+        */
+        void poly_small_mkgauss(SHAKE256 rng, sbyte[] fsrc, int f, uint logn)
+        {
+            int n, u;
+            uint mod2;
+
+            n = (int)1 << (int)logn;
+            mod2 = 0;
+            for (u = 0; u < n; u ++) {
+                int s;
+
+                for(;;) {
+                    s = mkgauss(rng, logn);
+
+                    /*
+                    * We need the coefficient to fit within -127..+127;
+                    * realistically, this is always the case except for
+                    * the very low degrees (N = 2 or 4), for which there
+                    * is no real security anyway.
+                    */
+                    if (s < -127 || s > 127) {
+                        continue; // restart
+                    }
+
+                    /*
+                    * We need the sum of all coefficients to be 1; otherwise,
+                    * the resultant of the polynomial with X^N+1 will be even,
+                    * and the binary GCD will fail.
+                    */
+                    if (u == n - 1) {
+                        if ((mod2 ^ (uint)(s & 1)) == 0) {
+                            continue; // restart
+                        }
+                    } else {
+                        mod2 ^= (uint)(s & 1);
+                    }
+                    fsrc[f+u] = (sbyte)s;
+                    break; // end
+                }
+            }
+        }
+
+        internal void keygen(SHAKE256 rng,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, sbyte[] Fsrc, int F, sbyte[] Gsrc, int G, ushort[] hsrc, int h,
+            uint logn)
+        {
+            /*
+            * Algorithm is the following:
+            *
+            *  - Generate f and g with the Gaussian distribution.
+            *
+            *  - If either Res(f,phi) or Res(g,phi) is even, try again.
+            *
+            *  - If ||(f,g)|| is too large, try again.
+            *
+            *  - If ||B~_{f,g}|| is too large, try again.
+            *
+            *  - If f is not invertible mod phi mod q, try again.
+            *
+            *  - Compute h = g/f mod phi mod q.
+            *
+            *  - Solve the NTRU equation fG - gF = q; if the solving fails,
+            *    try again. Usual failure condition is when Res(f,phi)
+            *    and Res(g,phi) are not prime to each other.
+            */
+            int n, u;
+            int h2, tmp2;
+            SHAKE256 rc;
+
+            n = (int)1 << (int)logn;
+            rc = rng;
+
+            /*
+            * We need to generate f and g randomly, until we find values
+            * such that the norm of (g,-f), and of the orthogonalized
+            * vector, are satisfying. The orthogonalized vector is:
+            *   (q*adj(f)/(f*adj(f)+g*adj(g)), q*adj(g)/(f*adj(f)+g*adj(g)))
+            * (it is actually the (N+1)-th row of the Gram-Schmidt basis).
+            *
+            * In the binary case, coefficients of f and g are generated
+            * independently of each other, with a discrete Gaussian
+            * distribution of standard deviation 1.17*sqrt(q/(2*N)). Then,
+            * the two vectors have expected norm 1.17*sqrt(q), which is
+            * also our acceptance bound: we require both vectors to be no
+            * larger than that (this will be satisfied about 1/4th of the
+            * time, thus we expect sampling new (f,g) about 4 times for that
+            * step).
+            *
+            * We require that Res(f,phi) and Res(g,phi) are both odd (the
+            * NTRU equation solver requires it).
+            */
+            for (;;) {
+                int rt1, rt2, rt3;
+                FalconFPR bnorm;
+                uint normf, normg, norm;
+                int lim;
+
+                /*
+                * The poly_small_mkgauss() function makes sure
+                * that the sum of coefficients is 1 modulo 2
+                * (i.e. the resultant of the polynomial with phi
+                * will be odd).
+                */
+                poly_small_mkgauss(rc, fsrc, f, logn);
+                poly_small_mkgauss(rc, gsrc, g, logn);
+
+                /*
+                * Verify that all coefficients are within the bounds
+                * defined in max_fg_bits. This is the case with
+                * overwhelming probability; this guarantees that the
+                * key will be encodable with FALCON_COMP_TRIM.
+                */
+                lim = 1 << (this.codec.max_fg_bits[logn] - 1);
+                for (u = 0; u < n; u ++) {
+                    /*
+                    * We can use non-CT tests since on any failure
+                    * we will discard f and g.
+                    */
+                    if (fsrc[f+u] >= lim || fsrc[f+u] <= -lim
+                        || gsrc[g+u] >= lim || gsrc[g+u] <= -lim)
+                    {
+                        lim = -1;
+                        break;
+                    }
+                }
+                if (lim < 0) {
+                    continue;
+                }
+
+                /*
+                * Bound is 1.17*sqrt(q). We compute the squared
+                * norms. With q = 12289, the squared bound is:
+                *   (1.17^2)* 12289 = 16822.4121
+                * Since f and g are integral, the squared norm
+                * of (g,-f) is an integer.
+                */
+                normf = poly_small_sqnorm(fsrc, f, logn);
+                normg = poly_small_sqnorm(gsrc, g, logn);
+                norm = (uint)((normf + normg) | -((normf | normg) >> 31));
+                if (norm >= 16823) {
+                    continue;
+                }
+
+                /*
+                * We compute the orthogonalized vector norm.
+                */
+                FalconFPR[] rtmp = new FalconFPR[3 * n];
+                rt1 = 0;
+                rt2 = rt1 + n;
+                rt3 = rt2 + n;
+                poly_small_to_fp(rtmp, rt1, fsrc, f, logn);
+                poly_small_to_fp(rtmp, rt2, gsrc, g, logn);
+                this.ffte.FFT(rtmp, rt1, logn);
+                this.ffte.FFT(rtmp, rt2, logn);
+                this.ffte.poly_invnorm2_fft(rtmp, rt3, rtmp, rt1, rtmp, rt2, logn);
+                this.ffte.poly_adj_fft(rtmp, rt1, logn);
+                this.ffte.poly_adj_fft(rtmp, rt2, logn);
+                this.ffte.poly_mulconst(rtmp, rt1, this.fpre.fpr_q, logn);
+                this.ffte.poly_mulconst(rtmp, rt2, this.fpre.fpr_q, logn);
+                this.ffte.poly_mul_autoadj_fft(rtmp, rt1, rtmp, rt3, logn);
+                this.ffte.poly_mul_autoadj_fft(rtmp, rt2, rtmp, rt3, logn);
+                this.ffte.iFFT(rtmp, rt1, logn);
+                this.ffte.iFFT(rtmp, rt2, logn);
+                bnorm = this.fpre.fpr_zero;
+                for (u = 0; u < n; u ++) {
+                    bnorm = this.fpre.fpr_add(bnorm, this.fpre.fpr_sqr(rtmp[rt1+u]));
+                    bnorm = this.fpre.fpr_add(bnorm, this.fpre.fpr_sqr(rtmp[rt2+u]));
+                }
+                if (!this.fpre.fpr_lt(bnorm, this.fpre.fpr_bnorm_max)) {
+                    continue;
+                }
+
+                /*
+                * Compute public key h = g/f mod X^N+1 mod q. If this
+                * fails, we must restart.
+                */
+                ushort[] htmp;
+                ushort[] h2src;
+                if (hsrc == null) {
+                    htmp = new ushort[2 * n];
+                    h2 = 0;
+                    h2src = htmp;
+                    tmp2 = h2 + n;
+                } else {
+                    htmp = new ushort[n];
+                    h2 = h;
+                    h2src = hsrc;
+                    tmp2 = 0;
+                }
+                if (vrfy.compute_public(h2src, h2, fsrc, f, gsrc, g, logn, htmp, tmp2) == 0) {
+                    continue;
+                }
+
+                /*
+                * Solve the NTRU equation to get F and G.
+                */
+                uint[] itmp = logn > 2 ? new uint[28 * n] : new uint[28 * n * 3];
+                lim = (1 << (this.codec.max_FG_bits[logn] - 1)) - 1;
+                if (solve_NTRU(logn, Fsrc, F, Gsrc, G, fsrc, f, gsrc, g, lim, itmp, 0) == 0) {
+                    continue;
+                }
+
+                /*
+                * Key pair is generated.
+                */
+                break;
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconNIST.cs b/crypto/src/pqc/crypto/falcon/FalconNIST.cs
new file mode 100644
index 000000000..0d2ba46e0
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconNIST.cs
@@ -0,0 +1,317 @@
+using System;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    internal class FalconNist
+    {
+        private FalconCodec codec;
+        private FalconVrfy vrfy;
+        private FalconCommon common;
+        private SecureRandom random;
+        private uint logn;
+        private uint noncelen;
+        private int CRYPTO_BYTES;
+        private int CRYPTO_PUBLICKEYBYTES;
+        private int CRYPTO_SECRETKEYBYTES;
+
+        internal uint GetNonceLength() {
+            return this.noncelen;
+        }
+        internal uint GetLogn() {
+            return this.logn;
+        }
+        internal int GetCryptoBytes() {
+            return this.CRYPTO_BYTES;
+        }
+
+        internal FalconNist(SecureRandom random, uint logn, uint noncelen) {
+            this.logn = logn;
+            this.codec = new FalconCodec();
+            this.common = new FalconCommon();
+            this.vrfy = new FalconVrfy(this.common);
+            this.random = random;
+            this.noncelen = noncelen;
+            int n = (int)1 << (int)logn;
+            this.CRYPTO_PUBLICKEYBYTES = 1 + (14 * n / 8);
+            if (logn == 10)
+            {
+                this.CRYPTO_SECRETKEYBYTES = 2305;
+                this.CRYPTO_BYTES = 1330;
+            }
+            else if (logn == 9 || logn == 8)
+            {
+                this.CRYPTO_SECRETKEYBYTES = 1 + (6 * n * 2 / 8) + n;
+                this.CRYPTO_BYTES = 690; // TODO find what the byte length is here when not at degree 9 or 10
+            }
+            else if (logn == 7 || logn == 6)
+            {
+                this.CRYPTO_SECRETKEYBYTES = 1 + (7 * n * 2 / 8) + n;
+                this.CRYPTO_BYTES = 690;
+            }
+            else
+            {
+                this.CRYPTO_SECRETKEYBYTES = 1 + (n * 2) + n;
+                this.CRYPTO_BYTES = 690;
+            }
+        }
+
+        internal int crypto_sign_keypair(out byte[] pk, out byte[] fEnc, out byte[] gEnc, out byte[] FEnc)
+        {
+            byte[] sk = new byte[CRYPTO_SECRETKEYBYTES];
+            pk = new byte[CRYPTO_PUBLICKEYBYTES];
+            int n = (int)1 << (int)this.logn;
+            SHAKE256 rng = new SHAKE256();
+            sbyte[] f = new sbyte[n], g = new sbyte[n], F = new sbyte[n];
+            ushort[] h = new ushort[n];
+            byte[] seed = new byte[48];
+            int u, v;
+            FalconKeygen keygen = new FalconKeygen(this.codec, this.vrfy);
+
+            /*
+            * Generate key pair.
+            */
+            this.random.NextBytes(seed);
+            rng.i_shake256_init();
+            rng.i_shake256_inject(seed, 0, seed.Length);
+            rng.i_shake256_flip();
+            keygen.keygen(rng, f, 0, g, 0, F, 0, null, 0, h, 0, this.logn);
+
+            /*
+            * Encode private key.
+            */
+            sk[0] = (byte)(0x50 + this.logn);
+            u = 1;
+            v = this.codec.trim_i8_encode(sk, u, CRYPTO_SECRETKEYBYTES - u,
+                f, 0, this.logn, this.codec.max_fg_bits[this.logn]);
+            if (v == 0) {
+                // TODO check which exception types to use here
+                throw new InvalidOperationException("f encode failed");
+            }
+            fEnc = Arrays.CopyOfRange(sk, u, u + v);
+            u += v;
+            v = this.codec.trim_i8_encode(sk, u, CRYPTO_SECRETKEYBYTES - u,
+                g, 0, this.logn, this.codec.max_fg_bits[this.logn]);
+            if (v == 0) {
+                throw new InvalidOperationException("g encode failed");
+            }
+            gEnc = Arrays.CopyOfRange(sk, u, u + v);
+            u += v;
+            v = this.codec.trim_i8_encode(sk,  u, CRYPTO_SECRETKEYBYTES - u,
+                F, 0, this.logn, this.codec.max_FG_bits[this.logn]);
+            if (v == 0) {
+                 throw new InvalidOperationException("F encode failed");
+            }
+            FEnc = Arrays.CopyOfRange(sk, u, u + v);
+            u += v;
+            if (u != CRYPTO_SECRETKEYBYTES) {
+                 throw new InvalidOperationException("secret key encoding failed");
+            }
+
+            /*
+            * Encode public key.
+            */
+            pk[0] = (byte)(0x00 + this.logn);
+            v = this.codec.modq_encode(pk, 1, CRYPTO_PUBLICKEYBYTES - 1, h, 0, this.logn);
+            if (v != CRYPTO_PUBLICKEYBYTES - 1) {
+                 throw new InvalidOperationException("public key encoding failed");
+            }
+
+            pk = Arrays.CopyOfRange(pk, 1, pk.Length);
+
+            return 0;
+        }
+
+        internal byte[] crypto_sign(byte[] sm,
+            byte[] msrc, int m, uint mlen,
+            byte[] sksrc, int sk)
+        {
+            // TEMPALLOC union {
+            //     uint8_t b[72 * 1024];
+            //     uint64_t dummy_u64;
+            //     fpr dummy_fpr;
+            // } tmp;
+            int u, v, sig_len;
+            int n = (int)1 << (int)this.logn;
+            sbyte[] f = new sbyte[n],
+                    g = new sbyte[n],
+                    F = new sbyte[n],
+                    G = new sbyte[n];
+            
+            short[] sig = new short[n];
+            ushort[] hm = new ushort[n];
+            
+            byte[] seed = new byte[48],
+                    nonce = new byte[this.noncelen];
+            
+            byte[] esig = new byte[this.CRYPTO_BYTES - 2 - this.noncelen];
+            SHAKE256 sc = new SHAKE256();
+            FalconSign signer = new FalconSign(this.common);
+
+            // /*
+            // * Decode the private key.
+            // */
+            // if (sksrc[sk+0] != 0x50 + this.logn) {
+            //     throw new ArgumentException("private key header incorrect");
+            // }
+            u = 0;
+            v = this.codec.trim_i8_decode(f, 0, this.logn, this.codec.max_fg_bits[this.logn],
+                sksrc, sk + u, CRYPTO_SECRETKEYBYTES - u);
+            if (v == 0)
+            {
+                throw new InvalidOperationException("f decode failed");
+            }
+            u += v;
+            v = this.codec.trim_i8_decode(g, 0, this.logn, this.codec.max_fg_bits[this.logn],
+                sksrc, sk + u, CRYPTO_SECRETKEYBYTES - u);
+            if (v == 0)
+            {
+                throw new InvalidOperationException("g decode failed");
+            }
+            u += v;
+            v = this.codec.trim_i8_decode(F, 0, this.logn, this.codec.max_FG_bits[this.logn],
+                sksrc, sk + u, CRYPTO_SECRETKEYBYTES - u);
+            if (v == 0) 
+            {
+                throw new InvalidOperationException("F decode failed");
+            }
+            u += v;
+            if (u != CRYPTO_SECRETKEYBYTES - 1) 
+            {
+                throw new InvalidOperationException("full Key not used");
+            }
+            if (this.vrfy.complete_private(G, 0, f, 0, g, 0, F, 0, this.logn, new ushort[2 * n],0) == 0) 
+            {
+                throw new InvalidOperationException("complete private failed");
+            }
+
+            /*
+            * Create a random nonce (40 bytes).
+            */
+            this.random.NextBytes(nonce);
+
+            /*
+            * Hash message nonce + message into a vector.
+            */
+            sc.i_shake256_init();
+            sc.i_shake256_inject(nonce,0,nonce.Length);
+            sc.i_shake256_inject(msrc,m, (int)mlen);
+            sc.i_shake256_flip();
+            this.common.hash_to_point_vartime(sc, hm, 0, this.logn);
+
+            /*
+            * Initialize a RNG.
+            */
+            this.random.NextBytes(seed);
+            sc.i_shake256_init();
+            sc.i_shake256_inject(seed, 0, seed.Length);
+            sc.i_shake256_flip();
+
+            /*
+            * Compute the signature.
+            */
+            signer.sign_dyn(sig, 0, sc, f, 0, g, 0, F, 0, G, 0, hm, 0, this.logn, new FalconFPR[10 * n], 0);
+
+            /*
+             * Encode the signature. Format is:
+             *   signature header     1 bytes
+             *   nonce                40 bytes
+             *   signature            slen bytes
+             */
+            esig[0] = (byte)(0x20 + logn);
+            sig_len = codec.comp_encode(esig, 1, esig.Length - 1, sig, 0, logn);
+            if (sig_len == 0)
+            {
+                throw new InvalidOperationException("signature failed to generate");
+            }
+            sig_len++;
+
+            // header
+            sm[0] = (byte)(0x30 + logn);
+            // nonce
+            Array.Copy(nonce, 0, sm, 1, noncelen);
+
+            // signature
+            Array.Copy(esig, 0, sm, 1 + noncelen, sig_len);
+
+            return Arrays.CopyOfRange(sm, 0, 1 + (int)noncelen + sig_len);
+        }
+
+        internal int crypto_sign_open(byte[] sig_encoded, byte[] nonce, byte[] m,
+            byte[] pksrc, int pk)
+        {
+            int sig_len, msg_len;
+            int n = (int)1 << (int)this.logn;
+            ushort[] h = new ushort[n],
+                    hm = new ushort[n];
+            short[] sig = new short[n];
+            SHAKE256 sc = new SHAKE256();
+
+            /*
+            * Decode public key.
+            */
+            // if (pksrc[pk+0] != 0x00 + this.logn) {
+            //     return -1;
+            // }
+            if (this.codec.modq_decode(h, 0, this.logn, pksrc, pk, CRYPTO_PUBLICKEYBYTES - 1)
+                != CRYPTO_PUBLICKEYBYTES - 1)
+            {
+                return -1;
+            }
+            this.vrfy.to_ntt_monty(h, 0, this.logn);
+
+            /*
+            * Find nonce, signature, message length.
+            */
+            // if (smlen < 2 + this.noncelen) {
+            //     return -1;
+            // }
+            // sig_len = ((int)sm[0] << 8) | (int)sm[1];
+            sig_len = sig_encoded.Length;
+            // if (sig_len > (smlen - 2 - this.noncelen)) {
+            //     return -1;
+            // }
+            // msg_len = smlen - 2 - this.noncelen - sig_len;
+            msg_len = m.Length;
+
+            /*
+            * Decode signature.
+            */
+            // esig = sm + 2 + this.noncelen + msg_len;
+            if (sig_len < 1 || sig_encoded[0] != (byte)(0x20 + this.logn)) {
+                return -1;
+            }
+            if (this.codec.comp_decode(sig, 0, this.logn, sig_encoded,
+                1, sig_len - 1) != sig_len - 1)
+            {
+                return -1;
+            }
+
+            /*
+            * Hash nonce + message into a vector.
+            */
+            sc.i_shake256_init();
+            // sc.i_shake256_inject(sm + 2, this.noncelen + msg_len);
+            sc.i_shake256_inject(nonce, 0, (int)this.noncelen);
+            sc.i_shake256_inject(m, 0, m.Length);
+            sc.i_shake256_flip();
+            this.common.hash_to_point_vartime(sc, hm, 0, this.logn);
+
+            /*
+            * Verify signature.
+            */
+            if (!this.vrfy.verify_raw(hm, 0, sig, 0, h, 0, this.logn, new ushort[n], 0)) {
+                return -1;
+            }
+
+            /*
+            * Return plaintext. - not in use
+            */
+            // Array.Copy(sm + 2 + this.noncelen, m, msg_len);
+            // *mlen = msg_len;
+            return 0;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconParameters.cs b/crypto/src/pqc/crypto/falcon/FalconParameters.cs
new file mode 100644
index 000000000..10d22a241
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconParameters.cs
@@ -0,0 +1,29 @@
+using Org.BouncyCastle.Crypto;
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public sealed class FalconParameters 
+        : ICipherParameters
+    {
+        public static readonly FalconParameters falcon_512 = new FalconParameters("falcon512", 9, 40);
+        public static readonly FalconParameters falcon_1024 = new FalconParameters("falcon1024", 10, 40);
+
+        private readonly string name;
+        private readonly uint logn;
+        private readonly uint nonce_length;
+
+        private FalconParameters(string name, uint logn, uint nonce_length)
+        {
+            this.name = name;
+            this.logn = logn;
+            this.nonce_length = nonce_length;
+        }
+
+        public int LogN => Convert.ToInt32(logn);
+
+        public int NonceLength => Convert.ToInt32(nonce_length);
+
+        public string Name => name;
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconPrivateKeyParameters.cs b/crypto/src/pqc/crypto/falcon/FalconPrivateKeyParameters.cs
new file mode 100644
index 000000000..12b055add
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconPrivateKeyParameters.cs
@@ -0,0 +1,48 @@
+using Org.BouncyCastle.Utilities;
+
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public sealed class FalconPrivateKeyParameters
+        : FalconKeyParameters
+    {
+        private readonly byte[] pk;
+        private readonly byte[] f;
+        private readonly byte[] g;
+        private readonly byte[] F;
+
+        public FalconPrivateKeyParameters(FalconParameters parameters, byte[] f, byte[] g, byte[] F, byte[] pk_encoded)
+            : base(true, parameters)
+        {
+            this.f = Arrays.Clone(f);
+            this.g = Arrays.Clone(g);
+            this.F = Arrays.Clone(F);
+            this.pk = Arrays.Clone(pk_encoded);
+        }
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.ConcatenateAll(f, g, F);
+        }
+        
+        public byte[] GetPublicKey()
+        {
+            return Arrays.Clone(pk);
+        }
+
+        public byte[] GetSpolyLittleF()
+        {
+            return Arrays.Clone(f);
+        }
+
+        public byte[] GetG()
+        {
+            return Arrays.Clone(g);
+        }
+
+        public byte[] GetSpolyBigF()
+        {
+            return Arrays.Clone(F);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconPublicKeyParameters.cs b/crypto/src/pqc/crypto/falcon/FalconPublicKeyParameters.cs
new file mode 100644
index 000000000..fa6b627ab
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconPublicKeyParameters.cs
@@ -0,0 +1,21 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public sealed class FalconPublicKeyParameters
+        : FalconKeyParameters
+    {
+        private readonly byte[] publicKey;
+
+        public FalconPublicKeyParameters(FalconParameters parameters, byte[] h)
+            : base(false, parameters)
+        {
+            this.publicKey = Arrays.Clone(h);
+        }
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.Clone(publicKey);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconRNG.cs b/crypto/src/pqc/crypto/falcon/FalconRNG.cs
new file mode 100644
index 000000000..31f04d5d7
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconRNG.cs
@@ -0,0 +1,261 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconRNG
+    {
+        byte[] bd;
+        //ulong bdummy_u64;
+        byte[] sd;
+        //ulong sdummy_u64;
+        //int type;
+        int ptr;
+
+        FalconConversions convertor;
+
+        internal FalconRNG() {
+            this.bd = new byte[512];
+            this.sd = new byte[256];
+            this.convertor = new FalconConversions();
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+
+        internal void prng_init(SHAKE256 src)
+        {
+            /*
+            * To ensure reproducibility for a given seed, we
+            * must enforce little-endian interpretation of
+            * the state words.
+            */
+            byte[] tmp = new byte[56];
+            ulong th, tl;
+            int i;
+
+            src.i_shake256_extract(tmp,0, 56);
+            for (i = 0; i < 14; i ++) {
+                uint w;
+
+                w = (uint)tmp[(i << 2) + 0]
+                    | ((uint)tmp[(i << 2) + 1] << 8)
+                    | ((uint)tmp[(i << 2) + 2] << 16)
+                    | ((uint)tmp[(i << 2) + 3] << 24);
+                //*(uint *)(this.sd + (i << 2)) = w;
+                Array.Copy(convertor.int_to_bytes((int)w), 0, this.sd, i << 2, 4);
+            }
+            //        tl = *(uint32_t *)(p->state.d + 48);
+            tl = convertor.bytes_to_uint(this.sd, 48);
+            //        th = *(uint32_t *)(p->state.d + 52);
+            th = convertor.bytes_to_uint(this.sd, 52);
+            Array.Copy(convertor.ulong_to_bytes(tl + (th << 32)), 0, this.sd, 48, 8);
+            this.prng_refill();
+        }
+
+        /*
+        * PRNG based on ChaCha20.
+        *
+        * State consists in key (32 bytes) then IV (16 bytes) and block counter
+        * (8 bytes). Normally, we should not care about local endianness (this
+        * is for a PRNG), but for the NIST competition we need reproducible KAT
+        * vectors that work across architectures, so we enforce little-endian
+        * interpretation where applicable. Moreover, output words are "spread
+        * out" over the output buffer with the interleaving pattern that is
+        * naturally obtained from the AVX2 implementation that runs eight
+        * ChaCha20 instances in parallel.
+        *
+        * The block counter is XORed into the first 8 bytes of the IV.
+        */
+        private void QROUND(uint[] state, int a, int b, int c, int d) {
+            state[a] += state[b];
+            state[d] ^= state[a];
+            state[d] = (state[d] << 16) | (state[d] >> 16);
+            state[c] += state[d];
+            state[b] ^= state[c];
+            state[b] = (state[b] << 12) | (state[b] >> 20);
+            state[a] += state[b];
+            state[d] ^= state[a];
+            state[d] = (state[d] <<  8) | (state[d] >> 24);
+            state[c] += state[d];
+            state[b] ^= state[c];
+            state[b] = (state[b] <<  7) | (state[b] >> 25);
+        }
+        void prng_refill()
+        {
+
+            uint[] CW = {
+                0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
+            };
+
+            ulong cc;
+            int u;
+
+            /*
+            * State uses local endianness. Only the output bytes must be
+            * converted to little endian (if used on a big-endian machine).
+            */
+            //cc = *(ulong *)(this.sd + 48);
+            cc = convertor.bytes_to_ulong(this.sd, 48);
+            for (u = 0; u < 8; u ++) {
+                uint[] state = new uint[16];
+                int v;
+                int i;
+
+                // memcpy(&state[0], CW, sizeof CW);
+                Array.Copy(CW, 0, state, 0, 4);
+                // memcpy(&state[4], this.sd, 48);
+                Array.Copy(convertor.bytes_to_uint_array(this.sd, 0, 12), 0, state, 4, 12);
+
+                state[14] ^= (uint)cc;
+                state[15] ^= (uint)(cc >> 32);
+                for (i = 0; i < 10; i ++) {
+
+                    QROUND(state, 0,  4,  8, 12);
+                    QROUND(state, 1,  5,  9, 13);
+                    QROUND(state, 2,  6, 10, 14);
+                    QROUND(state, 3,  7, 11, 15);
+                    QROUND(state, 0,  5, 10, 15);
+                    QROUND(state, 1,  6, 11, 12);
+                    QROUND(state, 2,  7,  8, 13);
+                    QROUND(state, 3,  4,  9, 14);
+
+                }
+
+                for (v = 0; v < 4; v++)
+                {
+                    state[v] += CW[v];
+                }
+                for (v = 4; v < 14; v++)
+                {
+                    //                state[v] += ((uint32_t *)p->state.d)[v - 4];
+                    // we multiply the -4 by 4 to account for 4 bytes per int
+                    state[v] += convertor.bytes_to_uint(sd, (4 * v) - 16);
+                }
+                //            state[14] += ((uint32_t *)p->state.d)[10]
+                //            ^ (uint32_t)cc;
+                state[14] += (uint)(convertor.bytes_to_uint(sd, 40) ^ ((int)cc));
+                //            state[15] += ((uint32_t *)p->state.d)[11]
+                //            ^ (uint32_t)(cc >> 32);
+                state[15] += (uint)(convertor.bytes_to_uint(sd, 44) ^ ((int)(cc >> 32)));
+                cc ++;
+
+                /*
+                * We mimic the interleaving that is used in the AVX2
+                * implementation.
+                */
+                for (v = 0; v < 16; v ++) {
+                    this.bd[(u << 2) + (v << 5) + 0] =
+                        (byte)state[v];
+                    this.bd[(u << 2) + (v << 5) + 1] =
+                        (byte)(state[v] >> 8);
+                    this.bd[(u << 2) + (v << 5) + 2] =
+                        (byte)(state[v] >> 16);
+                    this.bd[(u << 2) + (v << 5) + 3] =
+                        (byte)(state[v] >> 24);
+                }
+            }
+            //*(ulong *)(this.sd + 48) = cc;
+            Array.Copy(convertor.ulong_to_bytes(cc), 0, sd, 48, 8);
+
+
+            this.ptr = 0;
+        }
+
+        internal void prng_get_bytes( byte[] dstsrc, int dst, int len)
+        {
+            int buf;
+
+            buf = dst;
+            while (len > 0) {
+                int clen;
+
+                clen = (this.bd.Length) - this.ptr;
+                if (clen > len) {
+                    clen = len;
+                }
+                // memcpy(buf, this.bd, clen);
+                Array.Copy(this.bd, 0, dstsrc, buf, clen);
+                buf += clen;
+                len -= clen;
+                this.ptr += clen;
+                if (this.ptr == this.bd.Length) {
+                    this.prng_refill();
+                }
+            }
+        }
+
+        /*
+         * Get a 64-bit random value from a PRNG.
+         */
+        internal ulong prng_get_u64()
+        {
+            int u;
+
+            /*
+            * If there are less than 9 bytes in the buffer, we refill it.
+            * This means that we may drop the last few bytes, but this allows
+            * for faster extraction code. Also, it means that we never leave
+            * an empty buffer.
+            */
+            u = this.ptr;
+            if (u >= (this.bd.Length) - 9) {
+                this.prng_refill();
+                u = 0;
+            }
+            this.ptr = u + 8;
+
+            /*
+            * On systems that use little-endian encoding and allow
+            * unaligned accesses, we can simply read the data where it is.
+            */
+            return (ulong)this.bd[u + 0]
+                | ((ulong)this.bd[u + 1] << 8)
+                | ((ulong)this.bd[u + 2] << 16)
+                | ((ulong)this.bd[u + 3] << 24)
+                | ((ulong)this.bd[u + 4] << 32)
+                | ((ulong)this.bd[u + 5] << 40)
+                | ((ulong)this.bd[u + 6] << 48)
+                | ((ulong)this.bd[u + 7] << 56);
+        }
+
+        /*
+        * Get an 8-bit random value from a PRNG.
+        */
+        internal uint prng_get_u8()
+        {
+            uint v;
+
+            v = this.bd[this.ptr ++];
+            if (this.ptr == this.bd.Length) {
+                this.prng_refill();
+            }
+            return v;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconSign.cs b/crypto/src/pqc/crypto/falcon/FalconSign.cs
new file mode 100644
index 000000000..613ef498b
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconSign.cs
@@ -0,0 +1,974 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconSign
+    {
+
+        FalconFFT ffte;
+        FPREngine fpre;
+        FalconCommon common;
+
+        internal FalconSign(FalconCommon common) {
+            this.ffte = new FalconFFT();
+            this.fpre = new FPREngine();
+            this.common = common;
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+
+        /*
+        * Binary case:
+        *   N = 2^logn
+        *   phi = X^N+1
+        */
+
+        /*
+        * Get the size of the LDL tree for an input with polynomials of size
+        * 2^logn. The size is expressed in the number of elements.
+        */
+        internal uint ffLDL_treesize(uint logn)
+        {
+            /*
+            * For logn = 0 (polynomials are constant), the "tree" is a
+            * single element. Otherwise, the tree node has size 2^logn, and
+            * has two child trees for size logn-1 each. Thus, treesize s()
+            * must fulfill these two relations:
+            *
+            *   s(0) = 1
+            *   s(logn) = (2^logn) + 2*s(logn-1)
+            */
+            return (logn + 1) << (int)logn;
+        }
+
+        /*
+        * Inner function for ffLDL_fft(). It expects the matrix to be both
+        * auto-adjoint and quasicyclic; also, it uses the source operands
+        * as modifiable temporaries.
+        *
+        * tmp[] must have room for at least one polynomial.
+        */
+        internal void ffLDL_fft_inner(FalconFPR[] treesrc, int tree,
+            FalconFPR[] g0src, int g0, FalconFPR[] g1src, int g1, uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+            int n, hn;
+
+            n = (int)1 << (int)logn;
+            if (n == 1) {
+                treesrc[tree+0] = g0src[g0 + 0];
+                return;
+            }
+            hn = n >> 1;
+
+            /*
+            * The LDL decomposition yields L (which is written in the tree)
+            * and the diagonal of D. Since d00 = g0, we just write d11
+            * into tmp.
+            */
+            this.ffte.poly_LDLmv_fft(tmpsrc, tmp, treesrc, tree, g0src, g0, g1src, g1, g0src, g0, logn);
+
+            /*
+            * Split d00 (currently in g0) and d11 (currently in tmp). We
+            * reuse g0 and g1 as temporary storage spaces:
+            *   d00 splits into g1, g1+hn
+            *   d11 splits into g0, g0+hn
+            */
+            this.ffte.poly_split_fft(g1src, g1, g1src, g1 + hn, g0src, g0, logn);
+            this.ffte.poly_split_fft(g0src, g0, g0src, g0 + hn, tmpsrc, tmp, logn);
+
+            /*
+            * Each split result is the first row of a new auto-adjoint
+            * quasicyclic matrix for the next recursive step.
+            */
+            ffLDL_fft_inner(treesrc, tree + n,
+                g1src, g1, g1src, g1 + hn, logn - 1, tmpsrc, tmp);
+            ffLDL_fft_inner(treesrc, tree + n + (int)ffLDL_treesize(logn - 1),
+                g0src, g0, g0src, g0 + hn, logn - 1, tmpsrc, tmp);
+        }
+
+        /*
+        * Compute the ffLDL tree of an auto-adjoint matrix G. The matrix
+        * is provided as three polynomials (FFT representation).
+        *
+        * The "tree" array is filled with the computed tree, of size
+        * (logn+1)*(2^logn) elements (see ffLDL_treesize()).
+        *
+        * Input arrays MUST NOT overlap, except possibly the three unmodified
+        * arrays g00, g01 and g11. tmp[] should have room for at least three
+        * polynomials of 2^logn elements each.
+        */
+        internal void ffLDL_fft(FalconFPR[] treesrc, int tree, FalconFPR[] g00src, int g00,
+            FalconFPR[] g01src, int g01, FalconFPR[] g11src, int g11,
+            uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+            int n, hn;
+            int d00, d11;
+
+            n = (int)1 << (int)logn;
+            if (n == 1) {
+                treesrc[tree+0] = g00src[g00+0];
+                return;
+            }
+            hn = n >> 1;
+            d00 = tmp;
+            d11 = tmp + n;
+            tmp += n << 1;
+
+            // memcpy(d00, g00, n * sizeof *g00);
+            Array.Copy(g00src, g00, tmpsrc, d00, n);
+            this.ffte.poly_LDLmv_fft(tmpsrc, d11, treesrc, tree, g00src, g00, g01src, g01, g11src, g11, logn);
+
+            this.ffte.poly_split_fft(tmpsrc, tmp, tmpsrc, tmp + hn, tmpsrc, d00, logn);
+            this.ffte.poly_split_fft(tmpsrc, d00, tmpsrc, d00 + hn, tmpsrc, d11, logn);
+            // memcpy(d11, tmp, n * sizeof *tmp);
+            Array.Copy(tmpsrc, tmp, tmpsrc, d11, n);
+            ffLDL_fft_inner(treesrc, tree + n,
+                tmpsrc, d11, tmpsrc, d11 + hn, logn - 1, tmpsrc, tmp);
+            ffLDL_fft_inner(treesrc, tree + n + (int)ffLDL_treesize(logn - 1),
+                tmpsrc, d00, tmpsrc, d00 + hn, logn - 1, tmpsrc, tmp);
+        }
+
+        /*
+        * Normalize an ffLDL tree: each leaf of value x is replaced with
+        * sigma / sqrt(x).
+        */
+        internal void ffLDL_binary_normalize(FalconFPR[] treesrc, int tree, uint orig_logn, uint logn)
+        {
+            /*
+            * TODO: make an iterative version.
+            */
+            int n;
+
+            n = (int)1 << (int)logn;
+            if (n == 1) {
+                /*
+                * We actually store in the tree leaf the inverse of
+                * the value mandated by the specification: this
+                * saves a division both here and in the sampler.
+                */
+                treesrc[tree+0] = this.fpre.fpr_mul(this.fpre.fpr_sqrt(treesrc[tree+0]), this.fpre.fpr_inv_sigma[orig_logn]);
+            } else {
+                ffLDL_binary_normalize(treesrc, tree + n, orig_logn, logn - 1);
+                ffLDL_binary_normalize(treesrc, tree + n + (int)ffLDL_treesize(logn - 1),
+                    orig_logn, logn - 1);
+            }
+        }
+
+        /* =================================================================== */
+
+        /*
+        * Convert an integer polynomial (with small values) into the
+        * representation with complex numbers.
+        */
+        internal void smallints_to_fpr(FalconFPR[] rsrc, int r, sbyte[] tsrc, int t, uint logn)
+        {
+            int n, u;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                rsrc[r+u] = this.fpre.fpr_of(tsrc[t+u]);
+            }
+        }
+
+        /*
+        * The expanded private key contains:
+        *  - The B0 matrix (four elements)
+        *  - The ffLDL tree
+        */
+
+        int skoff_b00(uint logn)
+        {
+            return 0;
+        }
+
+        int skoff_b01(uint logn)
+        {
+            return (int)1 << (int)logn;
+        }
+
+        int skoff_b10(uint logn)
+        {
+            return 2 * (int)1 << (int)logn;
+        }
+
+        int skoff_b11(uint logn)
+        {
+            return 3 * (int)1 << (int)logn;
+        }
+
+        int skoff_tree(uint logn)
+        {
+            return 4 * (int)1 << (int)logn;
+        }
+
+        /*
+        * Perform Fast Fourier Sampling for target vector t. The Gram matrix
+        * is provided (G = [[g00, g01], [adj(g01), g11]]). The sampled vector
+        * is written over (t0,t1). The Gram matrix is modified as well. The
+        * tmp[] buffer must have room for four polynomials.
+        */
+        internal void ffSampling_fft_dyntree(SamplerZ samp,
+            FalconFPR[] t0src, int t0, FalconFPR[] t1src, int t1,
+            FalconFPR[] g00src, int g00, FalconFPR[] g01src, int g01, FalconFPR[] g11src, int g11,
+            uint orig_logn, uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+            int n, hn;
+            int z0, z1;
+
+            /*
+            * Deepest level: the LDL tree leaf value is just g00 (the
+            * array has length only 1 at this point); we normalize it
+            * with regards to sigma, then use it for sampling.
+            */
+            if (logn == 0) {
+                FalconFPR leaf;
+
+                leaf = g00src[g00+0];
+                leaf = this.fpre.fpr_mul(this.fpre.fpr_sqrt(leaf), this.fpre.fpr_inv_sigma[orig_logn]);
+                t0src[t0+0] = this.fpre.fpr_of(samp.Sample(t0src[t0+0], leaf));
+                t1src[t1+0] = this.fpre.fpr_of(samp.Sample(t1src[t1+0], leaf));
+                return;
+            }
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+
+            /*
+            * Decompose G into LDL. We only need d00 (identical to g00),
+            * d11, and l10; we do that in place.
+            */
+            this.ffte.poly_LDL_fft(g00src, g00, g01src, g01, g11src, g11, logn);
+
+            /*
+            * Split d00 and d11 and expand them into half-size quasi-cyclic
+            * Gram matrices. We also save l10 in tmp[].
+            */
+            this.ffte.poly_split_fft(tmpsrc, tmp, tmpsrc, tmp + hn, g00src, g00, logn);
+            // memcpy(g00, tmp, n * sizeof *tmp);
+            Array.Copy(tmpsrc, tmp, g00src, g00, n);
+            this.ffte.poly_split_fft(tmpsrc, tmp, tmpsrc, tmp + hn, g11src, g11, logn);
+            // memcpy(g11, tmp, n * sizeof *tmp);
+            // memcpy(tmp, g01, n * sizeof *g01);
+            // memcpy(g01, g00, hn * sizeof *g00);
+            // memcpy(g01 + hn, g11, hn * sizeof *g00);
+            Array.Copy(tmpsrc, tmp, g11src, g11, n);
+            Array.Copy(g01src, g01, tmpsrc, tmp, n);
+            Array.Copy(g00src, g00,g01src, g01, hn);
+            Array.Copy(g11src, g11, g01src, g01 + hn, hn);
+            /*
+            * The half-size Gram matrices for the recursive LDL tree
+            * building are now:
+            *   - left sub-tree: g00, g00+hn, g01
+            *   - right sub-tree: g11, g11+hn, g01+hn
+            * l10 is in tmp[].
+            */
+
+            /*
+            * We split t1 and use the first recursive call on the two
+            * halves, using the right sub-tree. The result is merged
+            * back into tmp + 2*n.
+            */
+            z1 = tmp + n;
+            this.ffte.poly_split_fft(tmpsrc, z1, tmpsrc, z1 + hn, tmpsrc, t1, logn);
+            ffSampling_fft_dyntree(samp, tmpsrc, z1, tmpsrc, z1 + hn,
+                g11src, g11, g11src, g11 + hn, g01src, g01 + hn, orig_logn, logn - 1, tmpsrc, z1 + n);
+            this.ffte.poly_merge_fft(tmpsrc, tmp + (n << 1), tmpsrc, z1, tmpsrc, z1 + hn, logn);
+
+            /*
+            * Compute tb0 = t0 + (t1 - z1) * l10.
+            * At that point, l10 is in tmp, t1 is unmodified, and z1 is
+            * in tmp + (n << 1). The buffer in z1 is free.
+            *
+            * In the end, z1 is written over t1, and tb0 is in t0.
+            */
+            // memcpy(z1, t1, n * sizeof *t1);
+            Array.Copy(tmpsrc, t1, tmpsrc, z1, n);
+            this.ffte.poly_sub(tmpsrc, z1, tmpsrc, tmp + (n << 1), logn);
+            // memcpy(t1, tmp + (n << 1), n * sizeof *tmp);
+            Array.Copy(tmpsrc, tmp + (n << 1), tmpsrc, t1, n);
+            this.ffte.poly_mul_fft(tmpsrc, tmp, tmpsrc, z1, logn);
+            this.ffte.poly_add(tmpsrc, t0, tmpsrc, tmp, logn);
+
+            /*
+            * Second recursive invocation, on the split tb0 (currently in t0)
+            * and the left sub-tree.
+            */
+            z0 = tmp;
+            this.ffte.poly_split_fft(tmpsrc, z0, tmpsrc, z0 + hn, tmpsrc, t0, logn);
+            ffSampling_fft_dyntree(samp, tmpsrc, z0, tmpsrc, z0 + hn,
+                g00src, g00, g00src, g00 + hn, g01src, g01, orig_logn, logn - 1, tmpsrc, z0 + n);
+            this.ffte.poly_merge_fft(tmpsrc, t0, tmpsrc, z0, tmpsrc, z0 + hn, logn);
+        }
+
+        /*
+        * Perform Fast Fourier Sampling for target vector t and LDL tree T.
+        * tmp[] must have size for at least two polynomials of size 2^logn.
+        */
+        internal void ffSampling_fft(SamplerZ samp,
+            FalconFPR[] z0src, int z0, FalconFPR[] z1src, int z1,
+            FalconFPR[] treesrc, int tree,
+            FalconFPR[] t0src, int t0, FalconFPR[] t1src, int t1, uint logn,
+            FalconFPR[] tmpsrc, int tmp)
+        {
+            int n, hn;
+            int tree0, tree1;
+
+            /*
+            * When logn == 2, we inline the last two recursion levels.
+            */
+            if (logn == 2) {
+                FalconFPR x0, x1, y0, y1, w0, w1, w2, w3, sigma;
+                FalconFPR a_re, a_im, b_re, b_im, c_re, c_im;
+
+                tree0 = tree + 4;
+                tree1 = tree + 8;
+
+                /*
+                * We split t1 into w*, then do the recursive invocation,
+                * with output in w*. We finally merge back into z1.
+                */
+                a_re = t1src[t1+0];
+                a_im = t1src[t1 + 2];
+                b_re = t1src[t1 + 1];
+                b_im = t1src[t1 + 3];
+                c_re = this.fpre.fpr_add(a_re, b_re);
+                c_im = this.fpre.fpr_add(a_im, b_im);
+                w0 = this.fpre.fpr_half(c_re);
+                w1 = this.fpre.fpr_half(c_im);
+                c_re = this.fpre.fpr_sub(a_re, b_re);
+                c_im = this.fpre.fpr_sub(a_im, b_im);
+                w2 = this.fpre.fpr_mul(this.fpre.fpr_add(c_re, c_im), this.fpre.fpr_invsqrt8);
+                w3 = this.fpre.fpr_mul(this.fpre.fpr_sub(c_im, c_re), this.fpre.fpr_invsqrt8);
+
+                x0 = w2;
+                x1 = w3;
+                sigma = treesrc[tree1 + 3];
+                w2 = this.fpre.fpr_of(samp.Sample(x0, sigma));
+                w3 = this.fpre.fpr_of(samp.Sample(x1, sigma));
+                a_re = this.fpre.fpr_sub(x0, w2);
+                a_im = this.fpre.fpr_sub(x1, w3);
+                b_re = treesrc[tree1 + 0];
+                b_im = treesrc[tree1 + 1];
+                c_re = this.fpre.fpr_sub(this.fpre.fpr_mul(a_re, b_re), this.fpre.fpr_mul(a_im, b_im));
+                c_im = this.fpre.fpr_add(this.fpre.fpr_mul(a_re, b_im), this.fpre.fpr_mul(a_im, b_re));
+                x0 = this.fpre.fpr_add(c_re, w0);
+                x1 = this.fpre.fpr_add(c_im, w1);
+                sigma = treesrc[tree1 + 2];
+                w0 = this.fpre.fpr_of(samp.Sample(x0, sigma));
+                w1 = this.fpre.fpr_of(samp.Sample(x1, sigma));
+
+                a_re = w0;
+                a_im = w1;
+                b_re = w2;
+                b_im = w3;
+                c_re = this.fpre.fpr_mul(this.fpre.fpr_sub(b_re, b_im), this.fpre.fpr_invsqrt2);
+                c_im = this.fpre.fpr_mul(this.fpre.fpr_add(b_re, b_im), this.fpre.fpr_invsqrt2);
+                z1src[z1 + 0] = w0 = this.fpre.fpr_add(a_re, c_re);
+                z1src[z1 + 2] = w2 = this.fpre.fpr_add(a_im, c_im);
+                z1src[z1 + 1] = w1 = this.fpre.fpr_sub(a_re, c_re);
+                z1src[z1 + 3] = w3 = this.fpre.fpr_sub(a_im, c_im);
+
+                /*
+                * Compute tb0 = t0 + (t1 - z1) * L. Value tb0 ends up in w*.
+                */
+                w0 = this.fpre.fpr_sub(t1src[t1+0], w0);
+                w1 = this.fpre.fpr_sub(t1src[t1 + 1], w1);
+                w2 = this.fpre.fpr_sub(t1src[t1 + 2], w2);
+                w3 = this.fpre.fpr_sub(t1src[t1 + 3], w3);
+
+                a_re = w0;
+                a_im = w2;
+                b_re = treesrc[tree+0];
+                b_im = treesrc[tree + 2];
+                w0 = this.fpre.fpr_sub(this.fpre.fpr_mul(a_re, b_re), this.fpre.fpr_mul(a_im, b_im));
+                w2 = this.fpre.fpr_add(this.fpre.fpr_mul(a_re, b_im), this.fpre.fpr_mul(a_im, b_re));
+                a_re = w1;
+                a_im = w3;
+                b_re = treesrc[tree + 1];
+                b_im = treesrc[tree + 3];
+                w1 = this.fpre.fpr_sub(this.fpre.fpr_mul(a_re, b_re), this.fpre.fpr_mul(a_im, b_im));
+                w3 = this.fpre.fpr_add(this.fpre.fpr_mul(a_re, b_im), this.fpre.fpr_mul(a_im, b_re));
+
+                w0 = this.fpre.fpr_add(w0, t0src[t0+0]);
+                w1 = this.fpre.fpr_add(w1, t0src[t0 + 1]);
+                w2 = this.fpre.fpr_add(w2, t0src[t0 + 2]);
+                w3 = this.fpre.fpr_add(w3, t0src[t0 + 3]);
+
+                /*
+                * Second recursive invocation.
+                */
+                a_re = w0;
+                a_im = w2;
+                b_re = w1;
+                b_im = w3;
+                c_re = this.fpre.fpr_add(a_re, b_re);
+                c_im = this.fpre.fpr_add(a_im, b_im);
+                w0 = this.fpre.fpr_half(c_re);
+                w1 = this.fpre.fpr_half(c_im);
+                c_re = this.fpre.fpr_sub(a_re, b_re);
+                c_im = this.fpre.fpr_sub(a_im, b_im);
+                w2 = this.fpre.fpr_mul(this.fpre.fpr_add(c_re, c_im), this.fpre.fpr_invsqrt8);
+                w3 = this.fpre.fpr_mul(this.fpre.fpr_sub(c_im, c_re), this.fpre.fpr_invsqrt8);
+
+                x0 = w2;
+                x1 = w3;
+                sigma = treesrc[tree0 + 3];
+                w2 = y0 = this.fpre.fpr_of(samp.Sample(x0, sigma));
+                w3 = y1 = this.fpre.fpr_of(samp.Sample(x1, sigma));
+                a_re = this.fpre.fpr_sub(x0, y0);
+                a_im = this.fpre.fpr_sub(x1, y1);
+                b_re = treesrc[tree0 + 0];
+                b_im = treesrc[tree0 + 1];
+                c_re = this.fpre.fpr_sub(this.fpre.fpr_mul(a_re, b_re), this.fpre.fpr_mul(a_im, b_im));
+                c_im = this.fpre.fpr_add(this.fpre.fpr_mul(a_re, b_im), this.fpre.fpr_mul(a_im, b_re));
+                x0 = this.fpre.fpr_add(c_re, w0);
+                x1 = this.fpre.fpr_add(c_im, w1);
+                sigma = treesrc[tree0 + 2];
+                w0 = this.fpre.fpr_of(samp.Sample(x0, sigma));
+                w1 = this.fpre.fpr_of(samp.Sample(x1, sigma));
+
+                a_re = w0;
+                a_im = w1;
+                b_re = w2;
+                b_im = w3;
+                c_re = this.fpre.fpr_mul(this.fpre.fpr_sub(b_re, b_im), this.fpre.fpr_invsqrt2);
+                c_im = this.fpre.fpr_mul(this.fpre.fpr_add(b_re, b_im), this.fpre.fpr_invsqrt2);
+                z0src[z0 + 0] = this.fpre.fpr_add(a_re, c_re);
+                z0src[z0 + 2] = this.fpre.fpr_add(a_im, c_im);
+                z0src[z0 + 1] = this.fpre.fpr_sub(a_re, c_re);
+                z0src[z0 + 3] = this.fpre.fpr_sub(a_im, c_im);
+
+                return;
+            }
+
+            /*
+            * Case logn == 1 is reachable only when using Falcon-2 (the
+            * smallest size for which Falcon is mathematically defined, but
+            * of course way too insecure to be of any use).
+            */
+            if (logn == 1) {
+                FalconFPR x0, x1, y0, y1, sigma;
+                FalconFPR a_re, a_im, b_re, b_im, c_re, c_im;
+
+                x0 = t1src[t1+0];
+                x1 = t1src[t1 + 1];
+                sigma = treesrc[tree + 3];
+                z1src[z1 + 0] = y0 = this.fpre.fpr_of(samp.Sample(x0, sigma));
+                z1src[z1 + 1] = y1 = this.fpre.fpr_of(samp.Sample(x1, sigma));
+                a_re = this.fpre.fpr_sub(x0, y0);
+                a_im = this.fpre.fpr_sub(x1, y1);
+                b_re = treesrc[tree+0];
+                b_im = treesrc[tree + 1];
+                c_re = this.fpre.fpr_sub(this.fpre.fpr_mul(a_re, b_re), this.fpre.fpr_mul(a_im, b_im));
+                c_im = this.fpre.fpr_add(this.fpre.fpr_mul(a_re, b_im), this.fpre.fpr_mul(a_im, b_re));
+                x0 = this.fpre.fpr_add(c_re, t0src[t0+0]);
+                x1 = this.fpre.fpr_add(c_im, t0src[t0 + 1]);
+                sigma = treesrc[tree + 2];
+                z0src[z0 + 0] = this.fpre.fpr_of(samp.Sample(x0, sigma));
+                z0src[z0 + 1] = this.fpre.fpr_of(samp.Sample(x1, sigma));
+
+                return;
+            }
+
+            /*
+            * Normal end of recursion is for logn == 0. Since the last
+            * steps of the recursions were inlined in the blocks above
+            * (when logn == 1 or 2), this case is not reachable, and is
+            * retained here only for documentation purposes.
+
+            if (logn == 0) {
+                fpr x0, x1, sigma;
+
+                x0 = t0src[t0+0];
+                x1 = t1src[t1+0];
+                sigma = treesrc[tree+0];
+                z0[0] = this.fpre.fpr_of(samp.sample(x0, sigma));
+                z1src[z1 + 0] = this.fpre.fpr_of(samp.sample(x1, sigma));
+                return;
+            }
+
+            */
+
+            /*
+            * General recursive case (logn >= 3).
+            */
+
+            n = (int)1 << (int)logn;
+            hn = n >> 1;
+            tree0 = tree + n;
+            tree1 = tree + n + (int)ffLDL_treesize(logn - 1);
+
+            /*
+            * We split t1 into z1 (reused as temporary storage), then do
+            * the recursive invocation, with output in tmp. We finally
+            * merge back into z1.
+            */
+            this.ffte.poly_split_fft(z1src, z1, z1src, z1 + hn, t1src, t1, logn);
+            ffSampling_fft(samp, tmpsrc, tmp, tmpsrc, tmp + hn,
+                treesrc, tree1, z1src, z1, z1src, z1 + hn, logn - 1, tmpsrc, tmp + n);
+            this.ffte.poly_merge_fft(z1src, z1, tmpsrc, tmp, tmpsrc, tmp + hn, logn);
+
+            /*
+            * Compute tb0 = t0 + (t1 - z1) * L. Value tb0 ends up in tmp[].
+            */
+            // memcpy(tmp, t1, n * sizeof *t1);
+            Array.Copy(t1src, t1, tmpsrc, tmp, n);
+            this.ffte.poly_sub(tmpsrc, tmp, z1src, z1, logn);
+            this.ffte.poly_mul_fft(tmpsrc, tmp, treesrc, tree, logn);
+            this.ffte.poly_add(tmpsrc, tmp, t0src, t0, logn);
+
+            /*
+            * Second recursive invocation.
+            */
+            this.ffte.poly_split_fft(z0src, z0, z0src, z0 + hn, tmpsrc, tmp, logn);
+            ffSampling_fft(samp, tmpsrc, tmp, tmpsrc, tmp + hn,
+                treesrc, tree0, z0src, z0, z0src, z0 + hn, logn - 1, tmpsrc, tmp + n);
+            this.ffte.poly_merge_fft(z0src, z0, tmpsrc, tmp, tmpsrc, tmp + hn, logn);
+        }
+
+        /*
+        * Compute a signature: the signature contains two vectors, s1 and s2.
+        * The s1 vector is not returned. The squared norm of (s1,s2) is
+        * computed, and if it is short enough, then s2 is returned into the
+        * s2[] buffer, and 1 is returned; otherwise, s2[] is untouched and 0 is
+        * returned; the caller should then try again. This function uses an
+        * expanded key.
+        *
+        * tmp[] must have room for at least six polynomials.
+        */
+        internal int do_sign_tree(SamplerZ samp, short[] s2src, int s2,
+            FalconFPR[] ex_keysrc, int expanded_key,
+            ushort[] hmsrc, int hm,
+            uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+            int n, u;
+            int t0, t1, tx, ty;
+            int b00, b01, b10, b11, tree;
+            FalconFPR ni;
+            uint sqn, ng;
+            short[] s1tmp, s2tmp;
+
+            n = (int)1 << (int)logn;
+            t0 = tmp;
+            t1 = t0 + n;
+            b00 = expanded_key + skoff_b00(logn);
+            b01 = expanded_key + skoff_b01(logn);
+            b10 = expanded_key + skoff_b10(logn);
+            b11 = expanded_key + skoff_b11(logn);
+            tree = expanded_key + skoff_tree(logn);
+
+            /*
+            * Set the target vector to [hm, 0] (hm is the hashed message).
+            */
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t0+u] = this.fpre.fpr_of(hmsrc[hm + u]);
+                /* This is implicit.
+                t1src[t1 + u] = fpr_zero;
+                */
+            }
+
+            /*
+            * Apply the lattice basis to obtain the real target
+            * vector (after normalization with regards to modulus).
+            */
+            this.ffte.FFT(tmpsrc, t0, logn);
+            ni = this.fpre.fpr_inverse_of_q;
+            // memcpy(t1, t0, n * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, t1, n);
+            this.ffte.poly_mul_fft(tmpsrc, t1, ex_keysrc, b01, logn);
+            this.ffte.poly_mulconst(tmpsrc, t1, this.fpre.fpr_neg(ni), logn);
+            this.ffte.poly_mul_fft(tmpsrc, t0, ex_keysrc, b11, logn);
+            this.ffte.poly_mulconst(tmpsrc, t0, ni, logn);
+
+            tx = t1 + n;
+            ty = tx + n;
+
+            /*
+            * Apply sampling. Output is written back in [tx, ty].
+            */
+            ffSampling_fft(samp, tmpsrc, tx, tmpsrc, ty, ex_keysrc, tree, tmpsrc, t0, tmpsrc, t1, logn, tmpsrc, ty + n);
+
+            /*
+            * Get the lattice point corresponding to that tiny vector.
+            */
+            // memcpy(t0, tx, n * sizeof *tx);
+            Array.Copy(tmpsrc, tx, tmpsrc, t0, n);
+            // memcpy(t1, ty, n * sizeof *ty);
+            Array.Copy(tmpsrc, ty, tmpsrc, t1, n);
+            this.ffte.poly_mul_fft(tmpsrc, tx, ex_keysrc, b00, logn);
+            this.ffte.poly_mul_fft(tmpsrc, ty, ex_keysrc, b10, logn);
+            this.ffte.poly_add(tmpsrc, tx, tmpsrc, ty, logn);
+            // memcpy(ty, t0, n * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, ty, n);
+            this.ffte.poly_mul_fft(tmpsrc, ty, ex_keysrc, b01, logn);
+
+            // memcpy(t0, tx, n * sizeof *tx);
+            Array.Copy(tmpsrc, tx, tmpsrc, t0, n);
+            this.ffte.poly_mul_fft(tmpsrc, t1, ex_keysrc, b11, logn);
+            this.ffte.poly_add(tmpsrc, t1, tmpsrc, ty, logn);
+
+            this.ffte.iFFT(tmpsrc, t0, logn);
+            this.ffte.iFFT(tmpsrc, t1, logn);
+
+            /*
+            * Compute the signature.
+            */
+            s1tmp = new short[n];
+            s2tmp = new short[n];
+            sqn = 0;
+            ng = 0;
+            for (u = 0; u < n; u ++) {
+                int z;
+
+                z = (int)hmsrc[hm + u] - (int)this.fpre.fpr_rint(tmpsrc[t0+u]);
+                sqn += (uint)(z * z);
+                ng |= sqn;
+                s1tmp[u] = (short)z;
+            }
+            sqn |= (uint)(-(ng >> 31));
+
+            /*
+            * With "normal" degrees (e.g. 512 or 1024), it is very
+            * improbable that the computed vector is not short enough;
+            * however, it may happen in practice for the very reduced
+            * versions (e.g. degree 16 or below). In that case, the caller
+            * will loop, and we must not write anything into s2[] because
+            * s2[] may overlap with the hashed message hm[] and we need
+            * hm[] for the next iteration.
+            */
+            for (u = 0; u < n; u ++) {
+                s2tmp[u] = (short)-this.fpre.fpr_rint(tmpsrc[t1 + u]);
+            }
+            if (this.common.is_short_half(sqn, s2tmp, 0, logn)) {
+                // memcpy(s2, s2tmp, n * sizeof *s2);
+                Array.Copy(s2tmp, 0, s2src, s2, n);
+                // memcpy(tmp, s1tmp, n * sizeof *s1tmp);
+                Array.Copy(s1tmp, 0, tmpsrc, tmp, n);
+                return 1;
+            }
+            return 0;
+        }
+
+        /*
+        * Compute a signature: the signature contains two vectors, s1 and s2.
+        * The s1 vector is not returned. The squared norm of (s1,s2) is
+        * computed, and if it is short enough, then s2 is returned into the
+        * s2[] buffer, and 1 is returned; otherwise, s2[] is untouched and 0 is
+        * returned; the caller should then try again.
+        *
+        * tmp[] must have room for at least nine polynomials.
+        */
+        internal int do_sign_dyn(SamplerZ samp, short[] s2src, int s2,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g,
+            sbyte[] Fsrc, int F, sbyte[] Gsrc, int G,
+            ushort[] hmsrc, int hm, uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+            int n, u;
+            int t0, t1, tx, ty;
+            int b00, b01, b10, b11;
+            int g00, g01, g11;
+            FalconFPR ni;
+            uint sqn, ng;
+            short[] s1tmp, s2tmp;
+
+            n = (int)1 << (int)logn;
+
+            /*
+            * Lattice basis is B = [[g, -f], [G, -F]]. We convert it to FFT.
+            */
+            b00 = tmp;
+            b01 = b00 + n;
+            b10 = b01 + n;
+            b11 = b10 + n;
+            smallints_to_fpr(tmpsrc, b01, fsrc, f, logn);
+            smallints_to_fpr(tmpsrc, b00, gsrc, g, logn);
+            smallints_to_fpr(tmpsrc, b11, Fsrc, F, logn);
+            smallints_to_fpr(tmpsrc, b10, Gsrc, G, logn);
+            this.ffte.FFT(tmpsrc, b01, logn);
+            this.ffte.FFT(tmpsrc, b00, logn);
+            this.ffte.FFT(tmpsrc, b11, logn);
+            this.ffte.FFT(tmpsrc, b10, logn);
+            this.ffte.poly_neg(tmpsrc, b01, logn);
+            this.ffte.poly_neg(tmpsrc, b11, logn);
+
+            /*
+            * Compute the Gram matrix G = B·B*. Formulas are:
+            *   g00 = b00*adj(b00) + b01*adj(b01)
+            *   g01 = b00*adj(b10) + b01*adj(b11)
+            *   g10 = b10*adj(b00) + b11*adj(b01)
+            *   g11 = b10*adj(b10) + b11*adj(b11)
+            *
+            * For historical reasons, this implementation uses
+            * g00, g01 and g11 (upper triangle). g10 is not kept
+            * since it is equal to adj(g01).
+            *
+            * We _replace_ the matrix B with the Gram matrix, but we
+            * must keep b01 and b11 for computing the target vector.
+            */
+            t0 = b11 + n;
+            t1 = t0 + n;
+
+            // memcpy(t0, b01, n * sizeof *b01);
+            Array.Copy(tmpsrc, b01, tmpsrc, t0, n);
+            this.ffte.poly_mulselfadj_fft(tmpsrc, t0, logn);    // t0 <- b01*adj(b01)
+
+            // memcpy(t1, b00, n * sizeof *b00);
+            Array.Copy(tmpsrc, b00, tmpsrc, t1, n);
+            this.ffte.poly_muladj_fft(tmpsrc, t1, tmpsrc, b10, logn);   // t1 <- b00*adj(b10)
+            this.ffte.poly_mulselfadj_fft(tmpsrc, b00, logn);   // b00 <- b00*adj(b00)
+            this.ffte.poly_add(tmpsrc, b00, tmpsrc, t0, logn);      // b00 <- g00
+            // memcpy(t0, b01, n * sizeof *b01);
+            Array.Copy(tmpsrc, b01, tmpsrc, t0, n);
+            this.ffte.poly_muladj_fft(tmpsrc, b01, tmpsrc, b11, logn);  // b01 <- b01*adj(b11)
+            this.ffte.poly_add(tmpsrc, b01, tmpsrc, t1, logn);      // b01 <- g01
+
+            this.ffte.poly_mulselfadj_fft(tmpsrc, b10, logn);   // b10 <- b10*adj(b10)
+            // memcpy(t1, b11, n * sizeof *b11);
+            Array.Copy(tmpsrc, b11, tmpsrc, t1, n);
+            this.ffte.poly_mulselfadj_fft(tmpsrc, t1, logn);    // t1 <- b11*adj(b11)
+            this.ffte.poly_add(tmpsrc, b10, tmpsrc, t1, logn);      // b10 <- g11
+
+            /*
+            * We rename variables to make things clearer. The three elements
+            * of the Gram matrix uses the first 3*n slots of tmp[], followed
+            * by b11 and b01 (in that order).
+            */
+            g00 = b00;
+            g01 = b01;
+            g11 = b10;
+            b01 = t0;
+            t0 = b01 + n;
+            t1 = t0 + n;
+
+            /*
+            * Memory layout at that point:
+            *   g00 g01 g11 b11 b01 t0 t1
+            */
+
+            /*
+            * Set the target vector to [hm, 0] (hm is the hashed message).
+            */
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t0+u] = this.fpre.fpr_of((short)hmsrc[hm + u]);
+                /* This is implicit.
+                t1src[t1 + u] = fpr_zero;
+                */
+            }
+
+            /*
+            * Apply the lattice basis to obtain the real target
+            * vector (after normalization with regards to modulus).
+            */
+            this.ffte.FFT(tmpsrc, t0, logn);
+            ni = this.fpre.fpr_inverse_of_q;
+            // memcpy(t1, t0, n * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, t1, n);
+            this.ffte.poly_mul_fft(tmpsrc, t1, tmpsrc, b01, logn);
+            this.ffte.poly_mulconst(tmpsrc, t1, this.fpre.fpr_neg(ni), logn);
+            this.ffte.poly_mul_fft(tmpsrc, t0, tmpsrc, b11, logn);
+            this.ffte.poly_mulconst(tmpsrc, t0, ni, logn);
+
+            /*
+            * b01 and b11 can be discarded, so we move back (t0,t1).
+            * Memory layout is now:
+            *      g00 g01 g11 t0 t1
+            */
+            // memcpy(b11, t0, n * 2 * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, b11, n * 2);
+            t0 = g11 + n;
+            t1 = t0 + n;
+
+            /*
+            * Apply sampling; result is written over (t0,t1).
+            */
+            ffSampling_fft_dyntree(samp,
+                tmpsrc, t0, tmpsrc, t1, tmpsrc, g00, tmpsrc, g01, tmpsrc, g11, logn, logn, tmpsrc, t1 + n);
+
+            /*
+            * We arrange the layout back to:
+            *     b00 b01 b10 b11 t0 t1
+            *
+            * We did not conserve the matrix basis, so we must recompute
+            * it now.
+            */
+            b00 = tmp;
+            b01 = b00 + n;
+            b10 = b01 + n;
+            b11 = b10 + n;
+            // memmove(b11 + n, t0, n * 2 * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, b11 + n, n * 2);
+            t0 = b11 + n;
+            t1 = t0 + n;
+            smallints_to_fpr(tmpsrc, b01, fsrc, f, logn);
+            smallints_to_fpr(tmpsrc, b00, gsrc, g, logn);
+            smallints_to_fpr(tmpsrc, b11, Fsrc, F, logn);
+            smallints_to_fpr(tmpsrc, b10, Gsrc, G, logn);
+            this.ffte.FFT(tmpsrc, b01, logn);
+            this.ffte.FFT(tmpsrc, b00, logn);
+            this.ffte.FFT(tmpsrc, b11, logn);
+            this.ffte.FFT(tmpsrc, b10, logn);
+            this.ffte.poly_neg(tmpsrc, b01, logn);
+            this.ffte.poly_neg(tmpsrc, b11, logn);
+            tx = t1 + n;
+            ty = tx + n;
+
+            /*
+            * Get the lattice point corresponding to that tiny vector.
+            */
+            // memcpy(tx, t0, n * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, tx, n);
+            // memcpy(ty, t1, n * sizeof *t1);
+            Array.Copy(tmpsrc, t1, tmpsrc, ty, n);
+            this.ffte.poly_mul_fft(tmpsrc, tx, tmpsrc, b00, logn);
+            this.ffte.poly_mul_fft(tmpsrc, ty, tmpsrc, b10, logn);
+            this.ffte.poly_add(tmpsrc, tx, tmpsrc, ty, logn);
+            // memcpy(ty, t0, n * sizeof *t0);
+            Array.Copy(tmpsrc, t0, tmpsrc, ty, n);
+            this.ffte.poly_mul_fft(tmpsrc, ty, tmpsrc, b01, logn);
+
+            // memcpy(t0, tx, n * sizeof *tx);
+            Array.Copy(tmpsrc, tx, tmpsrc, t0, n);
+            this.ffte.poly_mul_fft(tmpsrc, t1, tmpsrc, b11, logn);
+            this.ffte.poly_add(tmpsrc, t1, tmpsrc, ty, logn);
+            this.ffte.iFFT(tmpsrc, t0, logn);
+            this.ffte.iFFT(tmpsrc, t1, logn);
+
+            s1tmp = new short[n];
+            sqn = 0;
+            ng = 0;
+            for (u = 0; u < n; u ++) {
+                int z;
+
+                z = (int)hmsrc[hm + u] - (int)this.fpre.fpr_rint(tmpsrc[t0+u]);
+                sqn += (uint)(z * z);
+                ng |= sqn;
+                s1tmp[u] = (short)z;
+            }
+            sqn |= (uint)(-(ng >> 31));
+
+            /*
+            * With "normal" degrees (e.g. 512 or 1024), it is very
+            * improbable that the computed vector is not short enough;
+            * however, it may happen in practice for the very reduced
+            * versions (e.g. degree 16 or below). In that case, the caller
+            * will loop, and we must not write anything into s2[] because
+            * s2[] may overlap with the hashed message hm[] and we need
+            * hm[] for the next iteration.
+            */
+            s2tmp = new short[n];
+            for (u = 0; u < n; u ++) {
+                s2tmp[u] = (short)-this.fpre.fpr_rint(tmpsrc[t1 + u]);
+            }
+            if (this.common.is_short_half(sqn, s2tmp, 0, logn)) {
+                // memcpy(s2, s2tmp, n * sizeof *s2);
+                Array.Copy(s2tmp, 0, s2src, s2, n);
+                // memcpy(tmp, s1tmp, n * sizeof *s1tmp);
+                //Array.Copy(s1tmp, 0, tmpsrc, tmp, n);
+                return 1;
+            }
+            return 0;
+        }
+
+        internal void sign_tree(short[] sigsrc, int sig, SHAKE256 rng,
+            FalconFPR[] ex_keysrc, int expanded_key,
+            ushort[] hmsrc, int hm, uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+
+            int ftmp = tmp;
+            for (;;) {
+                /*
+                * Signature produces short vectors s1 and s2. The
+                * signature is acceptable only if the aggregate vector
+                * s1,s2 is short; we must use the same bound as the
+                * verifier.
+                *
+                * If the signature is acceptable, then we return only s2
+                * (the verifier recomputes s1 from s2, the hashed message,
+                * and the public key).
+                */
+                
+                /*
+                * Normal sampling. We use a fast PRNG seeded from our
+                * SHAKE context ('rng').
+                */
+                FalconRNG prng = new FalconRNG();
+                prng.prng_init(rng);
+                SamplerZ samp = new SamplerZ(prng, this.fpre.fpr_sigma_min[logn], this.fpre);
+
+
+                /*
+                * Do the actual signature.
+                */
+                if (do_sign_tree(samp, sigsrc, sig,
+                    ex_keysrc, expanded_key, hmsrc, hm, logn, tmpsrc, ftmp) != 0)
+                {
+                    break;
+                }
+            }
+        }
+
+        internal void sign_dyn(short[] sigsrc, int sig, SHAKE256 rng,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g,
+            sbyte[] Fsrc, int F, sbyte[] Gsrc, int G,
+            ushort[] hmsrc, int hm, uint logn, FalconFPR[] tmpsrc, int tmp)
+        {
+            for (;;) {
+                /*
+                * Signature produces short vectors s1 and s2. The
+                * signature is acceptable only if the aggregate vector
+                * s1,s2 is short; we must use the same bound as the
+                * verifier.
+                *
+                * If the signature is acceptable, then we return only s2
+                * (the verifier recomputes s1 from s2, the hashed message,
+                * and the public key).
+                */
+
+                /*
+                * Normal sampling. We use a fast PRNG seeded from our
+                * SHAKE context ('rng').
+                */
+
+                FalconRNG prng = new FalconRNG();
+                prng.prng_init(rng);
+                SamplerZ samp = new SamplerZ(prng, this.fpre.fpr_sigma_min[logn], this.fpre);
+
+                /*
+                * Do the actual signature.
+                */
+                if (do_sign_dyn(samp, sigsrc, sig,
+                    fsrc, f, gsrc,  g, Fsrc,  F, Gsrc,  G, hmsrc, hm, logn, tmpsrc, tmp) != 0)
+                {
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconSigner.cs b/crypto/src/pqc/crypto/falcon/FalconSigner.cs
new file mode 100644
index 000000000..e77e84102
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconSigner.cs
@@ -0,0 +1,69 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    public class FalconSigner
+        : IMessageSigner
+    {
+        private byte[] encodedkey;
+        private FalconNist nist;
+
+        public void Init(bool forSigning, ICipherParameters param)
+        {
+            if (forSigning)
+            {
+                if (param is ParametersWithRandom withRandom)
+                {
+                    FalconPrivateKeyParameters skparam = (FalconPrivateKeyParameters)withRandom.Parameters;
+                    encodedkey = skparam.GetEncoded();
+                    nist = new FalconNist(
+                        withRandom.Random,
+                        (uint)skparam.Parameters.LogN,
+                        (uint)skparam.Parameters.NonceLength);
+                }
+                else
+                {
+                    FalconPrivateKeyParameters skparam = (FalconPrivateKeyParameters)param;
+                    encodedkey = ((FalconPrivateKeyParameters)param).GetEncoded();
+                    nist = new FalconNist(
+                        CryptoServicesRegistrar.GetSecureRandom(),
+                        (uint)skparam.Parameters.LogN,
+                        (uint)skparam.Parameters.NonceLength);
+                }
+            }
+            else
+            {
+                FalconPublicKeyParameters pkparam = (FalconPublicKeyParameters)param;
+                encodedkey = pkparam.GetEncoded();
+                nist = new FalconNist(
+                    CryptoServicesRegistrar.GetSecureRandom(),
+                    (uint)pkparam.Parameters.LogN,
+                    (uint)pkparam.Parameters.NonceLength);
+            }
+        }
+
+        public byte[] GenerateSignature(byte[] message)
+        {
+            byte[] sm = new byte[nist.GetCryptoBytes()];
+
+            return nist.crypto_sign(sm, message, 0, (uint)message.Length, encodedkey, 0);
+        }
+
+        public bool VerifySignature(byte[] message, byte[] signature)
+        {
+            if (signature[0] != (byte)(0x30 + nist.GetLogn()))
+            {
+                return false;
+            }
+            byte[] nonce = new byte[nist.GetNonceLength()];
+            byte[] sig = new byte[signature.Length - nist.GetNonceLength() - 1];
+            Array.Copy(signature, 1, nonce, 0, nist.GetNonceLength());
+            Array.Copy(signature, nist.GetNonceLength() + 1, sig, 0, signature.Length - nist.GetNonceLength() - 1);
+            bool res = nist.crypto_sign_open(sig,nonce,message,encodedkey,0) == 0;
+            return res;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconSmallPrime.cs b/crypto/src/pqc/crypto/falcon/FalconSmallPrime.cs
new file mode 100644
index 000000000..83a7cdfaf
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconSmallPrime.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconSmallPrime
+    {
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#, this file corresponds to the small_prime type defined
+        * in keygen.c):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+        internal uint p;
+        internal uint g;
+        internal uint s;
+
+        internal FalconSmallPrime(uint p, uint g, uint s) {
+            this.p = p;
+            this.g = g;
+            this.s = s;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconSmallPrimes.cs b/crypto/src/pqc/crypto/falcon/FalconSmallPrimes.cs
new file mode 100644
index 000000000..8e11990bc
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconSmallPrimes.cs
@@ -0,0 +1,531 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconSmallPrimes
+    {
+        internal FalconSmallPrimes() {}
+        internal FalconSmallPrime[] PRIMES = {
+            new FalconSmallPrime( 2147473409,  383167813,      10239 ),
+            new FalconSmallPrime( 2147389441,  211808905,  471403745 ),
+            new FalconSmallPrime( 2147387393,   37672282, 1329335065 ),
+            new FalconSmallPrime( 2147377153, 1977035326,  968223422 ),
+            new FalconSmallPrime( 2147358721, 1067163706,  132460015 ),
+            new FalconSmallPrime( 2147352577, 1606082042,  598693809 ),
+            new FalconSmallPrime( 2147346433, 2033915641, 1056257184 ),
+            new FalconSmallPrime( 2147338241, 1653770625,  421286710 ),
+            new FalconSmallPrime( 2147309569,  631200819, 1111201074 ),
+            new FalconSmallPrime( 2147297281, 2038364663, 1042003613 ),
+            new FalconSmallPrime( 2147295233, 1962540515,   19440033 ),
+            new FalconSmallPrime( 2147239937, 2100082663,  353296760 ),
+            new FalconSmallPrime( 2147235841, 1991153006, 1703918027 ),
+            new FalconSmallPrime( 2147217409,  516405114, 1258919613 ),
+            new FalconSmallPrime( 2147205121,  409347988, 1089726929 ),
+            new FalconSmallPrime( 2147196929,  927788991, 1946238668 ),
+            new FalconSmallPrime( 2147178497, 1136922411, 1347028164 ),
+            new FalconSmallPrime( 2147100673,  868626236,  701164723 ),
+            new FalconSmallPrime( 2147082241, 1897279176,  617820870 ),
+            new FalconSmallPrime( 2147074049, 1888819123,  158382189 ),
+            new FalconSmallPrime( 2147051521,   25006327,  522758543 ),
+            new FalconSmallPrime( 2147043329,  327546255,   37227845 ),
+            new FalconSmallPrime( 2147039233,  766324424, 1133356428 ),
+            new FalconSmallPrime( 2146988033, 1862817362,   73861329 ),
+            new FalconSmallPrime( 2146963457,  404622040,  653019435 ),
+            new FalconSmallPrime( 2146959361, 1936581214,  995143093 ),
+            new FalconSmallPrime( 2146938881, 1559770096,  634921513 ),
+            new FalconSmallPrime( 2146908161,  422623708, 1985060172 ),
+            new FalconSmallPrime( 2146885633, 1751189170,  298238186 ),
+            new FalconSmallPrime( 2146871297,  578919515,  291810829 ),
+            new FalconSmallPrime( 2146846721, 1114060353,  915902322 ),
+            new FalconSmallPrime( 2146834433, 2069565474,   47859524 ),
+            new FalconSmallPrime( 2146818049, 1552824584,  646281055 ),
+            new FalconSmallPrime( 2146775041, 1906267847, 1597832891 ),
+            new FalconSmallPrime( 2146756609, 1847414714, 1228090888 ),
+            new FalconSmallPrime( 2146744321, 1818792070, 1176377637 ),
+            new FalconSmallPrime( 2146738177, 1118066398, 1054971214 ),
+            new FalconSmallPrime( 2146736129,   52057278,  933422153 ),
+            new FalconSmallPrime( 2146713601,  592259376, 1406621510 ),
+            new FalconSmallPrime( 2146695169,  263161877, 1514178701 ),
+            new FalconSmallPrime( 2146656257,  685363115,  384505091 ),
+            new FalconSmallPrime( 2146650113,  927727032,  537575289 ),
+            new FalconSmallPrime( 2146646017,   52575506, 1799464037 ),
+            new FalconSmallPrime( 2146643969, 1276803876, 1348954416 ),
+            new FalconSmallPrime( 2146603009,  814028633, 1521547704 ),
+            new FalconSmallPrime( 2146572289, 1846678872, 1310832121 ),
+            new FalconSmallPrime( 2146547713,  919368090, 1019041349 ),
+            new FalconSmallPrime( 2146508801,  671847612,   38582496 ),
+            new FalconSmallPrime( 2146492417,  283911680,  532424562 ),
+            new FalconSmallPrime( 2146490369, 1780044827,  896447978 ),
+            new FalconSmallPrime( 2146459649,  327980850, 1327906900 ),
+            new FalconSmallPrime( 2146447361, 1310561493,  958645253 ),
+            new FalconSmallPrime( 2146441217,  412148926,  287271128 ),
+            new FalconSmallPrime( 2146437121,  293186449, 2009822534 ),
+            new FalconSmallPrime( 2146430977,  179034356, 1359155584 ),
+            new FalconSmallPrime( 2146418689, 1517345488, 1790248672 ),
+            new FalconSmallPrime( 2146406401, 1615820390, 1584833571 ),
+            new FalconSmallPrime( 2146404353,  826651445,  607120498 ),
+            new FalconSmallPrime( 2146379777,    3816988, 1897049071 ),
+            new FalconSmallPrime( 2146363393, 1221409784, 1986921567 ),
+            new FalconSmallPrime( 2146355201, 1388081168,  849968120 ),
+            new FalconSmallPrime( 2146336769, 1803473237, 1655544036 ),
+            new FalconSmallPrime( 2146312193, 1023484977,  273671831 ),
+            new FalconSmallPrime( 2146293761, 1074591448,  467406983 ),
+            new FalconSmallPrime( 2146283521,  831604668, 1523950494 ),
+            new FalconSmallPrime( 2146203649,  712865423, 1170834574 ),
+            new FalconSmallPrime( 2146154497, 1764991362, 1064856763 ),
+            new FalconSmallPrime( 2146142209,  627386213, 1406840151 ),
+            new FalconSmallPrime( 2146127873, 1638674429, 2088393537 ),
+            new FalconSmallPrime( 2146099201, 1516001018,  690673370 ),
+            new FalconSmallPrime( 2146093057, 1294931393,  315136610 ),
+            new FalconSmallPrime( 2146091009, 1942399533,  973539425 ),
+            new FalconSmallPrime( 2146078721, 1843461814, 2132275436 ),
+            new FalconSmallPrime( 2146060289, 1098740778,  360423481 ),
+            new FalconSmallPrime( 2146048001, 1617213232, 1951981294 ),
+            new FalconSmallPrime( 2146041857, 1805783169, 2075683489 ),
+            new FalconSmallPrime( 2146019329,  272027909, 1753219918 ),
+            new FalconSmallPrime( 2145986561, 1206530344, 2034028118 ),
+            new FalconSmallPrime( 2145976321, 1243769360, 1173377644 ),
+            new FalconSmallPrime( 2145964033,  887200839, 1281344586 ),
+            new FalconSmallPrime( 2145906689, 1651026455,  906178216 ),
+            new FalconSmallPrime( 2145875969, 1673238256, 1043521212 ),
+            new FalconSmallPrime( 2145871873, 1226591210, 1399796492 ),
+            new FalconSmallPrime( 2145841153, 1465353397, 1324527802 ),
+            new FalconSmallPrime( 2145832961, 1150638905,  554084759 ),
+            new FalconSmallPrime( 2145816577,  221601706,  427340863 ),
+            new FalconSmallPrime( 2145785857,  608896761,  316590738 ),
+            new FalconSmallPrime( 2145755137, 1712054942, 1684294304 ),
+            new FalconSmallPrime( 2145742849, 1302302867,  724873116 ),
+            new FalconSmallPrime( 2145728513,  516717693,  431671476 ),
+            new FalconSmallPrime( 2145699841,  524575579, 1619722537 ),
+            new FalconSmallPrime( 2145691649, 1925625239,  982974435 ),
+            new FalconSmallPrime( 2145687553,  463795662, 1293154300 ),
+            new FalconSmallPrime( 2145673217,  771716636,  881778029 ),
+            new FalconSmallPrime( 2145630209, 1509556977,  837364988 ),
+            new FalconSmallPrime( 2145595393,  229091856,  851648427 ),
+            new FalconSmallPrime( 2145587201, 1796903241,  635342424 ),
+            new FalconSmallPrime( 2145525761,  715310882, 1677228081 ),
+            new FalconSmallPrime( 2145495041, 1040930522,  200685896 ),
+            new FalconSmallPrime( 2145466369,  949804237, 1809146322 ),
+            new FalconSmallPrime( 2145445889, 1673903706,   95316881 ),
+            new FalconSmallPrime( 2145390593,  806941852, 1428671135 ),
+            new FalconSmallPrime( 2145372161, 1402525292,  159350694 ),
+            new FalconSmallPrime( 2145361921, 2124760298, 1589134749 ),
+            new FalconSmallPrime( 2145359873, 1217503067, 1561543010 ),
+            new FalconSmallPrime( 2145355777,  338341402,   83865711 ),
+            new FalconSmallPrime( 2145343489, 1381532164,  641430002 ),
+            new FalconSmallPrime( 2145325057, 1883895478, 1528469895 ),
+            new FalconSmallPrime( 2145318913, 1335370424,   65809740 ),
+            new FalconSmallPrime( 2145312769, 2000008042, 1919775760 ),
+            new FalconSmallPrime( 2145300481,  961450962, 1229540578 ),
+            new FalconSmallPrime( 2145282049,  910466767, 1964062701 ),
+            new FalconSmallPrime( 2145232897,  816527501,  450152063 ),
+            new FalconSmallPrime( 2145218561, 1435128058, 1794509700 ),
+            new FalconSmallPrime( 2145187841,   33505311, 1272467582 ),
+            new FalconSmallPrime( 2145181697,  269767433, 1380363849 ),
+            new FalconSmallPrime( 2145175553,   56386299, 1316870546 ),
+            new FalconSmallPrime( 2145079297, 2106880293, 1391797340 ),
+            new FalconSmallPrime( 2145021953, 1347906152,  720510798 ),
+            new FalconSmallPrime( 2145015809,  206769262, 1651459955 ),
+            new FalconSmallPrime( 2145003521, 1885513236, 1393381284 ),
+            new FalconSmallPrime( 2144960513, 1810381315,   31937275 ),
+            new FalconSmallPrime( 2144944129, 1306487838, 2019419520 ),
+            new FalconSmallPrime( 2144935937,   37304730, 1841489054 ),
+            new FalconSmallPrime( 2144894977, 1601434616,  157985831 ),
+            new FalconSmallPrime( 2144888833,   98749330, 2128592228 ),
+            new FalconSmallPrime( 2144880641, 1772327002, 2076128344 ),
+            new FalconSmallPrime( 2144864257, 1404514762, 2029969964 ),
+            new FalconSmallPrime( 2144827393,  801236594,  406627220 ),
+            new FalconSmallPrime( 2144806913,  349217443, 1501080290 ),
+            new FalconSmallPrime( 2144796673, 1542656776, 2084736519 ),
+            new FalconSmallPrime( 2144778241, 1210734884, 1746416203 ),
+            new FalconSmallPrime( 2144759809, 1146598851,  716464489 ),
+            new FalconSmallPrime( 2144757761,  286328400, 1823728177 ),
+            new FalconSmallPrime( 2144729089, 1347555695, 1836644881 ),
+            new FalconSmallPrime( 2144727041, 1795703790,  520296412 ),
+            new FalconSmallPrime( 2144696321, 1302475157,  852964281 ),
+            new FalconSmallPrime( 2144667649, 1075877614,  504992927 ),
+            new FalconSmallPrime( 2144573441,  198765808, 1617144982 ),
+            new FalconSmallPrime( 2144555009,  321528767,  155821259 ),
+            new FalconSmallPrime( 2144550913,  814139516, 1819937644 ),
+            new FalconSmallPrime( 2144536577,  571143206,  962942255 ),
+            new FalconSmallPrime( 2144524289, 1746733766,    2471321 ),
+            new FalconSmallPrime( 2144512001, 1821415077,  124190939 ),
+            new FalconSmallPrime( 2144468993,  917871546, 1260072806 ),
+            new FalconSmallPrime( 2144458753,  378417981, 1569240563 ),
+            new FalconSmallPrime( 2144421889,  175229668, 1825620763 ),
+            new FalconSmallPrime( 2144409601, 1699216963,  351648117 ),
+            new FalconSmallPrime( 2144370689, 1071885991,  958186029 ),
+            new FalconSmallPrime( 2144348161, 1763151227,  540353574 ),
+            new FalconSmallPrime( 2144335873, 1060214804,  919598847 ),
+            new FalconSmallPrime( 2144329729,  663515846, 1448552668 ),
+            new FalconSmallPrime( 2144327681, 1057776305,  590222840 ),
+            new FalconSmallPrime( 2144309249, 1705149168, 1459294624 ),
+            new FalconSmallPrime( 2144296961,  325823721, 1649016934 ),
+            new FalconSmallPrime( 2144290817,  738775789,  447427206 ),
+            new FalconSmallPrime( 2144243713,  962347618,  893050215 ),
+            new FalconSmallPrime( 2144237569, 1655257077,  900860862 ),
+            new FalconSmallPrime( 2144161793,  242206694, 1567868672 ),
+            new FalconSmallPrime( 2144155649,  769415308, 1247993134 ),
+            new FalconSmallPrime( 2144137217,  320492023,  515841070 ),
+            new FalconSmallPrime( 2144120833, 1639388522,  770877302 ),
+            new FalconSmallPrime( 2144071681, 1761785233,  964296120 ),
+            new FalconSmallPrime( 2144065537,  419817825,  204564472 ),
+            new FalconSmallPrime( 2144028673,  666050597, 2091019760 ),
+            new FalconSmallPrime( 2144010241, 1413657615, 1518702610 ),
+            new FalconSmallPrime( 2143952897, 1238327946,  475672271 ),
+            new FalconSmallPrime( 2143940609,  307063413, 1176750846 ),
+            new FalconSmallPrime( 2143918081, 2062905559,  786785803 ),
+            new FalconSmallPrime( 2143899649, 1338112849, 1562292083 ),
+            new FalconSmallPrime( 2143891457,   68149545,   87166451 ),
+            new FalconSmallPrime( 2143885313,  921750778,  394460854 ),
+            new FalconSmallPrime( 2143854593,  719766593,  133877196 ),
+            new FalconSmallPrime( 2143836161, 1149399850, 1861591875 ),
+            new FalconSmallPrime( 2143762433, 1848739366, 1335934145 ),
+            new FalconSmallPrime( 2143756289, 1326674710,  102999236 ),
+            new FalconSmallPrime( 2143713281,  808061791, 1156900308 ),
+            new FalconSmallPrime( 2143690753,  388399459, 1926468019 ),
+            new FalconSmallPrime( 2143670273, 1427891374, 1756689401 ),
+            new FalconSmallPrime( 2143666177, 1912173949,  986629565 ),
+            new FalconSmallPrime( 2143645697, 2041160111,  371842865 ),
+            new FalconSmallPrime( 2143641601, 1279906897, 2023974350 ),
+            new FalconSmallPrime( 2143635457,  720473174, 1389027526 ),
+            new FalconSmallPrime( 2143621121, 1298309455, 1732632006 ),
+            new FalconSmallPrime( 2143598593, 1548762216, 1825417506 ),
+            new FalconSmallPrime( 2143567873,  620475784, 1073787233 ),
+            new FalconSmallPrime( 2143561729, 1932954575,  949167309 ),
+            new FalconSmallPrime( 2143553537,  354315656, 1652037534 ),
+            new FalconSmallPrime( 2143541249,  577424288, 1097027618 ),
+            new FalconSmallPrime( 2143531009,  357862822,  478640055 ),
+            new FalconSmallPrime( 2143522817, 2017706025, 1550531668 ),
+            new FalconSmallPrime( 2143506433, 2078127419, 1824320165 ),
+            new FalconSmallPrime( 2143488001,  613475285, 1604011510 ),
+            new FalconSmallPrime( 2143469569, 1466594987,  502095196 ),
+            new FalconSmallPrime( 2143426561, 1115430331, 1044637111 ),
+            new FalconSmallPrime( 2143383553,    9778045, 1902463734 ),
+            new FalconSmallPrime( 2143377409, 1557401276, 2056861771 ),
+            new FalconSmallPrime( 2143363073,  652036455, 1965915971 ),
+            new FalconSmallPrime( 2143260673, 1464581171, 1523257541 ),
+            new FalconSmallPrime( 2143246337, 1876119649,  764541916 ),
+            new FalconSmallPrime( 2143209473, 1614992673, 1920672844 ),
+            new FalconSmallPrime( 2143203329,  981052047, 2049774209 ),
+            new FalconSmallPrime( 2143160321, 1847355533,  728535665 ),
+            new FalconSmallPrime( 2143129601,  965558457,  603052992 ),
+            new FalconSmallPrime( 2143123457, 2140817191,    8348679 ),
+            new FalconSmallPrime( 2143100929, 1547263683,  694209023 ),
+            new FalconSmallPrime( 2143092737,  643459066, 1979934533 ),
+            new FalconSmallPrime( 2143082497,  188603778, 2026175670 ),
+            new FalconSmallPrime( 2143062017, 1657329695,  377451099 ),
+            new FalconSmallPrime( 2143051777,  114967950,  979255473 ),
+            new FalconSmallPrime( 2143025153, 1698431342, 1449196896 ),
+            new FalconSmallPrime( 2143006721, 1862741675, 1739650365 ),
+            new FalconSmallPrime( 2142996481,  756660457,  996160050 ),
+            new FalconSmallPrime( 2142976001,  927864010, 1166847574 ),
+            new FalconSmallPrime( 2142965761,  905070557,  661974566 ),
+            new FalconSmallPrime( 2142916609,   40932754, 1787161127 ),
+            new FalconSmallPrime( 2142892033, 1987985648,  675335382 ),
+            new FalconSmallPrime( 2142885889,  797497211, 1323096997 ),
+            new FalconSmallPrime( 2142871553, 2068025830, 1411877159 ),
+            new FalconSmallPrime( 2142861313, 1217177090, 1438410687 ),
+            new FalconSmallPrime( 2142830593,  409906375, 1767860634 ),
+            new FalconSmallPrime( 2142803969, 1197788993,  359782919 ),
+            new FalconSmallPrime( 2142785537,  643817365,  513932862 ),
+            new FalconSmallPrime( 2142779393, 1717046338,  218943121 ),
+            new FalconSmallPrime( 2142724097,   89336830,  416687049 ),
+            new FalconSmallPrime( 2142707713,    5944581, 1356813523 ),
+            new FalconSmallPrime( 2142658561,  887942135, 2074011722 ),
+            new FalconSmallPrime( 2142638081,  151851972, 1647339939 ),
+            new FalconSmallPrime( 2142564353, 1691505537, 1483107336 ),
+            new FalconSmallPrime( 2142533633, 1989920200, 1135938817 ),
+            new FalconSmallPrime( 2142529537,  959263126, 1531961857 ),
+            new FalconSmallPrime( 2142527489,  453251129, 1725566162 ),
+            new FalconSmallPrime( 2142502913, 1536028102,  182053257 ),
+            new FalconSmallPrime( 2142498817,  570138730,  701443447 ),
+            new FalconSmallPrime( 2142416897,  326965800,  411931819 ),
+            new FalconSmallPrime( 2142363649, 1675665410, 1517191733 ),
+            new FalconSmallPrime( 2142351361,  968529566, 1575712703 ),
+            new FalconSmallPrime( 2142330881, 1384953238, 1769087884 ),
+            new FalconSmallPrime( 2142314497, 1977173242, 1833745524 ),
+            new FalconSmallPrime( 2142289921,   95082313, 1714775493 ),
+            new FalconSmallPrime( 2142283777,  109377615, 1070584533 ),
+            new FalconSmallPrime( 2142277633,   16960510,  702157145 ),
+            new FalconSmallPrime( 2142263297,  553850819,  431364395 ),
+            new FalconSmallPrime( 2142208001,  241466367, 2053967982 ),
+            new FalconSmallPrime( 2142164993, 1795661326, 1031836848 ),
+            new FalconSmallPrime( 2142097409, 1212530046,  712772031 ),
+            new FalconSmallPrime( 2142087169, 1763869720,  822276067 ),
+            new FalconSmallPrime( 2142078977,  644065713, 1765268066 ),
+            new FalconSmallPrime( 2142074881,  112671944,  643204925 ),
+            new FalconSmallPrime( 2142044161, 1387785471, 1297890174 ),
+            new FalconSmallPrime( 2142025729,  783885537, 1000425730 ),
+            new FalconSmallPrime( 2142011393,  905662232, 1679401033 ),
+            new FalconSmallPrime( 2141974529,  799788433,  468119557 ),
+            new FalconSmallPrime( 2141943809, 1932544124,  449305555 ),
+            new FalconSmallPrime( 2141933569, 1527403256,  841867925 ),
+            new FalconSmallPrime( 2141931521, 1247076451,  743823916 ),
+            new FalconSmallPrime( 2141902849, 1199660531,  401687910 ),
+            new FalconSmallPrime( 2141890561,  150132350, 1720336972 ),
+            new FalconSmallPrime( 2141857793, 1287438162,  663880489 ),
+            new FalconSmallPrime( 2141833217,  618017731, 1819208266 ),
+            new FalconSmallPrime( 2141820929,  999578638, 1403090096 ),
+            new FalconSmallPrime( 2141786113,   81834325, 1523542501 ),
+            new FalconSmallPrime( 2141771777,  120001928,  463556492 ),
+            new FalconSmallPrime( 2141759489,  122455485, 2124928282 ),
+            new FalconSmallPrime( 2141749249,  141986041,  940339153 ),
+            new FalconSmallPrime( 2141685761,  889088734,  477141499 ),
+            new FalconSmallPrime( 2141673473,  324212681, 1122558298 ),
+            new FalconSmallPrime( 2141669377, 1175806187, 1373818177 ),
+            new FalconSmallPrime( 2141655041, 1113654822,  296887082 ),
+            new FalconSmallPrime( 2141587457,  991103258, 1585913875 ),
+            new FalconSmallPrime( 2141583361, 1401451409, 1802457360 ),
+            new FalconSmallPrime( 2141575169, 1571977166,  712760980 ),
+            new FalconSmallPrime( 2141546497, 1107849376, 1250270109 ),
+            new FalconSmallPrime( 2141515777,  196544219,  356001130 ),
+            new FalconSmallPrime( 2141495297, 1733571506, 1060744866 ),
+            new FalconSmallPrime( 2141483009,  321552363, 1168297026 ),
+            new FalconSmallPrime( 2141458433,  505818251,  733225819 ),
+            new FalconSmallPrime( 2141360129, 1026840098,  948342276 ),
+            new FalconSmallPrime( 2141325313,  945133744, 2129965998 ),
+            new FalconSmallPrime( 2141317121, 1871100260, 1843844634 ),
+            new FalconSmallPrime( 2141286401, 1790639498, 1750465696 ),
+            new FalconSmallPrime( 2141267969, 1376858592,  186160720 ),
+            new FalconSmallPrime( 2141255681, 2129698296, 1876677959 ),
+            new FalconSmallPrime( 2141243393, 2138900688, 1340009628 ),
+            new FalconSmallPrime( 2141214721, 1933049835, 1087819477 ),
+            new FalconSmallPrime( 2141212673, 1898664939, 1786328049 ),
+            new FalconSmallPrime( 2141202433,  990234828,  940682169 ),
+            new FalconSmallPrime( 2141175809, 1406392421,  993089586 ),
+            new FalconSmallPrime( 2141165569, 1263518371,  289019479 ),
+            new FalconSmallPrime( 2141073409, 1485624211,  507864514 ),
+            new FalconSmallPrime( 2141052929, 1885134788,  311252465 ),
+            new FalconSmallPrime( 2141040641, 1285021247,  280941862 ),
+            new FalconSmallPrime( 2141028353, 1527610374,  375035110 ),
+            new FalconSmallPrime( 2141011969, 1400626168,  164696620 ),
+            new FalconSmallPrime( 2140999681,  632959608,  966175067 ),
+            new FalconSmallPrime( 2140997633, 2045628978, 1290889438 ),
+            new FalconSmallPrime( 2140993537, 1412755491,  375366253 ),
+            new FalconSmallPrime( 2140942337,  719477232,  785367828 ),
+            new FalconSmallPrime( 2140925953,   45224252,  836552317 ),
+            new FalconSmallPrime( 2140917761, 1157376588, 1001839569 ),
+            new FalconSmallPrime( 2140887041,  278480752, 2098732796 ),
+            new FalconSmallPrime( 2140837889, 1663139953,  924094810 ),
+            new FalconSmallPrime( 2140788737,  802501511, 2045368990 ),
+            new FalconSmallPrime( 2140766209, 1820083885, 1800295504 ),
+            new FalconSmallPrime( 2140764161, 1169561905, 2106792035 ),
+            new FalconSmallPrime( 2140696577,  127781498, 1885987531 ),
+            new FalconSmallPrime( 2140684289,   16014477, 1098116827 ),
+            new FalconSmallPrime( 2140653569,  665960598, 1796728247 ),
+            new FalconSmallPrime( 2140594177, 1043085491,  377310938 ),
+            new FalconSmallPrime( 2140579841, 1732838211, 1504505945 ),
+            new FalconSmallPrime( 2140569601,  302071939,  358291016 ),
+            new FalconSmallPrime( 2140567553,  192393733, 1909137143 ),
+            new FalconSmallPrime( 2140557313,  406595731, 1175330270 ),
+            new FalconSmallPrime( 2140549121, 1748850918,  525007007 ),
+            new FalconSmallPrime( 2140477441,  499436566, 1031159814 ),
+            new FalconSmallPrime( 2140469249, 1886004401, 1029951320 ),
+            new FalconSmallPrime( 2140426241, 1483168100, 1676273461 ),
+            new FalconSmallPrime( 2140420097, 1779917297,  846024476 ),
+            new FalconSmallPrime( 2140413953,  522948893, 1816354149 ),
+            new FalconSmallPrime( 2140383233, 1931364473, 1296921241 ),
+            new FalconSmallPrime( 2140366849, 1917356555,  147196204 ),
+            new FalconSmallPrime( 2140354561,   16466177, 1349052107 ),
+            new FalconSmallPrime( 2140348417, 1875366972, 1860485634 ),
+            new FalconSmallPrime( 2140323841,  456498717, 1790256483 ),
+            new FalconSmallPrime( 2140321793, 1629493973,  150031888 ),
+            new FalconSmallPrime( 2140315649, 1904063898,  395510935 ),
+            new FalconSmallPrime( 2140280833, 1784104328,  831417909 ),
+            new FalconSmallPrime( 2140250113,  256087139,  697349101 ),
+            new FalconSmallPrime( 2140229633,  388553070,  243875754 ),
+            new FalconSmallPrime( 2140223489,  747459608, 1396270850 ),
+            new FalconSmallPrime( 2140200961,  507423743, 1895572209 ),
+            new FalconSmallPrime( 2140162049,  580106016, 2045297469 ),
+            new FalconSmallPrime( 2140149761,  712426444,  785217995 ),
+            new FalconSmallPrime( 2140137473, 1441607584,  536866543 ),
+            new FalconSmallPrime( 2140119041,  346538902, 1740434653 ),
+            new FalconSmallPrime( 2140090369,  282642885,   21051094 ),
+            new FalconSmallPrime( 2140076033, 1407456228,  319910029 ),
+            new FalconSmallPrime( 2140047361, 1619330500, 1488632070 ),
+            new FalconSmallPrime( 2140041217, 2089408064, 2012026134 ),
+            new FalconSmallPrime( 2140008449, 1705524800, 1613440760 ),
+            new FalconSmallPrime( 2139924481, 1846208233, 1280649481 ),
+            new FalconSmallPrime( 2139906049,  989438755, 1185646076 ),
+            new FalconSmallPrime( 2139867137, 1522314850,  372783595 ),
+            new FalconSmallPrime( 2139842561, 1681587377,  216848235 ),
+            new FalconSmallPrime( 2139826177, 2066284988, 1784999464 ),
+            new FalconSmallPrime( 2139824129,  480888214, 1513323027 ),
+            new FalconSmallPrime( 2139789313,  847937200,  858192859 ),
+            new FalconSmallPrime( 2139783169, 1642000434, 1583261448 ),
+            new FalconSmallPrime( 2139770881,  940699589,  179702100 ),
+            new FalconSmallPrime( 2139768833,  315623242,  964612676 ),
+            new FalconSmallPrime( 2139666433,  331649203,  764666914 ),
+            new FalconSmallPrime( 2139641857, 2118730799, 1313764644 ),
+            new FalconSmallPrime( 2139635713,  519149027,  519212449 ),
+            new FalconSmallPrime( 2139598849, 1526413634, 1769667104 ),
+            new FalconSmallPrime( 2139574273,  551148610,  820739925 ),
+            new FalconSmallPrime( 2139568129, 1386800242,  472447405 ),
+            new FalconSmallPrime( 2139549697,  813760130, 1412328531 ),
+            new FalconSmallPrime( 2139537409, 1615286260, 1609362979 ),
+            new FalconSmallPrime( 2139475969, 1352559299, 1696720421 ),
+            new FalconSmallPrime( 2139455489, 1048691649, 1584935400 ),
+            new FalconSmallPrime( 2139432961,  836025845,  950121150 ),
+            new FalconSmallPrime( 2139424769, 1558281165, 1635486858 ),
+            new FalconSmallPrime( 2139406337, 1728402143, 1674423301 ),
+            new FalconSmallPrime( 2139396097, 1727715782, 1483470544 ),
+            new FalconSmallPrime( 2139383809, 1092853491, 1741699084 ),
+            new FalconSmallPrime( 2139369473,  690776899, 1242798709 ),
+            new FalconSmallPrime( 2139351041, 1768782380, 2120712049 ),
+            new FalconSmallPrime( 2139334657, 1739968247, 1427249225 ),
+            new FalconSmallPrime( 2139332609, 1547189119,  623011170 ),
+            new FalconSmallPrime( 2139310081, 1346827917, 1605466350 ),
+            new FalconSmallPrime( 2139303937,  369317948,  828392831 ),
+            new FalconSmallPrime( 2139301889, 1560417239, 1788073219 ),
+            new FalconSmallPrime( 2139283457, 1303121623,  595079358 ),
+            new FalconSmallPrime( 2139248641, 1354555286,  573424177 ),
+            new FalconSmallPrime( 2139240449,   60974056,  885781403 ),
+            new FalconSmallPrime( 2139222017,  355573421, 1221054839 ),
+            new FalconSmallPrime( 2139215873,  566477826, 1724006500 ),
+            new FalconSmallPrime( 2139150337,  871437673, 1609133294 ),
+            new FalconSmallPrime( 2139144193, 1478130914, 1137491905 ),
+            new FalconSmallPrime( 2139117569, 1854880922,  964728507 ),
+            new FalconSmallPrime( 2139076609,  202405335,  756508944 ),
+            new FalconSmallPrime( 2139062273, 1399715741,  884826059 ),
+            new FalconSmallPrime( 2139045889, 1051045798, 1202295476 ),
+            new FalconSmallPrime( 2139033601, 1707715206,  632234634 ),
+            new FalconSmallPrime( 2139006977, 2035853139,  231626690 ),
+            new FalconSmallPrime( 2138951681,  183867876,  838350879 ),
+            new FalconSmallPrime( 2138945537, 1403254661,  404460202 ),
+            new FalconSmallPrime( 2138920961,  310865011, 1282911681 ),
+            new FalconSmallPrime( 2138910721, 1328496553,  103472415 ),
+            new FalconSmallPrime( 2138904577,   78831681,  993513549 ),
+            new FalconSmallPrime( 2138902529, 1319697451, 1055904361 ),
+            new FalconSmallPrime( 2138816513,  384338872, 1706202469 ),
+            new FalconSmallPrime( 2138810369, 1084868275,  405677177 ),
+            new FalconSmallPrime( 2138787841,  401181788, 1964773901 ),
+            new FalconSmallPrime( 2138775553, 1850532988, 1247087473 ),
+            new FalconSmallPrime( 2138767361,  874261901, 1576073565 ),
+            new FalconSmallPrime( 2138757121, 1187474742,  993541415 ),
+            new FalconSmallPrime( 2138748929, 1782458888, 1043206483 ),
+            new FalconSmallPrime( 2138744833, 1221500487,  800141243 ),
+            new FalconSmallPrime( 2138738689,  413465368, 1450660558 ),
+            new FalconSmallPrime( 2138695681,  739045140,  342611472 ),
+            new FalconSmallPrime( 2138658817, 1355845756,  672674190 ),
+            new FalconSmallPrime( 2138644481,  608379162, 1538874380 ),
+            new FalconSmallPrime( 2138632193, 1444914034,  686911254 ),
+            new FalconSmallPrime( 2138607617,  484707818, 1435142134 ),
+            new FalconSmallPrime( 2138591233,  539460669, 1290458549 ),
+            new FalconSmallPrime( 2138572801, 2093538990, 2011138646 ),
+            new FalconSmallPrime( 2138552321, 1149786988, 1076414907 ),
+            new FalconSmallPrime( 2138546177,  840688206, 2108985273 ),
+            new FalconSmallPrime( 2138533889,  209669619,  198172413 ),
+            new FalconSmallPrime( 2138523649, 1975879426, 1277003968 ),
+            new FalconSmallPrime( 2138490881, 1351891144, 1976858109 ),
+            new FalconSmallPrime( 2138460161, 1817321013, 1979278293 ),
+            new FalconSmallPrime( 2138429441, 1950077177,  203441928 ),
+            new FalconSmallPrime( 2138400769,  908970113,  628395069 ),
+            new FalconSmallPrime( 2138398721,  219890864,  758486760 ),
+            new FalconSmallPrime( 2138376193, 1306654379,  977554090 ),
+            new FalconSmallPrime( 2138351617,  298822498, 2004708503 ),
+            new FalconSmallPrime( 2138337281,  441457816, 1049002108 ),
+            new FalconSmallPrime( 2138320897, 1517731724, 1442269609 ),
+            new FalconSmallPrime( 2138290177, 1355911197, 1647139103 ),
+            new FalconSmallPrime( 2138234881,  531313247, 1746591962 ),
+            new FalconSmallPrime( 2138214401, 1899410930,  781416444 ),
+            new FalconSmallPrime( 2138202113, 1813477173, 1622508515 ),
+            new FalconSmallPrime( 2138191873, 1086458299, 1025408615 ),
+            new FalconSmallPrime( 2138183681, 1998800427,  827063290 ),
+            new FalconSmallPrime( 2138173441, 1921308898,  749670117 ),
+            new FalconSmallPrime( 2138103809, 1620902804, 2126787647 ),
+            new FalconSmallPrime( 2138099713,  828647069, 1892961817 ),
+            new FalconSmallPrime( 2138085377,  179405355, 1525506535 ),
+            new FalconSmallPrime( 2138060801,  615683235, 1259580138 ),
+            new FalconSmallPrime( 2138044417, 2030277840, 1731266562 ),
+            new FalconSmallPrime( 2138042369, 2087222316, 1627902259 ),
+            new FalconSmallPrime( 2138032129,  126388712, 1108640984 ),
+            new FalconSmallPrime( 2138011649,  715026550, 1017980050 ),
+            new FalconSmallPrime( 2137993217, 1693714349, 1351778704 ),
+            new FalconSmallPrime( 2137888769, 1289762259, 1053090405 ),
+            new FalconSmallPrime( 2137853953,  199991890, 1254192789 ),
+            new FalconSmallPrime( 2137833473,  941421685,  896995556 ),
+            new FalconSmallPrime( 2137817089,  750416446, 1251031181 ),
+            new FalconSmallPrime( 2137792513,  798075119,  368077456 ),
+            new FalconSmallPrime( 2137786369,  878543495, 1035375025 ),
+            new FalconSmallPrime( 2137767937,    9351178, 1156563902 ),
+            new FalconSmallPrime( 2137755649, 1382297614, 1686559583 ),
+            new FalconSmallPrime( 2137724929, 1345472850, 1681096331 ),
+            new FalconSmallPrime( 2137704449,  834666929,  630551727 ),
+            new FalconSmallPrime( 2137673729, 1646165729, 1892091571 ),
+            new FalconSmallPrime( 2137620481,  778943821,   48456461 ),
+            new FalconSmallPrime( 2137618433, 1730837875, 1713336725 ),
+            new FalconSmallPrime( 2137581569,  805610339, 1378891359 ),
+            new FalconSmallPrime( 2137538561,  204342388, 1950165220 ),
+            new FalconSmallPrime( 2137526273, 1947629754, 1500789441 ),
+            new FalconSmallPrime( 2137516033,  719902645, 1499525372 ),
+            new FalconSmallPrime( 2137491457,  230451261,  556382829 ),
+            new FalconSmallPrime( 2137440257,  979573541,  412760291 ),
+            new FalconSmallPrime( 2137374721,  927841248, 1954137185 ),
+            new FalconSmallPrime( 2137362433, 1243778559,  861024672 ),
+            new FalconSmallPrime( 2137313281, 1341338501,  980638386 ),
+            new FalconSmallPrime( 2137311233,  937415182, 1793212117 ),
+            new FalconSmallPrime( 2137255937,  795331324, 1410253405 ),
+            new FalconSmallPrime( 2137243649,  150756339, 1966999887 ),
+            new FalconSmallPrime( 2137182209,  163346914, 1939301431 ),
+            new FalconSmallPrime( 2137171969, 1952552395,  758913141 ),
+            new FalconSmallPrime( 2137159681,  570788721,  218668666 ),
+            new FalconSmallPrime( 2137147393, 1896656810, 2045670345 ),
+            new FalconSmallPrime( 2137141249,  358493842,  518199643 ),
+            new FalconSmallPrime( 2137139201, 1505023029,  674695848 ),
+            new FalconSmallPrime( 2137133057,   27911103,  830956306 ),
+            new FalconSmallPrime( 2137122817,  439771337, 1555268614 ),
+            new FalconSmallPrime( 2137116673,  790988579, 1871449599 ),
+            new FalconSmallPrime( 2137110529,  432109234,  811805080 ),
+            new FalconSmallPrime( 2137102337, 1357900653, 1184997641 ),
+            new FalconSmallPrime( 2137098241,  515119035, 1715693095 ),
+            new FalconSmallPrime( 2137090049,  408575203, 2085660657 ),
+            new FalconSmallPrime( 2137085953, 2097793407, 1349626963 ),
+            new FalconSmallPrime( 2137055233, 1556739954, 1449960883 ),
+            new FalconSmallPrime( 2137030657, 1545758650, 1369303716 ),
+            new FalconSmallPrime( 2136987649,  332602570,  103875114 ),
+            new FalconSmallPrime( 2136969217, 1499989506, 1662964115 ),
+            new FalconSmallPrime( 2136924161,  857040753,    4738842 ),
+            new FalconSmallPrime( 2136895489, 1948872712,  570436091 ),
+            new FalconSmallPrime( 2136893441,   58969960, 1568349634 ),
+            new FalconSmallPrime( 2136887297, 2127193379,  273612548 ),
+            new FalconSmallPrime( 2136850433,  111208983, 1181257116 ),
+            new FalconSmallPrime( 2136809473, 1627275942, 1680317971 ),
+            new FalconSmallPrime( 2136764417, 1574888217,   14011331 ),
+            new FalconSmallPrime( 2136741889,   14011055, 1129154251 ),
+            new FalconSmallPrime( 2136727553,   35862563, 1838555253 ),
+            new FalconSmallPrime( 2136721409,  310235666, 1363928244 ),
+            new FalconSmallPrime( 2136698881, 1612429202, 1560383828 ),
+            new FalconSmallPrime( 2136649729, 1138540131,  800014364 ),
+            new FalconSmallPrime( 2136606721,  602323503, 1433096652 ),
+            new FalconSmallPrime( 2136563713,  182209265, 1919611038 ),
+            new FalconSmallPrime( 2136555521,  324156477,  165591039 ),
+            new FalconSmallPrime( 2136549377,  195513113,  217165345 ),
+            new FalconSmallPrime( 2136526849, 1050768046,  939647887 ),
+            new FalconSmallPrime( 2136508417, 1886286237, 1619926572 ),
+            new FalconSmallPrime( 2136477697,  609647664,   35065157 ),
+            new FalconSmallPrime( 2136471553,  679352216, 1452259468 ),
+            new FalconSmallPrime( 2136457217,  128630031,  824816521 ),
+            new FalconSmallPrime( 2136422401,   19787464, 1526049830 ),
+            new FalconSmallPrime( 2136420353,  698316836, 1530623527 ),
+            new FalconSmallPrime( 2136371201, 1651862373, 1804812805 ),
+            new FalconSmallPrime( 2136334337,  326596005,  336977082 ),
+            new FalconSmallPrime( 2136322049,   63253370, 1904972151 ),
+            new FalconSmallPrime( 2136297473,  312176076,  172182411 ),
+            new FalconSmallPrime( 2136248321,  381261841,  369032670 ),
+            new FalconSmallPrime( 2136242177,  358688773, 1640007994 ),
+            new FalconSmallPrime( 2136229889,  512677188,   75585225 ),
+            new FalconSmallPrime( 2136219649, 2095003250, 1970086149 ),
+            new FalconSmallPrime( 2136207361, 1909650722,  537760675 ),
+            new FalconSmallPrime( 2136176641, 1334616195, 1533487619 ),
+            new FalconSmallPrime( 2136158209, 2096285632, 1793285210 ),
+            new FalconSmallPrime( 2136143873, 1897347517,  293843959 ),
+            new FalconSmallPrime( 2136133633,  923586222, 1022655978 ),
+            new FalconSmallPrime( 2136096769, 1464868191, 1515074410 ),
+            new FalconSmallPrime( 2136094721, 2020679520, 2061636104 ),
+            new FalconSmallPrime( 2136076289,  290798503, 1814726809 ),
+            new FalconSmallPrime( 2136041473,  156415894, 1250757633 ),
+            new FalconSmallPrime( 2135996417,  297459940, 1132158924 ),
+            new FalconSmallPrime( 2135955457,  538755304, 1688831340 ),
+            new FalconSmallPrime( 0, 0, 0 )
+            };
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/FalconVrfy.cs b/crypto/src/pqc/crypto/falcon/FalconVrfy.cs
new file mode 100644
index 000000000..4f28a77d9
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/FalconVrfy.cs
@@ -0,0 +1,860 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class FalconVrfy
+    {
+        FalconCommon common;
+        internal FalconVrfy() {
+            this.common = new FalconCommon();
+        }
+        internal FalconVrfy(FalconCommon common) {
+            this.common = common;
+        }
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+        /* ===================================================================== */
+        /*
+        * Constants for NTT.
+        *
+        *   n = 2^logn  (2 <= n <= 1024)
+        *   phi = X^n + 1
+        *   q = 12289
+        *   q0i = -1/q mod 2^16
+        *   R = 2^16 mod q
+        *   R2 = 2^32 mod q
+        */
+
+        const int Q = 12289;
+        const int Q0I = 12287;
+        const int R = 4091;
+        const int R2 = 10952;
+
+        /*
+        * Table for NTT, binary case:
+        *   GMb[x] = R*(g^rev(x)) mod q
+        * where g = 7 (it is a 2048-th primitive root of 1 modulo q)
+        * and rev() is the bit-reversal function over 10 bits.
+        */
+        internal ushort[] GMb = {
+            4091,  7888, 11060, 11208,  6960,  4342,  6275,  9759,
+            1591,  6399,  9477,  5266,   586,  5825,  7538,  9710,
+            1134,  6407,  1711,   965,  7099,  7674,  3743,  6442,
+            10414,  8100,  1885,  1688,  1364, 10329, 10164,  9180,
+            12210,  6240,   997,   117,  4783,  4407,  1549,  7072,
+            2829,  6458,  4431,  8877,  7144,  2564,  5664,  4042,
+            12189,   432, 10751,  1237,  7610,  1534,  3983,  7863,
+            2181,  6308,  8720,  6570,  4843,  1690,    14,  3872,
+            5569,  9368, 12163,  2019,  7543,  2315,  4673,  7340,
+            1553,  1156,  8401, 11389,  1020,  2967, 10772,  7045,
+            3316, 11236,  5285, 11578, 10637, 10086,  9493,  6180,
+            9277,  6130,  3323,   883, 10469,   489,  1502,  2851,
+            11061,  9729,  2742, 12241,  4970, 10481, 10078,  1195,
+            730,  1762,  3854,  2030,  5892, 10922,  9020,  5274,
+            9179,  3604,  3782, 10206,  3180,  3467,  4668,  2446,
+            7613,  9386,   834,  7703,  6836,  3403,  5351, 12276,
+            3580,  1739, 10820,  9787, 10209,  4070, 12250,  8525,
+            10401,  2749,  7338, 10574,  6040,   943,  9330,  1477,
+            6865,  9668,  3585,  6633, 12145,  4063,  3684,  7680,
+            8188,  6902,  3533,  9807,  6090,   727, 10099,  7003,
+            6945,  1949,  9731, 10559,  6057,   378,  7871,  8763,
+            8901,  9229,  8846,  4551,  9589, 11664,  7630,  8821,
+            5680,  4956,  6251,  8388, 10156,  8723,  2341,  3159,
+            1467,  5460,  8553,  7783,  2649,  2320,  9036,  6188,
+            737,  3698,  4699,  5753,  9046,  3687,    16,   914,
+            5186, 10531,  4552,  1964,  3509,  8436,  7516,  5381,
+            10733,  3281,  7037,  1060,  2895,  7156,  8887,  5357,
+            6409,  8197,  2962,  6375,  5064,  6634,  5625,   278,
+            932, 10229,  8927,  7642,   351,  9298,   237,  5858,
+            7692,  3146, 12126,  7586,  2053, 11285,  3802,  5204,
+            4602,  1748, 11300,   340,  3711,  4614,   300, 10993,
+            5070, 10049, 11616, 12247,  7421, 10707,  5746,  5654,
+            3835,  5553,  1224,  8476,  9237,  3845,   250, 11209,
+            4225,  6326,  9680, 12254,  4136,  2778,   692,  8808,
+            6410,  6718, 10105, 10418,  3759,  7356, 11361,  8433,
+            6437,  3652,  6342,  8978,  5391,  2272,  6476,  7416,
+            8418, 10824, 11986,  5733,   876,  7030,  2167,  2436,
+            3442,  9217,  8206,  4858,  5964,  2746,  7178,  1434,
+            7389,  8879, 10661, 11457,  4220,  1432, 10832,  4328,
+            8557,  1867,  9454,  2416,  3816,  9076,   686,  5393,
+            2523,  4339,  6115,   619,   937,  2834,  7775,  3279,
+            2363,  7488,  6112,  5056,   824, 10204, 11690,  1113,
+            2727,  9848,   896,  2028,  5075,  2654, 10464,  7884,
+            12169,  5434,  3070,  6400,  9132, 11672, 12153,  4520,
+            1273,  9739, 11468,  9937, 10039,  9720,  2262,  9399,
+            11192,   315,  4511,  1158,  6061,  6751, 11865,   357,
+            7367,  4550,   983,  8534,  8352, 10126,  7530,  9253,
+            4367,  5221,  3999,  8777,  3161,  6990,  4130, 11652,
+            3374, 11477,  1753,   292,  8681,  2806, 10378, 12188,
+            5800, 11811,  3181,  1988,  1024,  9340,  2477, 10928,
+            4582,  6750,  3619,  5503,  5233,  2463,  8470,  7650,
+            7964,  6395,  1071,  1272,  3474, 11045,  3291, 11344,
+            8502,  9478,  9837,  1253,  1857,  6233,  4720, 11561,
+            6034,  9817,  3339,  1797,  2879,  6242,  5200,  2114,
+            7962,  9353, 11363,  5475,  6084,  9601,  4108,  7323,
+            10438,  9471,  1271,   408,  6911,  3079,   360,  8276,
+            11535,  9156,  9049, 11539,   850,  8617,   784,  7919,
+            8334, 12170,  1846, 10213, 12184,  7827, 11903,  5600,
+            9779,  1012,   721,  2784,  6676,  6552,  5348,  4424,
+            6816,  8405,  9959,  5150,  2356,  5552,  5267,  1333,
+            8801,  9661,  7308,  5788,  4910,   909, 11613,  4395,
+            8238,  6686,  4302,  3044,  2285, 12249,  1963,  9216,
+            4296, 11918,   695,  4371,  9793,  4884,  2411, 10230,
+            2650,   841,  3890, 10231,  7248,  8505, 11196,  6688,
+            4059,  6060,  3686,  4722, 11853,  5816,  7058,  6868,
+            11137,  7926,  4894, 12284,  4102,  3908,  3610,  6525,
+            7938,  7982, 11977,  6755,   537,  4562,  1623,  8227,
+            11453,  7544,   906, 11816,  9548, 10858,  9703,  2815,
+            11736,  6813,  6979,   819,  8903,  6271, 10843,   348,
+            7514,  8339,  6439,   694,   852,  5659,  2781,  3716,
+            11589,  3024,  1523,  8659,  4114, 10738,  3303,  5885,
+            2978,  7289, 11884,  9123,  9323, 11830,    98,  2526,
+            2116,  4131, 11407,  1844,  3645,  3916,  8133,  2224,
+            10871,  8092,  9651,  5989,  7140,  8480,  1670,   159,
+            10923,  4918,   128,  7312,   725,  9157,  5006,  6393,
+            3494,  6043, 10972,  6181, 11838,  3423, 10514,  7668,
+            3693,  6658,  6905, 11953, 10212, 11922,  9101,  8365,
+            5110,    45,  2400,  1921,  4377,  2720,  1695,    51,
+            2808,   650,  1896,  9997,  9971, 11980,  8098,  4833,
+            4135,  4257,  5838,  4765, 10985, 11532,   590, 12198,
+            482, 12173,  2006,  7064, 10018,  3912, 12016, 10519,
+            11362,  6954,  2210,   284,  5413,  6601,  3865, 10339,
+            11188,  6231,   517,  9564, 11281,  3863,  1210,  4604,
+            8160, 11447,   153,  7204,  5763,  5089,  9248, 12154,
+            11748,  1354,  6672,   179,  5532,  2646,  5941, 12185,
+            862,  3158,   477,  7279,  5678,  7914,  4254,   302,
+            2893, 10114,  6890,  9560,  9647, 11905,  4098,  9824,
+            10269,  1353, 10715,  5325,  6254,  3951,  1807,  6449,
+            5159,  1308,  8315,  3404,  1877,  1231,   112,  6398,
+            11724, 12272,  7286,  1459, 12274,  9896,  3456,   800,
+            1397, 10678,   103,  7420,  7976,   936,   764,   632,
+            7996,  8223,  8445,  7758, 10870,  9571,  2508,  1946,
+            6524, 10158,  1044,  4338,  2457,  3641,  1659,  4139,
+            4688,  9733, 11148,  3946,  2082,  5261,  2036, 11850,
+            7636, 12236,  5366,  2380,  1399,  7720,  2100,  3217,
+            10912,  8898,  7578, 11995,  2791,  1215,  3355,  2711,
+            2267,  2004,  8568, 10176,  3214,  2337,  1750,  4729,
+            4997,  7415,  6315, 12044,  4374,  7157,  4844,   211,
+            8003, 10159,  9290, 11481,  1735,  2336,  5793,  9875,
+            8192,   986,  7527,  1401,   870,  3615,  8465,  2756,
+            9770,  2034, 10168,  3264,  6132,    54,  2880,  4763,
+            11805,  3074,  8286,  9428,  4881,  6933,  1090, 10038,
+            2567,   708,   893,  6465,  4962, 10024,  2090,  5718,
+            10743,   780,  4733,  4623,  2134,  2087,  4802,   884,
+            5372,  5795,  5938,  4333,  6559,  7549,  5269, 10664,
+            4252,  3260,  5917, 10814,  5768,  9983,  8096,  7791,
+            6800,  7491,  6272,  1907, 10947,  6289, 11803,  6032,
+            11449,  1171,  9201,  7933,  2479,  7970, 11337,  7062,
+            8911,  6728,  6542,  8114,  8828,  6595,  3545,  4348,
+            4610,  2205,  6999,  8106,  5560, 10390,  9321,  2499,
+            2413,  7272,  6881, 10582,  9308,  9437,  3554,  3326,
+            5991, 11969,  3415, 12283,  9838, 12063,  4332,  7830,
+            11329,  6605, 12271,  2044, 11611,  7353, 11201, 11582,
+            3733,  8943,  9978,  1627,  7168,  3935,  5050,  2762,
+            7496, 10383,   755,  1654, 12053,  4952, 10134,  4394,
+            6592,  7898,  7497,  8904, 12029,  3581, 10748,  5674,
+            10358,  4901,  7414,  8771,   710,  6764,  8462,  7193,
+            5371,  7274, 11084,   290,  7864,  6827, 11822,  2509,
+            6578,  4026,  5807,  1458,  5721,  5762,  4178,  2105,
+            11621,  4852,  8897,  2856, 11510,  9264,  2520,  8776,
+            7011,  2647,  1898,  7039,  5950, 11163,  5488,  6277,
+            9182, 11456,   633, 10046, 11554,  5633,  9587,  2333,
+            7008,  7084,  5047,  7199,  9865,  8997,   569,  6390,
+            10845,  9679,  8268, 11472,  4203,  1997,     2,  9331,
+            162,  6182,  2000,  3649,  9792,  6363,  7557,  6187,
+            8510,  9935,  5536,  9019,  3706, 12009,  1452,  3067,
+            5494,  9692,  4865,  6019,  7106,  9610,  4588, 10165,
+            6261,  5887,  2652, 10172,  1580, 10379,  4638,  9949
+        };
+
+        /*
+        * Table for inverse NTT, binary case:
+        *   iGMb[x] = R*((1/g)^rev(x)) mod q
+        * Since g = 7, 1/g = 8778 mod 12289.
+        */
+        internal ushort[] iGMb = {
+            4091,  4401,  1081,  1229,  2530,  6014,  7947,  5329,
+            2579,  4751,  6464, 11703,  7023,  2812,  5890, 10698,
+            3109,  2125,  1960, 10925, 10601, 10404,  4189,  1875,
+            5847,  8546,  4615,  5190, 11324, 10578,  5882, 11155,
+            8417, 12275, 10599,  7446,  5719,  3569,  5981, 10108,
+            4426,  8306, 10755,  4679, 11052,  1538, 11857,   100,
+            8247,  6625,  9725,  5145,  3412,  7858,  5831,  9460,
+            5217, 10740,  7882,  7506, 12172, 11292,  6049,    79,
+            13,  6938,  8886,  5453,  4586, 11455,  2903,  4676,
+            9843,  7621,  8822,  9109,  2083,  8507,  8685,  3110,
+            7015,  3269,  1367,  6397, 10259,  8435, 10527, 11559,
+            11094,  2211,  1808,  7319,    48,  9547,  2560,  1228,
+            9438, 10787, 11800,  1820, 11406,  8966,  6159,  3012,
+            6109,  2796,  2203,  1652,   711,  7004,  1053,  8973,
+            5244,  1517,  9322, 11269,   900,  3888, 11133, 10736,
+            4949,  7616,  9974,  4746, 10270,   126,  2921,  6720,
+            6635,  6543,  1582,  4868,    42,   673,  2240,  7219,
+            1296, 11989,  7675,  8578, 11949,   989, 10541,  7687,
+            7085,  8487,  1004, 10236,  4703,   163,  9143,  4597,
+            6431, 12052,  2991, 11938,  4647,  3362,  2060, 11357,
+            12011,  6664,  5655,  7225,  5914,  9327,  4092,  5880,
+            6932,  3402,  5133,  9394, 11229,  5252,  9008,  1556,
+            6908,  4773,  3853,  8780, 10325,  7737,  1758,  7103,
+            11375, 12273,  8602,  3243,  6536,  7590,  8591, 11552,
+            6101,  3253,  9969,  9640,  4506,  3736,  6829, 10822,
+            9130,  9948,  3566,  2133,  3901,  6038,  7333,  6609,
+            3468,  4659,   625,  2700,  7738,  3443,  3060,  3388,
+            3526,  4418, 11911,  6232,  1730,  2558, 10340,  5344,
+            5286,  2190, 11562,  6199,  2482,  8756,  5387,  4101,
+            4609,  8605,  8226,   144,  5656,  8704,  2621,  5424,
+            10812,  2959, 11346,  6249,  1715,  4951,  9540,  1888,
+            3764,    39,  8219,  2080,  2502,  1469, 10550,  8709,
+            5601,  1093,  3784,  5041,  2058,  8399, 11448,  9639,
+            2059,  9878,  7405,  2496,  7918, 11594,   371,  7993,
+            3073, 10326,    40, 10004,  9245,  7987,  5603,  4051,
+            7894,   676, 11380,  7379,  6501,  4981,  2628,  3488,
+            10956,  7022,  6737,  9933,  7139,  2330,  3884,  5473,
+            7865,  6941,  5737,  5613,  9505, 11568, 11277,  2510,
+            6689,   386,  4462,   105,  2076, 10443,   119,  3955,
+            4370, 11505,  3672, 11439,   750,  3240,  3133,   754,
+            4013, 11929,  9210,  5378, 11881, 11018,  2818,  1851,
+            4966,  8181,  2688,  6205,  6814,   926,  2936,  4327,
+            10175,  7089,  6047,  9410, 10492,  8950,  2472,  6255,
+            728,  7569,  6056, 10432, 11036,  2452,  2811,  3787,
+            945,  8998,  1244,  8815, 11017, 11218,  5894,  4325,
+            4639,  3819,  9826,  7056,  6786,  8670,  5539,  7707,
+            1361,  9812,  2949, 11265, 10301,  9108,   478,  6489,
+            101,  1911,  9483,  3608, 11997, 10536,   812,  8915,
+            637,  8159,  5299,  9128,  3512,  8290,  7068,  7922,
+            3036,  4759,  2163,  3937,  3755, 11306,  7739,  4922,
+            11932,   424,  5538,  6228, 11131,  7778, 11974,  1097,
+            2890, 10027,  2569,  2250,  2352,   821,  2550, 11016,
+            7769,   136,   617,  3157,  5889,  9219,  6855,   120,
+            4405,  1825,  9635,  7214, 10261, 11393,  2441,  9562,
+            11176,   599,  2085, 11465,  7233,  6177,  4801,  9926,
+            9010,  4514,  9455, 11352, 11670,  6174,  7950,  9766,
+            6896, 11603,  3213,  8473,  9873,  2835, 10422,  3732,
+            7961,  1457, 10857,  8069,   832,  1628,  3410,  4900,
+            10855,  5111,  9543,  6325,  7431,  4083,  3072,  8847,
+            9853, 10122,  5259, 11413,  6556,   303,  1465,  3871,
+            4873,  5813, 10017,  6898,  3311,  5947,  8637,  5852,
+            3856,   928,  4933,  8530,  1871,  2184,  5571,  5879,
+            3481, 11597,  9511,  8153,    35,  2609,  5963,  8064,
+            1080, 12039,  8444,  3052,  3813, 11065,  6736,  8454,
+            2340,  7651,  1910, 10709,  2117,  9637,  6402,  6028,
+            2124,  7701,  2679,  5183,  6270,  7424,  2597,  6795,
+            9222, 10837,   280,  8583,  3270,  6753,  2354,  3779,
+            6102,  4732,  5926,  2497,  8640, 10289,  6107, 12127,
+            2958, 12287, 10292,  8086,   817,  4021,  2610,  1444,
+            5899, 11720,  3292,  2424,  5090,  7242,  5205,  5281,
+            9956,  2702,  6656,   735,  2243, 11656,   833,  3107,
+            6012,  6801,  1126,  6339,  5250, 10391,  9642,  5278,
+            3513,  9769,  3025,   779,  9433,  3392,  7437,   668,
+            10184,  8111,  6527,  6568, 10831,  6482,  8263,  5711,
+            9780,   467,  5462,  4425, 11999,  1205,  5015,  6918,
+            5096,  3827,  5525, 11579,  3518,  4875,  7388,  1931,
+            6615,  1541,  8708,   260,  3385,  4792,  4391,  5697,
+            7895,  2155,  7337,   236, 10635, 11534,  1906,  4793,
+            9527,  7239,  8354,  5121, 10662,  2311,  3346,  8556,
+            707,  1088,  4936,   678, 10245,    18,  5684,   960,
+            4459,  7957,   226,  2451,     6,  8874,   320,  6298,
+            8963,  8735,  2852,  2981,  1707,  5408,  5017,  9876,
+            9790,  2968,  1899,  6729,  4183,  5290, 10084,  7679,
+            7941,  8744,  5694,  3461,  4175,  5747,  5561,  3378,
+            5227,   952,  4319,  9810,  4356,  3088, 11118,   840,
+            6257,   486,  6000,  1342, 10382,  6017,  4798,  5489,
+            4498,  4193,  2306,  6521,  1475,  6372,  9029,  8037,
+            1625,  7020,  4740,  5730,  7956,  6351,  6494,  6917,
+            11405,  7487, 10202, 10155,  7666,  7556, 11509,  1546,
+            6571, 10199,  2265,  7327,  5824, 11396, 11581,  9722,
+            2251, 11199,  5356,  7408,  2861,  4003,  9215,   484,
+            7526,  9409, 12235,  6157,  9025,  2121, 10255,  2519,
+            9533,  3824,  8674, 11419, 10888,  4762, 11303,  4097,
+            2414,  6496,  9953, 10554,   808,  2999,  2130,  4286,
+            12078,  7445,  5132,  7915,   245,  5974,  4874,  7292,
+            7560, 10539,  9952,  9075,  2113,  3721, 10285, 10022,
+            9578,  8934, 11074,  9498,   294,  4711,  3391,  1377,
+            9072, 10189,  4569, 10890,  9909,  6923,    53,  4653,
+            439, 10253,  7028, 10207,  8343,  1141,  2556,  7601,
+            8150, 10630,  8648,  9832,  7951, 11245,  2131,  5765,
+            10343,  9781,  2718,  1419,  4531,  3844,  4066,  4293,
+            11657, 11525, 11353,  4313,  4869, 12186,  1611, 10892,
+            11489,  8833,  2393,    15, 10830,  5003,    17,   565,
+            5891, 12177, 11058, 10412,  8885,  3974, 10981,  7130,
+            5840, 10482,  8338,  6035,  6964,  1574, 10936,  2020,
+            2465,  8191,   384,  2642,  2729,  5399,  2175,  9396,
+            11987,  8035,  4375,  6611,  5010, 11812,  9131, 11427,
+            104,  6348,  9643,  6757, 12110,  5617, 10935,   541,
+            135,  3041,  7200,  6526,  5085, 12136,   842,  4129,
+            7685, 11079,  8426,  1008,  2725, 11772,  6058,  1101,
+            1950,  8424,  5688,  6876, 12005, 10079,  5335,   927,
+            1770,   273,  8377,  2271,  5225, 10283,   116, 11807,
+            91, 11699,   757,  1304,  7524,  6451,  8032,  8154,
+            7456,  4191,   309,  2318,  2292, 10393, 11639,  9481,
+            12238, 10594,  9569,  7912, 10368,  9889, 12244,  7179,
+            3924,  3188,   367,  2077,   336,  5384,  5631,  8596,
+            4621,  1775,  8866,   451,  6108,  1317,  6246,  8795,
+            5896,  7283,  3132, 11564,  4977, 12161,  7371,  1366,
+            12130, 10619,  3809,  5149,  6300,  2638,  4197,  1418,
+            10065,  4156,  8373,  8644, 10445,   882,  8158, 10173,
+            9763, 12191,   459,  2966,  3166,   405,  5000,  9311,
+            6404,  8986,  1551,  8175,  3630, 10766,  9265,   700,
+            8573,  9508,  6630, 11437, 11595,  5850,  3950,  4775,
+            11941,  1446,  6018,  3386, 11470,  5310,  5476,   553,
+            9474,  2586,  1431,  2741,   473, 11383,  4745,   836,
+            4062, 10666,  7727, 11752,  5534,   312,  4307,  4351,
+            5764,  8679,  8381,  8187,     5,  7395,  4363,  1152,
+            5421,  5231,  6473,   436,  7567,  8603,  6229,  8230
+        };
+
+        /*
+        * Reduce a small signed integer modulo q. The source integer MUST
+        * be between -q/2 and +q/2.
+        */
+        internal uint mq_conv_small(int x)
+        {
+            /*
+            * If x < 0, the cast to uint will set the high bit to 1.
+            */
+            uint y;
+
+            y = (uint)x;
+            y += (uint)(Q & -(y >> 31));
+            return y;
+        }
+
+        /*
+        * Addition modulo q. Operands must be in the 0..q-1 range.
+        */
+        internal uint mq_add(uint x, uint y)
+        {
+            /*
+            * We compute x + y - q. If the result is negative, then the
+            * high bit will be set, and 'd >> 31' will be equal to 1;
+            * thus '-(d >> 31)' will be an all-one pattern. Otherwise,
+            * it will be an all-zero pattern. In other words, this
+            * implements a conditional addition of q.
+            */
+            uint d;
+
+            d = x + y - Q;
+            d += (uint)(Q & -(d >> 31));
+            return d;
+        }
+
+        /*
+        * Subtraction modulo q. Operands must be in the 0..q-1 range.
+        */
+        internal uint mq_sub(uint x, uint y)
+        {
+            /*
+            * As in mq_add(), we use a conditional addition to ensure the
+            * result is in the 0..q-1 range.
+            */
+            uint d;
+
+            d = x - y;
+            d += (uint)(Q & -(d >> 31));
+            return d;
+        }
+
+        /*
+        * Division by 2 modulo q. Operand must be in the 0..q-1 range.
+        */
+        internal uint mq_rshift1(uint x)
+        {
+            x += (uint)(Q & -(x & 1));
+            return (x >> 1);
+        }
+
+        /*
+        * Montgomery multiplication modulo q. If we set R = 2^16 mod q, then
+        * this function computes: x * y / R mod q
+        * Operands must be in the 0..q-1 range.
+        */
+        internal uint mq_montymul(uint x, uint y)
+        {
+            uint z, w;
+
+            /*
+            * We compute x*y + k*q with a value of k chosen so that the 16
+            * low bits of the result are 0. We can then shift the value.
+            * After the shift, result may still be larger than q, but it
+            * will be lower than 2*q, so a conditional subtraction works.
+            */
+
+            z = x * y;
+            w = ((z * Q0I) & 0xFFFF) * Q;
+
+            /*
+            * When adding z and w, the result will have its low 16 bits
+            * equal to 0. Since x, y and z are lower than q, the sum will
+            * be no more than (2^15 - 1) * q + (q - 1)^2, which will
+            * fit on 29 bits.
+            */
+            z = (z + w) >> 16;
+
+            /*
+            * After the shift, analysis shows that the value will be less
+            * than 2q. We do a subtraction then conditional subtraction to
+            * ensure the result is in the expected range.
+            */
+            z -= Q;
+            z += (uint)(Q & -(z >> 31));
+            return z;
+        }
+
+        /*
+        * Montgomery squaring (computes (x^2)/R).
+        */
+        internal uint mq_montysqr(uint x)
+        {
+            return mq_montymul(x, x);
+        }
+
+        /*
+        * Divide x by y modulo q = 12289.
+        */
+        internal uint mq_div_12289(uint x, uint y)
+        {
+            /*
+            * We invert y by computing y^(q-2) mod q.
+            *
+            * We use the following addition chain for exponent e = 12287:
+            *
+            *   e0 = 1
+            *   e1 = 2 * e0 = 2
+            *   e2 = e1 + e0 = 3
+            *   e3 = e2 + e1 = 5
+            *   e4 = 2 * e3 = 10
+            *   e5 = 2 * e4 = 20
+            *   e6 = 2 * e5 = 40
+            *   e7 = 2 * e6 = 80
+            *   e8 = 2 * e7 = 160
+            *   e9 = e8 + e2 = 163
+            *   e10 = e9 + e8 = 323
+            *   e11 = 2 * e10 = 646
+            *   e12 = 2 * e11 = 1292
+            *   e13 = e12 + e9 = 1455
+            *   e14 = 2 * e13 = 2910
+            *   e15 = 2 * e14 = 5820
+            *   e16 = e15 + e10 = 6143
+            *   e17 = 2 * e16 = 12286
+            *   e18 = e17 + e0 = 12287
+            *
+            * Additions on exponents are converted to Montgomery
+            * multiplications. We define all intermediate results as so
+            * many local variables, and let the C compiler work out which
+            * must be kept around.
+            */
+            uint y0, y1, y2, y3, y4, y5, y6, y7, y8, y9;
+            uint y10, y11, y12, y13, y14, y15, y16, y17, y18;
+
+            y0 = mq_montymul(y, R2);
+            y1 = mq_montysqr(y0);
+            y2 = mq_montymul(y1, y0);
+            y3 = mq_montymul(y2, y1);
+            y4 = mq_montysqr(y3);
+            y5 = mq_montysqr(y4);
+            y6 = mq_montysqr(y5);
+            y7 = mq_montysqr(y6);
+            y8 = mq_montysqr(y7);
+            y9 = mq_montymul(y8, y2);
+            y10 = mq_montymul(y9, y8);
+            y11 = mq_montysqr(y10);
+            y12 = mq_montysqr(y11);
+            y13 = mq_montymul(y12, y9);
+            y14 = mq_montysqr(y13);
+            y15 = mq_montysqr(y14);
+            y16 = mq_montymul(y15, y10);
+            y17 = mq_montysqr(y16);
+            y18 = mq_montymul(y17, y0);
+
+            /*
+            * Final multiplication with x, which is not in Montgomery
+            * representation, computes the correct division result.
+            */
+            return mq_montymul(y18, x);
+        }
+
+        /*
+        * Compute NTT on a ring element.
+        */
+        internal void mq_NTT(ushort[] asrc, int a, uint logn)
+        {
+            int n, t, m;
+
+            n = (int)1 << (int)logn;
+            t = n;
+            for (m = 1; m < n; m <<= 1) {
+                int ht, i, j1;
+
+                ht = t >> 1;
+                for (i = 0, j1 = 0; i < m; i ++, j1 += t) {
+                    int j, j2;
+                    uint s;
+
+                    s = GMb[m + i];
+                    j2 = j1 + ht;
+                    for (j = j1; j < j2; j ++) {
+                        uint u, v;
+
+                        u = asrc[a + j];
+                        v = mq_montymul(asrc[a + j + ht], s);
+                        asrc[a + j] = (ushort)mq_add(u, v);
+                        asrc[a + j + ht] = (ushort)mq_sub(u, v);
+                    }
+                }
+                t = ht;
+            }
+        }
+
+        /*
+        * Compute the inverse NTT on a ring element, binary case.
+        */
+        internal void mq_iNTT(ushort[] asrc, int a, uint logn)
+        {
+            int n, t, m;
+            uint ni;
+
+            n = (int)1 << (int)logn;
+            t = 1;
+            m = n;
+            while (m > 1) {
+                int hm, dt, i, j1;
+
+                hm = m >> 1;
+                dt = t << 1;
+                for (i = 0, j1 = 0; i < hm; i ++, j1 += dt) {
+                    int j, j2;
+                    uint s;
+
+                    j2 = j1 + t;
+                    s = iGMb[hm + i];
+                    for (j = j1; j < j2; j ++) {
+                        uint u, v, w;
+
+                        u = asrc[a + j];
+                        v = asrc[a + j + t];
+                        asrc[a + j] = (ushort)mq_add(u, v);
+                        w = mq_sub(u, v);
+                        asrc[a + j + t] = (ushort)
+                            mq_montymul(w, s);
+                    }
+                }
+                t = dt;
+                m = hm;
+            }
+
+            /*
+            * To complete the inverse NTT, we must now divide all values by
+            * n (the vector size). We thus need the inverse of n, i.e. we
+            * need to divide 1 by 2 logn times. But we also want it in
+            * Montgomery representation, i.e. we also want to multiply it
+            * by R = 2^16. In the common case, this should be a simple right
+            * shift. The loop below is generic and works also in corner cases;
+            * its computation time is negligible.
+            */
+            ni = R;
+            for (m = n; m > 1; m >>= 1) {
+                ni = mq_rshift1(ni);
+            }
+            for (m = 0; m < n; m ++) {
+                asrc[a + m] = (ushort)mq_montymul(asrc[a + m], ni);
+            }
+        }
+
+        /*
+        * Convert a polynomial (mod q) to Montgomery representation.
+        */
+        internal void mq_poly_tomonty(ushort[] fsrc, int f, uint logn)
+        {
+            int u, n;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                fsrc[f + u] = (ushort)mq_montymul(fsrc[f + u], R2);
+            }
+        }
+
+        /*
+        * Multiply two polynomials together (NTT representation, and using
+        * a Montgomery multiplication). Result f*g is written over f.
+        */
+        internal void mq_poly_montymul_ntt(ushort[] fsrc, int f, ushort[] gsrc, int g, uint logn)
+        {
+            int u, n;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                fsrc[f + u] = (ushort)mq_montymul(fsrc[f + u], gsrc[g + u]);
+            }
+        }
+
+        /*
+        * Subtract polynomial g from polynomial f.
+        */
+        internal void mq_poly_sub(ushort[] fsrc, int f, ushort[] gsrc, int g, uint logn)
+        {
+            int u, n;
+
+            n = (int)1 << (int)logn;
+            for (u = 0; u < n; u ++) {
+                fsrc[f + u] = (ushort)mq_sub(fsrc[f + u], gsrc[g + u]);
+            }
+        }
+
+        /* ===================================================================== */
+
+        internal void to_ntt_monty(ushort[] hsrc, int h, uint logn)
+        {
+            mq_NTT(hsrc, h, logn);
+            mq_poly_tomonty(hsrc, h, logn);
+        }
+
+        internal bool verify_raw(ushort[] c0src, int c0, short[] s2src, int s2,
+            ushort[] hsrc, int h, uint logn, ushort[] tmpsrc, int tmp)
+        {
+            int u, n;
+            int tt;
+
+            n = (int)1 << (int)logn;
+            tt = tmp;
+
+            /*
+            * Reduce s2 elements modulo q ([0..q-1] range).
+            */
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = (uint)s2src[s2 + u];
+                w += (uint)(Q & -(w >> 31));
+                tmpsrc[tt+u] = (ushort)w;
+            }
+
+            /*
+            * Compute -s1 = s2*h - c0 mod phi mod q (in tt[]).
+            */
+            mq_NTT(tmpsrc, tt, logn);
+            mq_poly_montymul_ntt(tmpsrc, tt, hsrc, h, logn);
+            mq_iNTT(tmpsrc, tt, logn);
+            mq_poly_sub(tmpsrc, tt, c0src, c0, logn);
+
+            /*
+            * Normalize -s1 elements into the [-q/2..q/2] range.
+            */
+            short[] shorttmp = new short[n];
+            for (u = 0; u < n; u ++) {
+                int w;
+
+                w = (int)tmpsrc[tt+u];
+                w -= (int)(Q & -(((Q >> 1) - (uint)w) >> 31));
+                tmpsrc[tt + u] = (ushort)w;
+                shorttmp[u] = (short)tmpsrc[tt + u];
+            }
+
+
+            /*
+            * Signature is valid if and only if the aggregate (-s1,s2) vector
+            * is short enough.
+            */
+            return this.common.is_short(shorttmp, 0, s2src, s2, logn);
+        }
+
+        internal int compute_public(ushort[] hsrc, int h,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, uint logn, ushort[] tmpsrc, int tmp)
+        {
+            int u, n;
+            int tt;
+
+            n = (int)1 << (int)logn;
+            tt = tmp;
+            for (u = 0; u < n; u ++) {
+                tmpsrc[tt+u] = (ushort)mq_conv_small(fsrc[f+u]);
+                hsrc[h+u] = (ushort)mq_conv_small(gsrc[g+u]);
+            }
+            mq_NTT(hsrc, h, logn);
+            mq_NTT(tmpsrc, tt, logn);
+            for (u = 0; u < n; u ++) {
+                if (tmpsrc[tt+u] == 0) {
+                    return 0;
+                }
+                hsrc[h+u] = (ushort)mq_div_12289(hsrc[h+u], tmpsrc[tt+u]);
+            }
+            mq_iNTT(hsrc, h, logn);
+            return 1;
+        }
+
+        internal int complete_private(sbyte[] Gsrc, int G,
+            sbyte[] fsrc, int f, sbyte[] gsrc, int g, sbyte[] Fsrc, int F,
+            uint logn, ushort[] tmpsrc, int tmp)
+        {
+            int u, n;
+            int t1, t2;
+
+            n = (int)1 << (int)logn;
+            t1 = tmp;
+            t2 = t1 + n;
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t1+u] = (ushort)mq_conv_small(gsrc[g+u]);
+                tmpsrc[t2+u] = (ushort)mq_conv_small(Fsrc[F+u]);
+            }
+            mq_NTT(tmpsrc, t1, logn);
+            mq_NTT(tmpsrc, t2, logn);
+            mq_poly_tomonty(tmpsrc, t1, logn);
+            mq_poly_montymul_ntt(tmpsrc, t1, tmpsrc, t2, logn);
+            for (u = 0; u < n; u ++) {
+                tmpsrc[t2+u] = (ushort)mq_conv_small(fsrc[f+u]);
+            }
+            mq_NTT(tmpsrc, t2, logn);
+            for (u = 0; u < n; u ++) {
+                if (tmpsrc[t2+u] == 0) {
+                    return 0;
+                }
+                tmpsrc[t1+u] = (ushort)mq_div_12289(tmpsrc[t1+u], tmpsrc[t2+u]);
+            }
+            mq_iNTT(tmpsrc, t1, logn);
+            for (u = 0; u < n; u ++) {
+                uint w;
+                int gi;
+
+                w = tmpsrc[t1+u];
+                w -= (uint)(Q & ~-((w - (Q >> 1)) >> 31));
+                //gi = *(int *)&w;
+                gi = (int)w;
+                if (gi < -127 || gi > +127) {
+                    return 0;
+                }
+                Gsrc[G+u] = (sbyte)gi;
+            }
+            return 1;
+        }
+
+        internal int is_invertible(
+            short[] s2src, int s2, uint logn, ushort[] tmpsrc, int tmp)
+        {
+            int u, n;
+            int tt;
+            uint r;
+
+            n = (int)1 << (int)logn;
+            tt = tmp;
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = (uint)s2src[s2 + u];
+                w += (uint)(Q & -(w >> 31));
+                tmpsrc[tt+u] = (ushort)w;
+            }
+            mq_NTT(tmpsrc, tt, logn);
+            r = 0;
+            for (u = 0; u < n; u ++) {
+                r |= (uint)(tmpsrc[tt+u] - 1);
+            }
+            return (int)(1u - (r >> 31));
+        }
+
+        internal int verify_recover(ushort[] hsrc, int h,
+            ushort[] c0src, int c0, short[] s1src, int s1, short[] s2src, int s2,
+            uint logn, ushort[] tmpsrc, int tmp)
+        {
+            int u, n;
+            int tt;
+            uint r;
+
+            n = (int)1 << (int)logn;
+
+            /*
+            * Reduce elements of s1 and s2 modulo q; then write s2 into tt[]
+            * and c0 - s1 into h[].
+            */
+            tt = tmp;
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = (uint)s2src[s2 + u];
+                w += (uint)(Q & -(w >> 31));
+                tmpsrc[tt+u] = (ushort)w;
+
+                w = (uint)s1src[s1+u];
+                w += (uint)(Q & -(w >> 31));
+                w = mq_sub(c0src[c0 + u], w);
+                hsrc[h+u] = (ushort)w;
+            }
+
+            /*
+            * Compute h = (c0 - s1) / s2. If one of the coefficients of s2
+            * is zero (in NTT representation) then the operation fails. We
+            * keep that information into a flag so that we do not deviate
+            * from strict constant-time processing; if all coefficients of
+            * s2 are non-zero, then the high bit of r will be zero.
+            */
+            mq_NTT(tmpsrc, tt, logn);
+            mq_NTT(hsrc, h, logn);
+            r = 0;
+            for (u = 0; u < n; u ++) {
+                r |= (uint)(tmpsrc[tt+u] - 1);
+                hsrc[h+u] = (ushort)mq_div_12289(hsrc[h+u], tmpsrc[tt+u]);
+            }
+            mq_iNTT(hsrc, h, logn);
+
+            /*
+            * Signature is acceptable if and only if it is short enough,
+            * and s2 was invertible mod phi mod q. The caller must still
+            * check that the rebuilt public key matches the expected
+            * value (e.g. through a hash).
+            */
+            r = ~r & (uint)-(this.common.is_short(s1src, s1, s2src, s2, logn) ? 1 : 0);
+            return (int)(r >> 31);
+        }
+
+        internal int count_nttzero(short[] sigsrc, int sig, uint logn, ushort[] tmpsrc, int tmp)
+        {
+            int s2;
+            int u, n;
+            uint r;
+
+            n = (int)1 << (int)logn;
+            s2 = tmp;
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = (uint)sigsrc[sig + u];
+                w += (uint)(Q & -(w >> 31));
+                tmpsrc[s2 + u] = (ushort)w;
+            }
+            mq_NTT(tmpsrc, s2, logn);
+            r = 0;
+            for (u = 0; u < n; u ++) {
+                uint w;
+
+                w = (uint)tmpsrc[s2 + u] - 1u;
+                r += (w >> 31);
+            }
+            return (int)r;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/SHAKE256.cs b/crypto/src/pqc/crypto/falcon/SHAKE256.cs
new file mode 100644
index 000000000..eb7c77e09
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/SHAKE256.cs
@@ -0,0 +1,569 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{   
+    class SHAKE256
+    {
+
+        /* 
+        * License from the reference C code (the code was copied then modified
+        * to function in C#):
+        * ==========================(LICENSE BEGIN)============================
+        *
+        * Copyright (c) 2017-2019  Falcon Project
+        *
+        * 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, including
+        * without limitation the rights to use, copy, modify, merge, publish,
+        * distribute, sublicense, and/or sell copies of the Software, and to
+        * permit persons to whom the Software is furnished to do so, subject to
+        * the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+        * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+        * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+        * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+        * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * ===========================(LICENSE END)=============================
+        */
+        
+        ulong[] A;
+        byte[] dubf;
+        ulong dptr;
+
+        ulong[] RC = {
+            0x0000000000000001, 0x0000000000008082,
+            0x800000000000808A, 0x8000000080008000,
+            0x000000000000808B, 0x0000000080000001,
+            0x8000000080008081, 0x8000000000008009,
+            0x000000000000008A, 0x0000000000000088,
+            0x0000000080008009, 0x000000008000000A,
+            0x000000008000808B, 0x800000000000008B,
+            0x8000000000008089, 0x8000000000008003,
+            0x8000000000008002, 0x8000000000000080,
+            0x000000000000800A, 0x800000008000000A,
+            0x8000000080008081, 0x8000000000008080,
+            0x0000000080000001, 0x8000000080008008
+        };
+
+        void process_block(ulong[] A) {
+            ulong t0, t1, t2, t3, t4;
+            ulong tt0, tt1, tt2, tt3;
+            ulong t, kt;
+            ulong c0, c1, c2, c3, c4, bnn;
+            int j;
+
+            /*
+            * Invert some words (alternate internal representation, which
+            * saves some operations).
+            */
+            A[ 1] = ~A[ 1];
+            A[ 2] = ~A[ 2];
+            A[ 8] = ~A[ 8];
+            A[12] = ~A[12];
+            A[17] = ~A[17];
+            A[20] = ~A[20];
+
+            /*
+            * Compute the 24 rounds. This loop is partially unrolled (each
+            * iteration computes two rounds).
+            */
+            for (j = 0; j < 24; j += 2) {
+
+                tt0 = A[ 1] ^ A[ 6];
+                tt1 = A[11] ^ A[16];
+                tt0 ^= A[21] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 4] ^ A[ 9];
+                tt3 = A[14] ^ A[19];
+                tt0 ^= A[24];
+                tt2 ^= tt3;
+                t0 = tt0 ^ tt2;
+
+                tt0 = A[ 2] ^ A[ 7];
+                tt1 = A[12] ^ A[17];
+                tt0 ^= A[22] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 0] ^ A[ 5];
+                tt3 = A[10] ^ A[15];
+                tt0 ^= A[20];
+                tt2 ^= tt3;
+                t1 = tt0 ^ tt2;
+
+                tt0 = A[ 3] ^ A[ 8];
+                tt1 = A[13] ^ A[18];
+                tt0 ^= A[23] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 1] ^ A[ 6];
+                tt3 = A[11] ^ A[16];
+                tt0 ^= A[21];
+                tt2 ^= tt3;
+                t2 = tt0 ^ tt2;
+
+                tt0 = A[ 4] ^ A[ 9];
+                tt1 = A[14] ^ A[19];
+                tt0 ^= A[24] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 2] ^ A[ 7];
+                tt3 = A[12] ^ A[17];
+                tt0 ^= A[22];
+                tt2 ^= tt3;
+                t3 = tt0 ^ tt2;
+
+                tt0 = A[ 0] ^ A[ 5];
+                tt1 = A[10] ^ A[15];
+                tt0 ^= A[20] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 3] ^ A[ 8];
+                tt3 = A[13] ^ A[18];
+                tt0 ^= A[23];
+                tt2 ^= tt3;
+                t4 = tt0 ^ tt2;
+
+                A[ 0] = A[ 0] ^ t0;
+                A[ 5] = A[ 5] ^ t0;
+                A[10] = A[10] ^ t0;
+                A[15] = A[15] ^ t0;
+                A[20] = A[20] ^ t0;
+                A[ 1] = A[ 1] ^ t1;
+                A[ 6] = A[ 6] ^ t1;
+                A[11] = A[11] ^ t1;
+                A[16] = A[16] ^ t1;
+                A[21] = A[21] ^ t1;
+                A[ 2] = A[ 2] ^ t2;
+                A[ 7] = A[ 7] ^ t2;
+                A[12] = A[12] ^ t2;
+                A[17] = A[17] ^ t2;
+                A[22] = A[22] ^ t2;
+                A[ 3] = A[ 3] ^ t3;
+                A[ 8] = A[ 8] ^ t3;
+                A[13] = A[13] ^ t3;
+                A[18] = A[18] ^ t3;
+                A[23] = A[23] ^ t3;
+                A[ 4] = A[ 4] ^ t4;
+                A[ 9] = A[ 9] ^ t4;
+                A[14] = A[14] ^ t4;
+                A[19] = A[19] ^ t4;
+                A[24] = A[24] ^ t4;
+                A[ 5] = (A[ 5] << 36) | (A[ 5] >> (64 - 36));
+                A[10] = (A[10] <<  3) | (A[10] >> (64 -  3));
+                A[15] = (A[15] << 41) | (A[15] >> (64 - 41));
+                A[20] = (A[20] << 18) | (A[20] >> (64 - 18));
+                A[ 1] = (A[ 1] <<  1) | (A[ 1] >> (64 -  1));
+                A[ 6] = (A[ 6] << 44) | (A[ 6] >> (64 - 44));
+                A[11] = (A[11] << 10) | (A[11] >> (64 - 10));
+                A[16] = (A[16] << 45) | (A[16] >> (64 - 45));
+                A[21] = (A[21] <<  2) | (A[21] >> (64 - 2));
+                A[ 2] = (A[ 2] << 62) | (A[ 2] >> (64 - 62));
+                A[ 7] = (A[ 7] <<  6) | (A[ 7] >> (64 -  6));
+                A[12] = (A[12] << 43) | (A[12] >> (64 - 43));
+                A[17] = (A[17] << 15) | (A[17] >> (64 - 15));
+                A[22] = (A[22] << 61) | (A[22] >> (64 - 61));
+                A[ 3] = (A[ 3] << 28) | (A[ 3] >> (64 - 28));
+                A[ 8] = (A[ 8] << 55) | (A[ 8] >> (64 - 55));
+                A[13] = (A[13] << 25) | (A[13] >> (64 - 25));
+                A[18] = (A[18] << 21) | (A[18] >> (64 - 21));
+                A[23] = (A[23] << 56) | (A[23] >> (64 - 56));
+                A[ 4] = (A[ 4] << 27) | (A[ 4] >> (64 - 27));
+                A[ 9] = (A[ 9] << 20) | (A[ 9] >> (64 - 20));
+                A[14] = (A[14] << 39) | (A[14] >> (64 - 39));
+                A[19] = (A[19] <<  8) | (A[19] >> (64 -  8));
+                A[24] = (A[24] << 14) | (A[24] >> (64 - 14));
+
+                bnn = ~A[12];
+                kt = A[ 6] | A[12];
+                c0 = A[ 0] ^ kt;
+                kt = bnn | A[18];
+                c1 = A[ 6] ^ kt;
+                kt = A[18] & A[24];
+                c2 = A[12] ^ kt;
+                kt = A[24] | A[ 0];
+                c3 = A[18] ^ kt;
+                kt = A[ 0] & A[ 6];
+                c4 = A[24] ^ kt;
+                A[ 0] = c0;
+                A[ 6] = c1;
+                A[12] = c2;
+                A[18] = c3;
+                A[24] = c4;
+                bnn = ~A[22];
+                kt = A[ 9] | A[10];
+                c0 = A[ 3] ^ kt;
+                kt = A[10] & A[16];
+                c1 = A[ 9] ^ kt;
+                kt = A[16] | bnn;
+                c2 = A[10] ^ kt;
+                kt = A[22] | A[ 3];
+                c3 = A[16] ^ kt;
+                kt = A[ 3] & A[ 9];
+                c4 = A[22] ^ kt;
+                A[ 3] = c0;
+                A[ 9] = c1;
+                A[10] = c2;
+                A[16] = c3;
+                A[22] = c4;
+                bnn = ~A[19];
+                kt = A[ 7] | A[13];
+                c0 = A[ 1] ^ kt;
+                kt = A[13] & A[19];
+                c1 = A[ 7] ^ kt;
+                kt = bnn & A[20];
+                c2 = A[13] ^ kt;
+                kt = A[20] | A[ 1];
+                c3 = bnn ^ kt;
+                kt = A[ 1] & A[ 7];
+                c4 = A[20] ^ kt;
+                A[ 1] = c0;
+                A[ 7] = c1;
+                A[13] = c2;
+                A[19] = c3;
+                A[20] = c4;
+                bnn = ~A[17];
+                kt = A[ 5] & A[11];
+                c0 = A[ 4] ^ kt;
+                kt = A[11] | A[17];
+                c1 = A[ 5] ^ kt;
+                kt = bnn | A[23];
+                c2 = A[11] ^ kt;
+                kt = A[23] & A[ 4];
+                c3 = bnn ^ kt;
+                kt = A[ 4] | A[ 5];
+                c4 = A[23] ^ kt;
+                A[ 4] = c0;
+                A[ 5] = c1;
+                A[11] = c2;
+                A[17] = c3;
+                A[23] = c4;
+                bnn = ~A[ 8];
+                kt = bnn & A[14];
+                c0 = A[ 2] ^ kt;
+                kt = A[14] | A[15];
+                c1 = bnn ^ kt;
+                kt = A[15] & A[21];
+                c2 = A[14] ^ kt;
+                kt = A[21] | A[ 2];
+                c3 = A[15] ^ kt;
+                kt = A[ 2] & A[ 8];
+                c4 = A[21] ^ kt;
+                A[ 2] = c0;
+                A[ 8] = c1;
+                A[14] = c2;
+                A[15] = c3;
+                A[21] = c4;
+                A[ 0] = A[ 0] ^ RC[j + 0];
+
+                tt0 = A[ 6] ^ A[ 9];
+                tt1 = A[ 7] ^ A[ 5];
+                tt0 ^= A[ 8] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[24] ^ A[22];
+                tt3 = A[20] ^ A[23];
+                tt0 ^= A[21];
+                tt2 ^= tt3;
+                t0 = tt0 ^ tt2;
+
+                tt0 = A[12] ^ A[10];
+                tt1 = A[13] ^ A[11];
+                tt0 ^= A[14] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 0] ^ A[ 3];
+                tt3 = A[ 1] ^ A[ 4];
+                tt0 ^= A[ 2];
+                tt2 ^= tt3;
+                t1 = tt0 ^ tt2;
+
+                tt0 = A[18] ^ A[16];
+                tt1 = A[19] ^ A[17];
+                tt0 ^= A[15] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[ 6] ^ A[ 9];
+                tt3 = A[ 7] ^ A[ 5];
+                tt0 ^= A[ 8];
+                tt2 ^= tt3;
+                t2 = tt0 ^ tt2;
+
+                tt0 = A[24] ^ A[22];
+                tt1 = A[20] ^ A[23];
+                tt0 ^= A[21] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[12] ^ A[10];
+                tt3 = A[13] ^ A[11];
+                tt0 ^= A[14];
+                tt2 ^= tt3;
+                t3 = tt0 ^ tt2;
+
+                tt0 = A[ 0] ^ A[ 3];
+                tt1 = A[ 1] ^ A[ 4];
+                tt0 ^= A[ 2] ^ tt1;
+                tt0 = (tt0 << 1) | (tt0 >> 63);
+                tt2 = A[18] ^ A[16];
+                tt3 = A[19] ^ A[17];
+                tt0 ^= A[15];
+                tt2 ^= tt3;
+                t4 = tt0 ^ tt2;
+
+                A[ 0] = A[ 0] ^ t0;
+                A[ 3] = A[ 3] ^ t0;
+                A[ 1] = A[ 1] ^ t0;
+                A[ 4] = A[ 4] ^ t0;
+                A[ 2] = A[ 2] ^ t0;
+                A[ 6] = A[ 6] ^ t1;
+                A[ 9] = A[ 9] ^ t1;
+                A[ 7] = A[ 7] ^ t1;
+                A[ 5] = A[ 5] ^ t1;
+                A[ 8] = A[ 8] ^ t1;
+                A[12] = A[12] ^ t2;
+                A[10] = A[10] ^ t2;
+                A[13] = A[13] ^ t2;
+                A[11] = A[11] ^ t2;
+                A[14] = A[14] ^ t2;
+                A[18] = A[18] ^ t3;
+                A[16] = A[16] ^ t3;
+                A[19] = A[19] ^ t3;
+                A[17] = A[17] ^ t3;
+                A[15] = A[15] ^ t3;
+                A[24] = A[24] ^ t4;
+                A[22] = A[22] ^ t4;
+                A[20] = A[20] ^ t4;
+                A[23] = A[23] ^ t4;
+                A[21] = A[21] ^ t4;
+                A[ 3] = (A[ 3] << 36) | (A[ 3] >> (64 - 36));
+                A[ 1] = (A[ 1] <<  3) | (A[ 1] >> (64 -  3));
+                A[ 4] = (A[ 4] << 41) | (A[ 4] >> (64 - 41));
+                A[ 2] = (A[ 2] << 18) | (A[ 2] >> (64 - 18));
+                A[ 6] = (A[ 6] <<  1) | (A[ 6] >> (64 -  1));
+                A[ 9] = (A[ 9] << 44) | (A[ 9] >> (64 - 44));
+                A[ 7] = (A[ 7] << 10) | (A[ 7] >> (64 - 10));
+                A[ 5] = (A[ 5] << 45) | (A[ 5] >> (64 - 45));
+                A[ 8] = (A[ 8] <<  2) | (A[ 8] >> (64 - 2));
+                A[12] = (A[12] << 62) | (A[12] >> (64 - 62));
+                A[10] = (A[10] <<  6) | (A[10] >> (64 -  6));
+                A[13] = (A[13] << 43) | (A[13] >> (64 - 43));
+                A[11] = (A[11] << 15) | (A[11] >> (64 - 15));
+                A[14] = (A[14] << 61) | (A[14] >> (64 - 61));
+                A[18] = (A[18] << 28) | (A[18] >> (64 - 28));
+                A[16] = (A[16] << 55) | (A[16] >> (64 - 55));
+                A[19] = (A[19] << 25) | (A[19] >> (64 - 25));
+                A[17] = (A[17] << 21) | (A[17] >> (64 - 21));
+                A[15] = (A[15] << 56) | (A[15] >> (64 - 56));
+                A[24] = (A[24] << 27) | (A[24] >> (64 - 27));
+                A[22] = (A[22] << 20) | (A[22] >> (64 - 20));
+                A[20] = (A[20] << 39) | (A[20] >> (64 - 39));
+                A[23] = (A[23] <<  8) | (A[23] >> (64 -  8));
+                A[21] = (A[21] << 14) | (A[21] >> (64 - 14));
+
+                bnn = ~A[13];
+                kt = A[ 9] | A[13];
+                c0 = A[ 0] ^ kt;
+                kt = bnn | A[17];
+                c1 = A[ 9] ^ kt;
+                kt = A[17] & A[21];
+                c2 = A[13] ^ kt;
+                kt = A[21] | A[ 0];
+                c3 = A[17] ^ kt;
+                kt = A[ 0] & A[ 9];
+                c4 = A[21] ^ kt;
+                A[ 0] = c0;
+                A[ 9] = c1;
+                A[13] = c2;
+                A[17] = c3;
+                A[21] = c4;
+                bnn = ~A[14];
+                kt = A[22] | A[ 1];
+                c0 = A[18] ^ kt;
+                kt = A[ 1] & A[ 5];
+                c1 = A[22] ^ kt;
+                kt = A[ 5] | bnn;
+                c2 = A[ 1] ^ kt;
+                kt = A[14] | A[18];
+                c3 = A[ 5] ^ kt;
+                kt = A[18] & A[22];
+                c4 = A[14] ^ kt;
+                A[18] = c0;
+                A[22] = c1;
+                A[ 1] = c2;
+                A[ 5] = c3;
+                A[14] = c4;
+                bnn = ~A[23];
+                kt = A[10] | A[19];
+                c0 = A[ 6] ^ kt;
+                kt = A[19] & A[23];
+                c1 = A[10] ^ kt;
+                kt = bnn & A[ 2];
+                c2 = A[19] ^ kt;
+                kt = A[ 2] | A[ 6];
+                c3 = bnn ^ kt;
+                kt = A[ 6] & A[10];
+                c4 = A[ 2] ^ kt;
+                A[ 6] = c0;
+                A[10] = c1;
+                A[19] = c2;
+                A[23] = c3;
+                A[ 2] = c4;
+                bnn = ~A[11];
+                kt = A[ 3] & A[ 7];
+                c0 = A[24] ^ kt;
+                kt = A[ 7] | A[11];
+                c1 = A[ 3] ^ kt;
+                kt = bnn | A[15];
+                c2 = A[ 7] ^ kt;
+                kt = A[15] & A[24];
+                c3 = bnn ^ kt;
+                kt = A[24] | A[ 3];
+                c4 = A[15] ^ kt;
+                A[24] = c0;
+                A[ 3] = c1;
+                A[ 7] = c2;
+                A[11] = c3;
+                A[15] = c4;
+                bnn = ~A[16];
+                kt = bnn & A[20];
+                c0 = A[12] ^ kt;
+                kt = A[20] | A[ 4];
+                c1 = bnn ^ kt;
+                kt = A[ 4] & A[ 8];
+                c2 = A[20] ^ kt;
+                kt = A[ 8] | A[12];
+                c3 = A[ 4] ^ kt;
+                kt = A[12] & A[16];
+                c4 = A[ 8] ^ kt;
+                A[12] = c0;
+                A[16] = c1;
+                A[20] = c2;
+                A[ 4] = c3;
+                A[ 8] = c4;
+                A[ 0] = A[ 0] ^ RC[j + 1];
+                t = A[ 5];
+                A[ 5] = A[18];
+                A[18] = A[11];
+                A[11] = A[10];
+                A[10] = A[ 6];
+                A[ 6] = A[22];
+                A[22] = A[20];
+                A[20] = A[12];
+                A[12] = A[19];
+                A[19] = A[15];
+                A[15] = A[24];
+                A[24] = A[ 8];
+                A[ 8] = t;
+                t = A[ 1];
+                A[ 1] = A[ 9];
+                A[ 9] = A[14];
+                A[14] = A[ 2];
+                A[ 2] = A[13];
+                A[13] = A[23];
+                A[23] = A[ 4];
+                A[ 4] = A[21];
+                A[21] = A[16];
+                A[16] = A[ 3];
+                A[ 3] = A[17];
+                A[17] = A[ 7];
+                A[ 7] = t;
+            }
+
+                /*
+                * Invert some words back to normal representation.
+                */
+                A[ 1] = ~A[ 1];
+                A[ 2] = ~A[ 2];
+                A[ 8] = ~A[ 8];
+                A[12] = ~A[12];
+                A[17] = ~A[17];
+                A[20] = ~A[20];
+        }
+        
+        internal void i_shake256_init()
+        {
+            this.dptr = 0;
+
+            /*
+            * Representation of an all-ones uint64_t is the same regardless
+            * of local endianness.
+            */
+            // memset(this.A, 0, sizeof this.A);
+            this.A = new ulong[25];
+            this.dubf = new byte[200];
+
+            for (int i = 0; i < this.A.Length; i++) {
+                this.A[i] = 0;
+            }
+        }
+
+        internal void i_shake256_inject(byte[] insrc, int inarray, int len)
+        {
+            ulong dptr;
+
+            dptr = this.dptr;
+            while (len > 0) {
+                int clen, u;
+
+                clen = 136 - (int)dptr;
+                if (clen > len) {
+                    clen = len;
+                }
+                for (u = 0; u < clen; u ++) {
+                    int v;
+
+                    v = u + (int)dptr;
+                    this.A[v >> 3] ^= (ulong)insrc[inarray + u] << ((v & 7) << 3);
+                }
+                dptr += (ulong)clen;
+                inarray += clen;
+                len -= clen;
+                if (dptr == 136) {
+                    process_block(this.A);
+                    dptr = 0;
+                }
+            }
+            this.dptr = dptr;
+        }
+
+        internal void i_shake256_flip()
+        {
+            /*
+            * We apply padding and pre-XOR the value into the state. We
+            * set dptr to the end of the buffer, so that first call to
+            * shake_extract() will process the block.
+            */
+            uint v;
+
+            v = (uint)this.dptr;
+            this.A[v >> 3] ^= (ulong)0x1F << (int)((v & 7) << 3);
+            this.A[16] ^= (ulong)0x80 << 56;
+            this.dptr = 136;
+        }
+
+        internal void i_shake256_extract(byte[] outsrc, int outarray, int len)
+        {
+            ulong dptr;
+
+            dptr = this.dptr;
+            while (len > 0) {
+                int clen;
+
+                if (dptr == 136) {
+                    process_block(this.A);
+                    dptr = 0;
+                }
+                clen = 136 - (int)dptr;
+                if (clen > len) {
+                    clen = len;
+                }
+                len -= clen;
+                while (clen -- > 0) {
+                    outsrc[outarray ++] = (byte)(this.A[dptr >> 3] >> (int)((dptr & 7) << 3));
+                    dptr ++;
+                }
+            }
+            this.dptr = dptr;
+        }
+        
+    }
+}
diff --git a/crypto/src/pqc/crypto/falcon/SamplerZ.cs b/crypto/src/pqc/crypto/falcon/SamplerZ.cs
new file mode 100644
index 000000000..b43cd2c38
--- /dev/null
+++ b/crypto/src/pqc/crypto/falcon/SamplerZ.cs
@@ -0,0 +1,229 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Falcon
+{
+    class SamplerZ
+    {
+        FalconRNG p;
+        FalconFPR sigma_min;
+        FPREngine fpre;
+
+        internal SamplerZ(FalconRNG p, FalconFPR sigma_min, FPREngine fpre) {
+            this.p = p;
+            this.sigma_min = sigma_min;
+            this.fpre = fpre;
+        }
+
+        internal int Sample(FalconFPR mu, FalconFPR isigma) {
+            return this.sampler(mu, isigma);
+        }
+
+        /*
+        * Sample an integer value along a half-gaussian distribution centered
+        * on zero and standard deviation 1.8205, with a precision of 72 bits.
+        */
+        int gaussian0_sampler(FalconRNG p)
+        {
+
+            uint[] dist = {
+                10745844u,  3068844u,  3741698u,
+                5559083u,  1580863u,  8248194u,
+                2260429u, 13669192u,  2736639u,
+                708981u,  4421575u, 10046180u,
+                169348u,  7122675u,  4136815u,
+                30538u, 13063405u,  7650655u,
+                    4132u, 14505003u,  7826148u,
+                    417u, 16768101u, 11363290u,
+                    31u,  8444042u,  8086568u,
+                    1u, 12844466u,   265321u,
+                    0u,  1232676u, 13644283u,
+                    0u,    38047u,  9111839u,
+                    0u,      870u,  6138264u,
+                    0u,       14u, 12545723u,
+                    0u,        0u,  3104126u,
+                    0u,        0u,    28824u,
+                    0u,        0u,      198u,
+                    0u,        0u,        1u
+            };
+
+            uint v0, v1, v2, hi;
+            ulong lo;
+            int u;
+            int z;
+
+            /*
+            * Get a random 72-bit value, into three 24-bit limbs v0..v2.
+            */
+            lo = p.prng_get_u64();
+            hi = p.prng_get_u8();
+            v0 = (uint)lo & 0xFFFFFF;
+            v1 = (uint)(lo >> 24) & 0xFFFFFF;
+            v2 = (uint)(lo >> 48) | (hi << 16);
+
+            /*
+            * Sampled value is z, such that v0..v2 is lower than the first
+            * z elements of the table.
+            */
+            z = 0;
+            for (u = 0; u < dist.Length; u += 3) {
+                uint w0, w1, w2, cc;
+
+                w0 = dist[u + 2];
+                w1 = dist[u + 1];
+                w2 = dist[u + 0];
+                cc = (v0 - w0) >> 31;
+                cc = (v1 - w1 - cc) >> 31;
+                cc = (v2 - w2 - cc) >> 31;
+                z += (int)cc;
+            }
+            return z;
+
+        }
+
+        /*
+        * Sample a bit with probability exp(-x) for some x >= 0.
+        */
+        int BerExp(FalconRNG p, FalconFPR x, FalconFPR ccs)
+        {
+            int s, i;
+            FalconFPR r;
+            uint sw, w;
+            ulong z;
+
+            /*
+            * Reduce x modulo log(2): x = s*log(2) + r, with s an integer,
+            * and 0 <= r < log(2). Since x >= 0, we can use this.fpre.fpr_trunc().
+            */
+            s = (int)this.fpre.fpr_trunc(this.fpre.fpr_mul(x, this.fpre.fpr_inv_log2));
+            r = this.fpre.fpr_sub(x, this.fpre.fpr_mul(this.fpre.fpr_of(s), this.fpre.fpr_log2));
+
+            /*
+            * It may happen (quite rarely) that s >= 64; if sigma = 1.2
+            * (the minimum value for sigma), r = 0 and b = 1, then we get
+            * s >= 64 if the half-Gaussian produced a z >= 13, which happens
+            * with probability about 0.000000000230383991, which is
+            * approximatively equal to 2^(-32). In any case, if s >= 64,
+            * then BerExp will be non-zero with probability less than
+            * 2^(-64), so we can simply saturate s at 63.
+            */
+            sw = (uint)s;
+            sw ^= (uint)((sw ^ 63) & -((63 - sw) >> 31));
+            s = (int)sw;
+
+            /*
+            * Compute exp(-r); we know that 0 <= r < log(2) at this point, so
+            * we can use this.fpre.fpr_expm_p63(), which yields a result scaled to 2^63.
+            * We scale it up to 2^64, then right-shift it by s bits because
+            * we really want exp(-x) = 2^(-s)*exp(-r).
+            *
+            * The "-1" operation makes sure that the value fits on 64 bits
+            * (i.e. if r = 0, we may get 2^64, and we prefer 2^64-1 in that
+            * case). The bias is negligible since this.fpre.fpr_expm_p63() only computes
+            * with 51 bits of precision or so.
+            */
+            z = ((this.fpre.fpr_expm_p63(r, ccs) << 1) - 1) >> s;
+
+            /*
+            * Sample a bit with probability exp(-x). Since x = s*log(2) + r,
+            * exp(-x) = 2^-s * exp(-r), we compare lazily exp(-x) with the
+            * PRNG output to limit its consumption, the sign of the difference
+            * yields the expected result.
+            */
+            i = 64;
+            do {
+                i -= 8;
+                w = p.prng_get_u8() - ((uint)(z >> i) & 0xFF);
+            } while (w == 0 && i > 0);
+            return (int)(w >> 31);
+        }
+
+        /*
+        * The sampler produces a random integer that follows a discrete Gaussian
+        * distribution, centered on mu, and with standard deviation sigma. The
+        * provided parameter isigma is equal to 1/sigma.
+        *
+        * The value of sigma MUST lie between 1 and 2 (i.e. isigma lies between
+        * 0.5 and 1); in Falcon, sigma should always be between 1.2 and 1.9.
+        */
+        int sampler(FalconFPR mu, FalconFPR isigma)
+        {
+            int s;
+            FalconFPR r, dss, ccs;
+
+            /*
+            * Center is mu. We compute mu = s + r where s is an integer
+            * and 0 <= r < 1.
+            */
+            s = (int)this.fpre.fpr_floor(mu);
+            r = this.fpre.fpr_sub(mu, this.fpre.fpr_of(s));
+
+            /*
+            * dss = 1/(2*sigma^2) = 0.5*(isigma^2).
+            */
+            dss = this.fpre.fpr_half(this.fpre.fpr_sqr(isigma));
+
+            /*
+            * ccs = sigma_min / sigma = sigma_min * isigma.
+            */
+            ccs = this.fpre.fpr_mul(isigma, this.sigma_min);
+
+            /*
+            * We now need to sample on center r.
+            */
+            for (;;) {
+                int z0, z, b;
+                FalconFPR x;
+
+                /*
+                * Sample z for a Gaussian distribution. Then get a
+                * random bit b to turn the sampling into a bimodal
+                * distribution: if b = 1, we use z+1, otherwise we
+                * use -z. We thus have two situations:
+                *
+                *  - b = 1: z >= 1 and sampled against a Gaussian
+                *    centered on 1.
+                *  - b = 0: z <= 0 and sampled against a Gaussian
+                *    centered on 0.
+                */
+                z0 = gaussian0_sampler(this.p);
+                b = (int)this.p.prng_get_u8() & 1;
+                z = b + ((b << 1) - 1) * z0;
+
+                /*
+                * Rejection sampling. We want a Gaussian centered on r;
+                * but we sampled against a Gaussian centered on b (0 or
+                * 1). But we know that z is always in the range where
+                * our sampling distribution is greater than the Gaussian
+                * distribution, so rejection works.
+                *
+                * We got z with distribution:
+                *    G(z) = exp(-((z-b)^2)/(2*sigma0^2))
+                * We target distribution:
+                *    S(z) = exp(-((z-r)^2)/(2*sigma^2))
+                * Rejection sampling works by keeping the value z with
+                * probability S(z)/G(z), and starting again otherwise.
+                * This requires S(z) <= G(z), which is the case here.
+                * Thus, we simply need to keep our z with probability:
+                *    P = exp(-x)
+                * where:
+                *    x = ((z-r)^2)/(2*sigma^2) - ((z-b)^2)/(2*sigma0^2)
+                *
+                * Here, we scale up the Bernouilli distribution, which
+                * makes rejection more probable, but makes rejection
+                * rate sufficiently decorrelated from the Gaussian
+                * center and standard deviation that the whole sampler
+                * can be said to be constant-time.
+                */
+                x = this.fpre.fpr_mul(this.fpre.fpr_sqr(this.fpre.fpr_sub(this.fpre.fpr_of(z), r)), dss);
+                x = this.fpre.fpr_sub(x, this.fpre.fpr_mul(this.fpre.fpr_of(z0 * z0), this.fpre.fpr_inv_2sqrsigma0));
+                if (BerExp(this.p, x, ccs) != 0) {
+                    /*
+                    * Rejection sampling was centered on r, but the
+                    * actual center is mu = s + r.
+                    */
+                    return s + z;
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/frodo/FrodoEngine.cs b/crypto/src/pqc/crypto/frodo/FrodoEngine.cs
index 020f721ea..6aa3c8d89 100644
--- a/crypto/src/pqc/crypto/frodo/FrodoEngine.cs
+++ b/crypto/src/pqc/crypto/frodo/FrodoEngine.cs
@@ -10,7 +10,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
     public class FrodoEngine
     {
         // constant parameters
-        private static int nbar = 8;
+        internal static int nbar = 8;
         private static int mbar = 8;
         private static int len_seedA = 128;
         private static int len_z = 128;
@@ -127,24 +127,20 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             return res;
         }
 
-        private short[] MatrixMul(short[] X, int Xrow, int Xcol, short[] Y, int Yrow, int Ycol)
+        private short[] MatrixMul(short[] X, int Xrow, int Xcol, short[] Y, int Ycol)
         {
+            int qMask = q - 1;
             short[] res = new short[Xrow * Ycol];
             for (int i = 0; i < Xrow; i++)
             {
                 for (int j = 0; j < Ycol; j++)
                 {
+                    int accum = 0;
                     for (int k = 0; k < Xcol; k++)
                     {
-                        short a_test =  (short) (res[i * Ycol + j] & 0xffff);
-                        short b_test =  (short) (X[i * Xcol + k] & 0xffff);
-                        short c_test =  (short) (Y[k * Ycol + j] & 0xffff);
-                        short bc_test = (short) (((X[i * Xcol + k] & 0xffff) * (Y[k * Ycol + j] & 0xffff)) & 0xffff);
-                        short abc_test = (short) (((res[i * Ycol + j] & 0xffff) + ((X[i * Xcol + k] & 0xffff) * (Y[k * Ycol + j] & 0xffff)) & 0xffff));
-                        res[i * Ycol + j] = (short) ((res[i * Ycol + j] & 0xffff) +
-                            ((X[i * Xcol + k] & 0xffff) * (Y[k * Ycol + j] & 0xffff)) & 0xffff);
+                        accum += X[i * Xcol + k] * Y[k * Ycol + j];
                     }
-                    res[i * Ycol + j] = (short) (((res[i * Ycol + j] & 0xffff) % q) & 0xffff);
+                    res[i * Ycol + j] = (short)(accum & qMask);
                 }
             }
 
@@ -153,11 +149,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 
         private short[] MatrixAdd(short[] X, short[] Y, int n1, int m1)
         {
+            int qMask = q - 1;
             short[] res = new short[n1 * m1];
             for (int i = 0; i < n1; i++)
-            for (int j = 0; j < m1; j++)
-                res[i * m1 + j] = (short) (((X[i * m1 + j] & 0xffff) + (Y[i * m1 + j] & 0xffff)) % q);
-
+            {
+                for (int j = 0; j < m1; j++)
+                {
+                    res[i * m1 + j] = (short)((X[i * m1 + j] + Y[i * m1 + j]) & qMask);
+                }
+            }
             return res;
         }
 
@@ -223,7 +223,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             // 2. Generate pseudorandom seed seedA = SHAKE(z, len_seedA) (length in bits)
             byte[] seedA = new byte[len_seedA_bytes];
             digest.BlockUpdate(z, 0, z.Length);
-            ((IXof) digest).DoFinal(seedA, 0, seedA.Length);
+            ((IXof) digest).OutputFinal(seedA, 0, seedA.Length);
 
             // 3. A = Frodo.Gen(seedA)
             short[] A = gen.GenMatrix(seedA);
@@ -233,7 +233,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 
             digest.Update((byte) 0x5f);
             digest.BlockUpdate(seedSE, 0, seedSE.Length);
-            ((IXof) digest).DoFinal(rbytes, 0, rbytes.Length);
+            ((IXof) digest).OutputFinal(rbytes, 0, rbytes.Length);
 
             short[] r = new short[2 * n * nbar];
             for (int i = 0; i < r.Length; i++)
@@ -247,7 +247,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             short[] E = SampleMatrix(r, n * nbar, n, nbar);
 
             // 7. B = A * S + E
-            short[] B = MatrixAdd(MatrixMul(A, n, n, S, n, nbar), E, n, nbar);
+            short[] B = MatrixAdd(MatrixMul(A, n, n, S, nbar), E, n, nbar);
 
             // 8. b = Pack(B)
             byte[] b = FrodoPack(B);
@@ -258,7 +258,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 
             byte[] pkh = new byte[len_pkh_bytes];
             digest.BlockUpdate(pk, 0, pk.Length);
-            ((IXof) digest).DoFinal(pkh, 0, pkh.Length);
+            ((IXof) digest).OutputFinal(pkh, 0, pkh.Length);
 
             //10. sk = (s || seedA || b, S^T, pkh)
             Array.Copy(Arrays.Concatenate(s, pk), 0,
@@ -377,13 +377,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             // 2. pkh = SHAKE(pk, len_pkh)
             byte[] pkh = new byte[len_pkh_bytes];
             digest.BlockUpdate(pk, 0, len_pk_bytes);
-            ((IXof) digest).DoFinal(pkh, 0, len_pkh_bytes);
+            ((IXof) digest).OutputFinal(pkh, 0, len_pkh_bytes);
 
             // 3. seedSE || k = SHAKE(pkh || mu, len_seedSE + len_k) (length in bits)
             byte[] seedSE_k = new byte[len_seedSE + len_k];
             digest.BlockUpdate(pkh, 0, len_pkh_bytes);
             digest.BlockUpdate(mu, 0, len_mu_bytes);
-            ((IXof) digest).DoFinal(seedSE_k, 0, len_seedSE_bytes + len_k_bytes);
+            ((IXof) digest).OutputFinal(seedSE_k, 0, len_seedSE_bytes + len_k_bytes);
 
             byte[] seedSE = Arrays.CopyOfRange(seedSE_k, 0, len_seedSE_bytes);
             byte[] k = Arrays.CopyOfRange(seedSE_k, len_seedSE_bytes, len_seedSE_bytes + len_k_bytes);
@@ -392,7 +392,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             byte[] rbytes = new byte[(2 * mbar * n + mbar * nbar) * len_chi_bytes];
             digest.Update((byte) 0x96);
             digest.BlockUpdate(seedSE, 0, seedSE.Length);
-            ((IXof) digest).DoFinal(rbytes, 0, rbytes.Length);
+            ((IXof) digest).OutputFinal(rbytes, 0, rbytes.Length);
 
             short[] r = new short[rbytes.Length / 2];
             for (int i = 0; i < r.Length; i++)
@@ -408,7 +408,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             short[] A = gen.GenMatrix(seedA);
 
             // 8. B' = S' A + E'
-            short[] Bprime = MatrixAdd(MatrixMul(Sprime, mbar, n, A, n, n), Eprime, mbar, n);
+            short[] Bprime = MatrixAdd(MatrixMul(Sprime, mbar, n, A, n), Eprime, mbar, n);
 
             // 9. c1 = Frodo.Pack(B')
             byte[] c1 = FrodoPack(Bprime);
@@ -421,7 +421,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 
 
             // 12. V = S' B + E''
-            short[] V = MatrixAdd(MatrixMul(Sprime, mbar, n, B, n, nbar), Eprimeprime, mbar, nbar);
+            short[] V = MatrixAdd(MatrixMul(Sprime, mbar, n, B, nbar), Eprimeprime, mbar, nbar);
 
             // 13. C = V + Frodo.Encode(mu)
             short[] EncodedMU = Encode(mu);
@@ -436,16 +436,20 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             digest.BlockUpdate(c1, 0, c1.Length);
             digest.BlockUpdate(c2, 0, c2.Length);
             digest.BlockUpdate(k, 0, len_k_bytes);
-            ((IXof) digest).DoFinal(ss, 0, len_s_bytes);
+            ((IXof) digest).OutputFinal(ss, 0, len_s_bytes);
         }
 
         private short[] MatrixSub(short[] X, short[] Y, int n1, int n2)
         {
+            int qMask = q - 1;
             short[] res = new short[n1 * n2];
             for (int i = 0; i < n1; i++)
-            for (int j = 0; j < n2; j++)
-                res[i * n2 + j] = (short) ((((X[i * n2 + j]) - (Y[i * n2 + j])) & 0xffff) % q);
-
+            {
+                for (int j = 0; j < n2; j++)
+                {
+                    res[i * n2 + j] = (short)((X[i * n2 + j] - Y[i * n2 + j]) & qMask);
+                }
+            }
             return res;
         }
 
@@ -556,7 +560,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             short[] C = FrodoUnpack(c2, mbar, nbar);
 
             // 3. M = C - B' S
-            short[] BprimeS = MatrixMul(Bprime, mbar, n, S, n, nbar);
+            short[] BprimeS = MatrixMul(Bprime, mbar, n, S, nbar);
             short[] M = MatrixSub(C, BprimeS, mbar, nbar);
 
             // 4. mu' = Frodo.Decode(M)
@@ -568,7 +572,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             byte[] seedSEprime_kprime = new byte[len_seedSE_bytes + len_k_bytes];
             digest.BlockUpdate(pkh, 0, len_pkh_bytes);
             digest.BlockUpdate(muprime, 0, len_mu_bytes);
-            ((IXof) digest).DoFinal(seedSEprime_kprime, 0, len_seedSE_bytes + len_k_bytes);
+            ((IXof) digest).OutputFinal(seedSEprime_kprime, 0, len_seedSE_bytes + len_k_bytes);
 
             byte[] kprime = Arrays.CopyOfRange(seedSEprime_kprime, len_seedSE_bytes, len_seedSE_bytes + len_k_bytes);
 
@@ -576,7 +580,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             byte[] rbytes = new byte[(2 * mbar * n + mbar * mbar) * len_chi_bytes];
             digest.Update((byte) 0x96);
             digest.BlockUpdate(seedSEprime_kprime, 0, len_seedSE_bytes);
-            ((IXof) digest).DoFinal(rbytes, 0, rbytes.Length);
+            ((IXof) digest).OutputFinal(rbytes, 0, rbytes.Length);
 
             short[] r = new short[2 * mbar * n + mbar * nbar];
             for (int i = 0; i < r.Length; i++)
@@ -594,7 +598,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             short[] A = gen.GenMatrix(seedA);
 
             // 11. B'' = S' A + E'
-            short[] Bprimeprime = MatrixAdd(MatrixMul(Sprime, mbar, n, A, n, n), Eprime, mbar, n);
+            short[] Bprimeprime = MatrixAdd(MatrixMul(Sprime, mbar, n, A, n), Eprime, mbar, n);
 
             // 12. E'' = Frodo.SampleMatrix(r[2*mbar*n .. 2*mbar*n + mbar*nbar-1], mbar, n)
             short[] Eprimeprime = SampleMatrix(r, 2 * mbar * n, mbar, nbar);
@@ -603,7 +607,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             short[] B = FrodoUnpack(b, n, nbar);
 
             // 14. V = S' B + E''
-            short[] V = MatrixAdd(MatrixMul(Sprime, mbar, n, B, n, nbar), Eprimeprime, mbar, nbar);
+            short[] V = MatrixAdd(MatrixMul(Sprime, mbar, n, B, nbar), Eprimeprime, mbar, nbar);
 
             // 15. C' = V + Frodo.Encode(muprime)
             short[] Cprime = MatrixAdd(V, Encode(muprime), mbar, nbar);
@@ -620,7 +624,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             digest.BlockUpdate(c1, 0, c1.Length);
             digest.BlockUpdate(c2, 0, c2.Length);
             digest.BlockUpdate(kbar, 0, kbar.Length);
-            ((IXof) digest).DoFinal(ss, 0, len_ss_bytes);
+            ((IXof) digest).OutputFinal(ss, 0, len_ss_bytes);
         }
 
     }
diff --git a/crypto/src/pqc/crypto/frodo/FrodoKEMExtractor.cs b/crypto/src/pqc/crypto/frodo/FrodoKEMExtractor.cs
index 475879a8d..055fe3bc6 100644
--- a/crypto/src/pqc/crypto/frodo/FrodoKEMExtractor.cs
+++ b/crypto/src/pqc/crypto/frodo/FrodoKEMExtractor.cs
@@ -29,9 +29,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             return session_key;
         }
 
-        public int GetInputSize()
-        {
-            return engine.CipherTextSize;
-        }
+        public int EncapsulationLength => (int)engine.CipherTextSize;
     }
 }
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/frodo/FrodoMatrixGenerator.cs b/crypto/src/pqc/crypto/frodo/FrodoMatrixGenerator.cs
index ffd7727b3..ae1c2320e 100644
--- a/crypto/src/pqc/crypto/frodo/FrodoMatrixGenerator.cs
+++ b/crypto/src/pqc/crypto/frodo/FrodoMatrixGenerator.cs
@@ -1,12 +1,9 @@
-
-
 using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
-using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 {
@@ -35,95 +32,92 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             {
                 short[] A = new short[n * n];
                 ushort i, j;
-                byte[] b, tmp = new byte[(16 * n) / 8];
-                byte[] temp = new byte[2];
+                byte[] tmp = new byte[(16 * n) / 8];
+                byte[] b = new byte[2 + seedA.Length];
+                Array.Copy(seedA, 0, b, 2, seedA.Length);
+                uint qMask32 = ((uint)(q - 1) << 16) | (ushort)(q - 1);
+
+                IXof digest = new ShakeDigest(128);
+
                 for (i = 0; i < n; i++)
                 {
                     // 1. b = i || seedA in {0,1}^{16 + len_seedA}, where i is encoded as a 16-bit integer in little-endian byte order
-                    Pack.UInt16_To_LE(i, temp);
-                    b = Arrays.Concatenate(temp, seedA);
+                    Pack.UInt16_To_LE(i, b);
 
                     // 2. c_{i,0} || c_{i,1} || ... || c_{i,n-1} = SHAKE128(b, 16n) (length in bits) where each c_{i,j} is parsed as a 16-bit integer in little-endian byte order format
-                    IXof digest = new ShakeDigest(128);
                     digest.BlockUpdate(b, 0, b.Length);
-                    digest.DoFinal(tmp, 0, tmp.Length);
-                    for (j = 0; j < n; j++)
+                    digest.OutputFinal(tmp, 0, tmp.Length);
+                    for (j = 0; j < n; j += 8)
                     {
-                        A[i * n + j] = (short) (Pack.LE_To_UInt16(tmp, 2 * j) % q);//todo add % q
+                        uint k01 = Pack.LE_To_UInt32(tmp, (2 * j) +  0) & qMask32;
+                        uint k23 = Pack.LE_To_UInt32(tmp, (2 * j) +  4) & qMask32;
+                        uint k45 = Pack.LE_To_UInt32(tmp, (2 * j) +  8) & qMask32;
+                        uint k67 = Pack.LE_To_UInt32(tmp, (2 * j) + 12) & qMask32;
+                        // 6. A[i][j+k] = c[k] where c is treated as a sequence of 8 16-bit integers each in little-endian byte order
+                        A[i * n + j + 0] = (short)k01;
+                        A[i * n + j + 1] = (short)(k01 >> 16);
+                        A[i * n + j + 2] = (short)k23;
+                        A[i * n + j + 3] = (short)(k23 >> 16);
+                        A[i * n + j + 4] = (short)k45;
+                        A[i * n + j + 5] = (short)(k45 >> 16);
+                        A[i * n + j + 6] = (short)k67;
+                        A[i * n + j + 7] = (short)(k67 >> 16);
                     }
-
                 }
-
                 return A;
             }
-
         }
 
         internal class Aes128MatrixGenerator
             : FrodoMatrixGenerator
         {
-            BufferedBlockCipher cipher;
-
             public Aes128MatrixGenerator(int n, int q)
                 : base(n, q)
             {
-                cipher = new BufferedBlockCipher(new AesEngine());
-
             }
 
             internal override short[] GenMatrix(byte[] seedA)
             {
-                //        """Generate matrix A using AES-128 (FrodoKEM specification, Algorithm 7)"""
-                //        A = [[None for j in range(self.n)] for i in range(self.n)]
+                // """Generate matrix A using AES-128 (FrodoKEM specification, Algorithm 7)"""
+                // A = [[None for j in range(self.n)] for i in range(self.n)]
                 short[] A = new short[n * n];
                 byte[] b = new byte[16];
                 byte[] c = new byte[16];
-                byte[] temp = new byte[4];
+                uint qMask32 = ((uint)(q - 1) << 16) | (ushort)(q - 1);
+
+                IBlockCipher cipher = AesUtilities.CreateEngine();
+                cipher.Init(true, new KeyParameter(seedA));
 
                 // 1. for i = 0; i < n; i += 1
                 for (int i = 0; i < n; i++)
                 {
+                    Pack.UInt16_To_LE((ushort)i, b, 0);
+
                     // 2. for j = 0; j < n; j += 8
                     for (int j = 0; j < n; j += 8)
                     {
-
                         // 3. b = i || j || 0 || ... || 0 in {0,1}^128, where i and j are encoded as 16-bit integers in little-endian byte order
-                        Pack.UInt16_To_LE((ushort) (i & 0xffff), temp);
-                        Array.Copy(temp, 0, b, 0, 2);
-                        Pack.UInt16_To_LE((ushort) (j & 0xffff), temp);
-                        Array.Copy(temp, 0, b, 2, 2);
-                        //                b = bytearray(16)
-                        //                struct.pack_into('<H', b, 0, i)
-                        //                struct.pack_into('<H', b, 2, j)
+                        Pack.UInt16_To_LE((ushort)j, b, 2);
                         // 4. c = AES128(seedA, b)
-                        Aes128(c, seedA, b);
+                        cipher.ProcessBlock(b, 0, c, 0);
                         // 5. for k = 0; k < 8; k += 1
-                        for (int k = 0; k < 8; k++)
-                        {
-                            // 6. A[i][j+k] = c[k] where c is treated as a sequence of 8 16-bit integers each in little-endian byte order
-                            A[i * n + j + k] = (short) (Pack.LE_To_UInt16(c, 2 * k) % q); //todo add % q
-                        }
+                        uint k01 = Pack.LE_To_UInt32(c,  0) & qMask32;
+                        uint k23 = Pack.LE_To_UInt32(c,  4) & qMask32;
+                        uint k45 = Pack.LE_To_UInt32(c,  8) & qMask32;
+                        uint k67 = Pack.LE_To_UInt32(c, 12) & qMask32;
+                        // 6. A[i][j+k] = c[k] where c is treated as a sequence of 8 16-bit integers each in little-endian byte order
+                        A[i * n + j + 0] = (short)k01;
+                        A[i * n + j + 1] = (short)(k01 >> 16);
+                        A[i * n + j + 2] = (short)k23;
+                        A[i * n + j + 3] = (short)(k23 >> 16);
+                        A[i * n + j + 4] = (short)k45;
+                        A[i * n + j + 5] = (short)(k45 >> 16);
+                        A[i * n + j + 6] = (short)k67;
+                        A[i * n + j + 7] = (short)(k67 >> 16);
                     }
                 }
-
                 return A;
             }
-
-            void Aes128(byte[] output, byte[] keyBytes, byte[] msg)
-            {
-                try
-                {
-                    KeyParameter kp = new KeyParameter(keyBytes);
-                    cipher.Init(true, kp);
-                    int len = cipher.ProcessBytes(msg, 0, msg.Length, output, 0);
-                    cipher.DoFinal(output, len);
-                }
-                catch (InvalidCipherTextException e)
-                {
-                    throw new Exception(e.ToString(), e);
-                }
-
-            }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/frodo/FrodoParameters.cs b/crypto/src/pqc/crypto/frodo/FrodoParameters.cs
index ad04d8c5e..69d3e5de6 100644
--- a/crypto/src/pqc/crypto/frodo/FrodoParameters.cs
+++ b/crypto/src/pqc/crypto/frodo/FrodoParameters.cs
@@ -1,28 +1,25 @@
-
-using System;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 {
-    public class FrodoParameters
+    public sealed class FrodoParameters
         : ICipherParameters
     {
-
         private static short[] cdf_table640  = {4643, 13363, 20579, 25843, 29227, 31145, 32103, 32525, 32689, 32745, 32762, 32766, 32767};
         private static short[] cdf_table976  = {5638, 15915, 23689, 28571, 31116, 32217, 32613, 32731, 32760, 32766, 32767};
         private static short[] cdf_table1344 = {9142, 23462, 30338, 32361, 32725, 32765, 32767};
 
-        public static FrodoParameters frodokem19888r3 = new FrodoParameters("frodokem19888", 640, 15, 2, cdf_table640, new ShakeDigest(128), new FrodoMatrixGenerator.Aes128MatrixGenerator(640, (1<<15)), 64);
-        public static FrodoParameters frodokem19888shaker3 = new FrodoParameters("frodokem19888shake", 640, 15, 2, cdf_table640, new ShakeDigest(128), new FrodoMatrixGenerator.Shake128MatrixGenerator(640, (1<<15)), 64);
+        public static FrodoParameters frodokem19888r3 = new FrodoParameters("frodokem19888", 640, 15, 2, cdf_table640, new ShakeDigest(128), new FrodoMatrixGenerator.Aes128MatrixGenerator(640, (1<<15)));
+        public static FrodoParameters frodokem19888shaker3 = new FrodoParameters("frodokem19888shake", 640, 15, 2, cdf_table640, new ShakeDigest(128), new FrodoMatrixGenerator.Shake128MatrixGenerator(640, (1<<15)));
 
-        public static FrodoParameters frodokem31296r3 = new FrodoParameters("frodokem31296", 976, 16, 3, cdf_table976, new ShakeDigest(256), new FrodoMatrixGenerator.Aes128MatrixGenerator(976, (1<<16)), 96);
-        public static FrodoParameters frodokem31296shaker3 = new FrodoParameters("frodokem31296shake", 976, 16, 3, cdf_table976, new ShakeDigest(256), new FrodoMatrixGenerator.Shake128MatrixGenerator(976, (1<<16)), 96);
+        public static FrodoParameters frodokem31296r3 = new FrodoParameters("frodokem31296", 976, 16, 3, cdf_table976, new ShakeDigest(256), new FrodoMatrixGenerator.Aes128MatrixGenerator(976, (1<<16)));
+        public static FrodoParameters frodokem31296shaker3 = new FrodoParameters("frodokem31296shake", 976, 16, 3, cdf_table976, new ShakeDigest(256), new FrodoMatrixGenerator.Shake128MatrixGenerator(976, (1<<16)));
 
-        public static FrodoParameters frodokem43088r3 = new FrodoParameters("frodokem43088", 1344, 16, 4, cdf_table1344, new ShakeDigest(256), new FrodoMatrixGenerator.Aes128MatrixGenerator(1344, (1<<16)), 128);
-        public static FrodoParameters frodokem43088shaker3 = new FrodoParameters("frodokem43088shake", 1344, 16, 4, cdf_table1344, new ShakeDigest(256), new FrodoMatrixGenerator.Shake128MatrixGenerator(1344, (1<<16)), 128);
+        public static FrodoParameters frodokem43088r3 = new FrodoParameters("frodokem43088", 1344, 16, 4, cdf_table1344, new ShakeDigest(256), new FrodoMatrixGenerator.Aes128MatrixGenerator(1344, (1<<16)));
+        public static FrodoParameters frodokem43088shaker3 = new FrodoParameters("frodokem43088shake", 1344, 16, 4, cdf_table1344, new ShakeDigest(256), new FrodoMatrixGenerator.Shake128MatrixGenerator(1344, (1<<16)));
 
-        private String name;
+        private string name;
         private int n;
         private int d;
         private int b;
@@ -32,7 +29,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
         private int defaultKeySize;
         private FrodoEngine engine;
 
-        public FrodoParameters(String name, int n, int d, int b, short[] cdf_table, IDigest digest, FrodoMatrixGenerator mGen, int defaultKeySize)
+        public FrodoParameters(string name, int n, int d, int b, short[] cdf_table, IDigest digest,
+            FrodoMatrixGenerator mGen)
         {
             this.name = name;
             this.n = n;
@@ -41,7 +39,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
             this.cdf_table = cdf_table;
             this.digest = digest;
             this.mGen = mGen;
-            this.defaultKeySize = defaultKeySize;
+            this.defaultKeySize = B * FrodoEngine.nbar * FrodoEngine.nbar;
             this.engine = new FrodoEngine(n, d, b, cdf_table, digest, mGen);
         }
 
@@ -49,7 +47,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Frodo
 
         public int N => n;
 
-        public String Name => name;
+        public string Name => name;
 
         public int D => d;
 
diff --git a/crypto/src/pqc/crypto/hqc/FastFourierTransform.cs b/crypto/src/pqc/crypto/hqc/FastFourierTransform.cs
new file mode 100644
index 000000000..be7e822d0
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/FastFourierTransform.cs
@@ -0,0 +1,279 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class FastFourierTransform
+    {
+        internal static void FFT(int[] output, int[] elements, int noCoefs, int fft)
+        {
+            int m = HqcParameters.PARAM_M;
+            int mSize = 1 << (HqcParameters.PARAM_M - 1);
+
+            int fftSize = 1 << fft;
+
+            int[] f0 = new int[fftSize];
+            int[] f1 = new int[fftSize];
+            int[] deltas = new int[m - 1];
+            int[] u = new int[mSize];
+            int[] v = new int[mSize];
+
+            // Step 1: Compute betas
+            int[] betas = new int[m - 1];
+            int[] betaSum = new int[mSize];
+
+            ComputeFFTBetas(betas, m);
+            ComputeSubsetSum(betaSum, betas, m - 1);
+
+            // Step 2: Compute radix
+            ComputeRadix(f0, f1, elements, fft, fft);
+
+            // Step 3: Compute deltas
+            for (int i = 0; i < m - 1; i++)
+            {
+                deltas[i] = GFCalculator.mult(betas[i], betas[i]) ^ betas[i];
+            }
+
+            // Step 5:
+            ComputeFFTRec(u, f0, (noCoefs + 1) / 2, m - 1, fft - 1, deltas, fft, m);
+            ComputeFFTRec(v, f1, noCoefs / 2, m - 1, fft - 1, deltas, fft, m);
+
+            // Step 6.7
+            int k = 1;
+            k = 1 << (m - 1);
+
+            Array.Copy(v, 0, output, k, k);
+
+            output[0] = u[0];
+            output[k] ^= u[0];
+
+            for (int i = 1; i < k; i++)
+            {
+                output[i] = u[i] ^ GFCalculator.mult(betaSum[i], v[i]);
+                output[k + i] ^= output[i];
+            }
+        }
+
+        internal static void ComputeFFTBetas(int[] betas, int m)
+        {
+            for (int i = 0; i < m - 1; i++)
+            {
+                betas[i] = 1 << (m - 1 - i);
+            }
+        }
+
+        internal static void ComputeSubsetSum(int[] subsetSum, int[] set, int size)
+        {
+            subsetSum[0] = 0;
+
+            for (int i = 0; i < size; i++)
+            {
+                for (int j = 0; j < (1 << i); j++)
+                {
+                    subsetSum[(1 << i) + j] = set[i] ^ subsetSum[j];
+                }
+            }
+        }
+
+        internal static void ComputeRadix(int[] f0, int[] f1, int[] f, int mf, int fft)
+        {
+            switch (mf)
+            {
+                case 4:
+                    f0[4] = f[8] ^ f[12];
+                    f0[6] = f[12] ^ f[14];
+                    f0[7] = f[14] ^ f[15];
+                    f1[5] = f[11] ^ f[13];
+                    f1[6] = f[13] ^ f[14];
+                    f1[7] = f[15];
+                    f0[5] = f[10] ^ f[12] ^ f1[5];
+                    f1[4] = f[9] ^ f[13] ^ f0[5];
+
+                    f0[0] = f[0];
+                    f1[3] = f[7] ^ f[11] ^ f[15];
+                    f0[3] = f[6] ^ f[10] ^ f[14] ^ f1[3];
+                    f0[2] = f[4] ^ f0[4] ^ f0[3] ^ f1[3];
+                    f1[1] = f[3] ^ f[5] ^ f[9] ^ f[13] ^ f1[3];
+                    f1[2] = f[3] ^ f1[1] ^ f0[3];
+                    f0[1] = f[2] ^ f0[2] ^ f1[1];
+                    f1[0] = f[1] ^ f0[1];
+                    return;
+
+                case 3:
+                    f0[0] = f[0];
+                    f0[2] = f[4] ^ f[6];
+                    f0[3] = f[6] ^ f[7];
+                    f1[1] = f[3] ^ f[5] ^ f[7];
+                    f1[2] = f[5] ^ f[6];
+                    f1[3] = f[7];
+                    f0[1] = f[2] ^ f0[2] ^ f1[1];
+                    f1[0] = f[1] ^ f0[1];
+                    return;
+
+                case 2:
+                    f0[0] = f[0];
+                    f0[1] = f[2] ^ f[3];
+                    f1[0] = f[1] ^ f0[1];
+                    f1[1] = f[3];
+                    return;
+
+                case 1:
+                    f0[0] = f[0];
+                    f1[0] = f[1];
+                    return;
+
+                default:
+                    ComputeRadixBig(f0, f1, f, mf, fft);
+                    break;
+            }
+        }
+
+        internal static void ComputeRadixBig(int[] f0, int[] f1, int[] f, int mf, int fft)
+        {
+            int n = 1;
+            n <<= (mf - 2);
+            int fftSize = 1 << (fft - 2);
+
+            int[] Q = new int[2 * fftSize];
+            int[] R = new int[2 * fftSize];
+
+            int[] Q0 = new int[fftSize];
+            int[] Q1 = new int[fftSize];
+            int[] R0 = new int[fftSize];
+            int[] R1 = new int[fftSize];
+
+
+            Utils.CopyBytes(f, 3 * n, Q, 0, 2 * n);
+            Utils.CopyBytes(f, 3 * n, Q, n, 2 * n);
+            Utils.CopyBytes(f, 0, R, 0, 4 * n);
+
+            for (int i = 0; i < n; ++i)
+            {
+                Q[i] ^= f[2 * n + i];
+                R[n + i] ^= Q[i];
+            }
+
+            ComputeRadix(Q0, Q1, Q, mf - 1, fft);
+            ComputeRadix(R0, R1, R, mf - 1, fft);
+
+            Utils.CopyBytes(R0, 0, f0, 0, 2 * n);
+            Utils.CopyBytes(Q0, 0, f0, n, 2 * n);
+            Utils.CopyBytes(R1, 0, f1, 0, 2 * n);
+            Utils.CopyBytes(Q1, 0, f1, n, 2 * n);
+        }
+
+        internal static void ComputeFFTRec(int[] output, int[] func, int noCoeffs, int noOfBetas, int noCoeffsPlus, int[] betaSet, int fft, int m)
+        {
+            int fftSize = 1 << (fft - 2);
+            int mSize = 1 << (m - 2);
+
+            int[] fx0 = new int[fftSize];
+            int[] fx1 = new int[fftSize];
+            int[] gammaSet = new int[m - 2];
+            int[] deltaSet = new int[m - 2];
+            int k = 1;
+            int[] gammaSumSet = new int[mSize];
+            int[] uSet = new int[mSize];
+            int[] vSet = new int[mSize];
+            int[] tempSet = new int[m - fft + 1];
+
+            int x = 0;
+            if (noCoeffsPlus == 1)
+            {
+                for (int i = 0; i < noOfBetas; i++)
+                {
+                    tempSet[i] = GFCalculator.mult(betaSet[i], func[1]);
+                }
+
+                output[0] = func[0];
+                x = 1;
+                for (int j = 0; j < noOfBetas; j++)
+                {
+                    for (int t = 0; t < x; t++)
+                    {
+                        output[x + t] = output[t] ^ tempSet[j];
+                    }
+                    x <<= 1;
+                }
+                return;
+            }
+
+            if (betaSet[noOfBetas - 1] != 1)
+            {
+                int betaMPow = 1;
+                x = 1;
+                x <<= noCoeffsPlus;
+                for (int i = 1; i < x; i++)
+                {
+                    betaMPow = GFCalculator.mult(betaMPow, betaSet[noOfBetas - 1]);
+                    func[i] = GFCalculator.mult(betaMPow, func[i]);
+                }
+            }
+
+            ComputeRadix(fx0, fx1, func, noCoeffsPlus, fft);
+
+            for (int i = 0; i < noOfBetas - 1; i++)
+            {
+                gammaSet[i] = GFCalculator.mult(betaSet[i], GFCalculator.inverse(betaSet[noOfBetas - 1]));
+                deltaSet[i] = GFCalculator.mult(gammaSet[i], gammaSet[i]) ^ gammaSet[i];
+            }
+
+            ComputeSubsetSum(gammaSumSet, gammaSet, noOfBetas - 1);
+
+            ComputeFFTRec(uSet, fx0, (noCoeffs + 1) / 2, noOfBetas - 1, noCoeffsPlus - 1, deltaSet, fft, m);
+
+            k = 1;
+            k <<= ((noOfBetas - 1) & 0xf);
+            if (noCoeffs <= 3)
+            {
+                output[0] = uSet[0];
+                output[k] = uSet[0] ^ fx1[0];
+                for (int i = 1; i < k; i++)
+                {
+                    output[i] = uSet[i] ^ GFCalculator.mult(gammaSumSet[i], fx1[0]);
+                    output[k + i] = output[i] ^ fx1[0];
+                }
+            }
+            else
+            {
+                ComputeFFTRec(vSet, fx1, noCoeffs / 2, noOfBetas - 1, noCoeffsPlus - 1, deltaSet, fft, m);
+
+                Array.Copy(vSet, 0, output, k, k);
+
+                output[0] = uSet[0];
+                output[k] ^= uSet[0];
+                for (int i = 1; i < k; i++)
+                {
+                    output[i] = uSet[i] ^ GFCalculator.mult(gammaSumSet[i], vSet[i]);
+                    output[k + i] ^= output[i];
+                }
+
+
+            }
+        }
+
+        internal static void FastFourierTransformGetError(byte[] errorSet, int[] input, int mSize, int[] logArrays)
+        {
+            int m = HqcParameters.PARAM_M;
+            int gfMulOrder = HqcParameters.GF_MUL_ORDER;
+
+            int[] gammaSet = new int[m - 1];
+            int[] gammaSumSet = new int[mSize];
+            int k = mSize;
+
+            ComputeFFTBetas(gammaSet, m);
+            ComputeSubsetSum(gammaSumSet, gammaSet, m - 1);
+
+            errorSet[0] ^= (byte) (1 ^ Utils.ToUnsigned16Bits(-input[0] >> 15));
+            errorSet[0] ^= (byte) (1 ^ Utils.ToUnsigned16Bits(-input[k] >> 15));
+
+            for (int i = 1; i < k; i++)
+            {
+                int tmp = gfMulOrder - logArrays[gammaSumSet[i]];
+                errorSet[tmp] ^= (byte) (1 ^ System.Math.Abs(-input[i] >> 15));
+
+                tmp = gfMulOrder - logArrays[gammaSumSet[i] ^ 1];
+                errorSet[tmp] ^= (byte) (1 ^ System.Math.Abs(-input[k + i] >> 15));
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/GF2PolynomialCalculator.cs b/crypto/src/pqc/crypto/hqc/GF2PolynomialCalculator.cs
new file mode 100644
index 000000000..0c01ade96
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/GF2PolynomialCalculator.cs
@@ -0,0 +1,134 @@
+using System;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
+#endif
+
+using Org.BouncyCastle.Math.Raw;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class GF2PolynomialCalculator
+    {
+        private const int TABLE = 16;
+
+        static void Mod(ulong[] res, ulong[] a, int n, int nByte64)
+        {
+            for (int i = 0; i < nByte64; i++)
+            {
+                ulong r = a[i + nByte64 - 1] >> (n & 0x3F);
+                ulong carry = a[i + nByte64] << (64 - (n & 0x3F));
+                res[i] = a[i] ^ r ^ carry;
+            }
+            res[nByte64 - 1] &= Utils.BitMask((ulong)n, 64);
+        }
+
+        static void Swap(int[] table, int firstIndex, int secondIndex)
+        {
+            int tmp = table[firstIndex];
+            table[firstIndex] = table[secondIndex];
+            table[secondIndex] = tmp;
+        }
+
+        static void FastConvolutionMult(ulong[] res, int[] a, ulong[] b, int weight, int nByte64, int we,
+            HqcKeccakRandomGenerator random)
+        {
+            int[] permutedTable = new int[TABLE];
+            for (int i = 0; i < 16; i++)
+            {
+                permutedTable[i] = i;
+            }
+
+            byte[] permutationTableByte = new byte[TABLE*2];
+            random.ExpandSeed(permutationTableByte, TABLE << 1);
+
+            int[] permutationTable = new int[TABLE];
+            Utils.FromByteArrayToByte16Array(permutationTable, permutationTableByte);
+
+            for (int i = 0; i < TABLE - 1; i++)
+            {
+                Swap(permutedTable, i, i + permutationTable[i] % (TABLE - i));
+            }
+
+            ulong[] table = new ulong[TABLE * (nByte64 + 1)];
+            int idx = permutedTable[0] * (nByte64 + 1);
+            Array.Copy(b, 0, table, idx, nByte64);
+            table[idx + nByte64] = 0UL;
+
+            for (int i = 1; i < TABLE; i++)
+            {
+                idx = permutedTable[i] * (nByte64 + 1);
+                table[idx + nByte64] = Nat.ShiftUpBits64(nByte64, b, 0, i, 0UL, table, idx);
+            }
+
+            int[] permutedSparseVect = new int[we];
+            for (int i = 0; i < weight; i++)
+            {
+                permutedSparseVect[i] = i;
+            }
+
+            byte[] permutationSparseVectBytes = new byte[we * 2];
+            random.ExpandSeed(permutationSparseVectBytes, weight << 1);
+
+            int[] permutationSparseVect = new int[we];
+            Utils.FromByteArrayToByte16Array(permutationSparseVect, permutationSparseVectBytes);
+
+            for (int i = 0; i < (weight - 1); i++)
+            {
+                Swap(permutedSparseVect, i, i + permutationSparseVect[i] % (weight - i));
+            }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> resBytes = MemoryMarshal.AsBytes(res.AsSpan());
+            for (int i = 0; i < weight; i++)
+            {
+                int dec = a[permutedSparseVect[i]] & 0xf;
+                int s = a[permutedSparseVect[i]] >> 4;
+
+                idx = permutedTable[dec] * (nByte64 + 1);
+
+                int count = s * 2 + nByte64 * 8;
+                for (int j = nByte64; j >= 0; --j)
+                {
+                    ulong tmp = BinaryPrimitives.ReadUInt64LittleEndian(resBytes[count..]);
+                    BinaryPrimitives.WriteUInt64LittleEndian(resBytes[count..], tmp ^ table[idx + j]);
+                    count -= 8;
+                }
+            }
+#else
+            ushort[] resByte16 = new ushort[res.Length * 4];
+            for (int i = 0; i < weight; i++)
+            {
+                int dec = a[permutedSparseVect[i]] & 0xf;
+                int s = a[permutedSparseVect[i]] >> 4;
+
+                idx = permutedTable[dec] * (nByte64 + 1);
+
+                int count = s;
+                for (int j = 0; j <= nByte64; j++)
+                {
+                    Utils.XorULongToByte16Array(resByte16, count, table[idx + j]);
+                    count += 4;
+                }
+            }
+            Utils.FromByte16ArrayToULongArray(res, resByte16);
+#endif
+        }
+
+        internal static void ModMult(ulong[] res, int[] a, ulong[] b, int weight,int n,  int nByte64, int we,
+            HqcKeccakRandomGenerator random)
+        {
+            ulong[] tmp = new ulong[(nByte64 << 1) + 1];
+            FastConvolutionMult(tmp, a, b, weight, nByte64, we, random);
+            Mod(res, tmp, n, nByte64);
+        }
+
+        internal static void AddULongs(ulong[] res, ulong[] a, ulong[] b)
+        {
+            for (int i = 0; i < a.Length; i++)
+            {
+                res[i] = a[i] ^ b[i];
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/GFCalculator.cs b/crypto/src/pqc/crypto/hqc/GFCalculator.cs
new file mode 100644
index 000000000..d63e70513
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/GFCalculator.cs
@@ -0,0 +1,29 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class GFCalculator
+    {
+        static int[] exp = new int[] { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1, 2, 4 };
+        static int[] log = new int[] { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 };
+
+        internal static int mult(int a, int b)
+        {
+            int mask;
+            mask = Utils.ToUnsigned16Bits(-a >> 31); // a != 0
+            mask &= Utils.ToUnsigned16Bits(-b >> 31); // b != 0
+            return Utils.ToUnsigned16Bits(mask & exp[mod(log[a] + log[b])]);
+        }
+
+        internal static int mod(int a)
+        {
+            int tmp = Utils.ToUnsigned16Bits(a - HqcParameters.GF_MUL_ORDER);
+            int mask = Utils.ToUnsigned8bits(-(tmp >> 15));
+            return Utils.ToUnsigned16Bits(tmp + (mask & HqcParameters.GF_MUL_ORDER));
+        }
+
+        internal static int inverse(int a)
+        {
+            int mask = Utils.ToUnsigned16Bits(-a >> 31);
+            return mask & exp[HqcParameters.GF_MUL_ORDER - log[a]];
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcEngine.cs b/crypto/src/pqc/crypto/hqc/HqcEngine.cs
new file mode 100644
index 000000000..ac1a96f00
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcEngine.cs
@@ -0,0 +1,436 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class HqcEngine
+    {
+        private int n;
+        private int n1;
+        private int n2;
+        private int k;
+        private int delta;
+        private int w;
+        private int wr;
+        private int we;
+        private int g;
+        private int rejectionThreshold;
+        private int fft;
+        private int mulParam;
+
+        private int SEED_SIZE = 40;
+        private byte G_FCT_DOMAIN = 3;
+        private byte H_FCT_DOMAIN = 4;
+        private byte K_FCT_DOMAIN = 5;
+
+        private int N_BYTE;
+        private int n1n2;
+        private int N_BYTE_64;
+        private int K_BYTE;
+        private int K_BYTE_64;
+        private int N1_BYTE_64;
+        private int N1N2_BYTE_64;
+        private int N1N2_BYTE;
+        private int N1_BYTE;
+
+        private int[] generatorPoly;
+        private int SHA512_BYTES = 512 / 8;
+
+        public HqcEngine(int n, int n1, int n2, int k, int g, int delta, int w, int wr, int we, int rejectionThreshold, int fft, int[] generatorPoly)
+        {
+            this.n = n;
+            this.k = k;
+            this.delta = delta;
+            this.w = w;
+            this.wr = wr;
+            this.we = we;
+            this.n1 = n1;
+            this.n2 = n2;
+            this.n1n2 = n1 * n2;
+            this.generatorPoly = generatorPoly;
+            this.g = g;
+            this.rejectionThreshold = rejectionThreshold;
+            this.fft = fft;
+
+            this.mulParam = (n2 + 127) / 128;
+            this.N_BYTE = Utils.GetByteSizeFromBitSize(n);
+            this.K_BYTE = k;
+            this.N_BYTE_64 = Utils.GetByte64SizeFromBitSize(n);
+            this.K_BYTE_64 = Utils.GetByteSizeFromBitSize(k);
+            this.N1_BYTE_64 = Utils.GetByteSizeFromBitSize(n1);
+            this.N1N2_BYTE_64 = Utils.GetByte64SizeFromBitSize(n1 * n2);
+            this.N1N2_BYTE = Utils.GetByteSizeFromBitSize(n1 * n2);
+            this.N1_BYTE = Utils.GetByteSizeFromBitSize(n1);
+        }
+
+        /**
+         * Generate key pairs
+         * - Secret key : (x,y)
+         * - Public key: (h,s)
+         *  @param pk     output pk = (publicSeed||s)
+         *
+         **/
+        public void GenKeyPair(byte[] pk, byte[] sk, byte[] seed)
+        {
+            // Randomly generate seeds for secret keys and public keys
+            byte[] secretKeySeed = new byte[SEED_SIZE];
+
+            HqcKeccakRandomGenerator randomGenerator = new HqcKeccakRandomGenerator(256);
+            randomGenerator.RandomGeneratorInit(seed, null, seed.Length, 0);
+            randomGenerator.Squeeze(secretKeySeed, 40);
+
+            // 1. Randomly generate secret keys x, y
+            HqcKeccakRandomGenerator secretKeySeedExpander = new HqcKeccakRandomGenerator(256);
+            secretKeySeedExpander.SeedExpanderInit(secretKeySeed, secretKeySeed.Length);
+
+            ulong[] xLongBytes = new ulong[N_BYTE_64];
+            int[] yPos = new int[this.w];
+
+            GenerateSecretKey(xLongBytes, secretKeySeedExpander, w);
+            GenerateSecretKeyByCoordinates(yPos, secretKeySeedExpander, w);
+
+            // 2. Randomly generate h
+            byte[] publicKeySeed = new byte[SEED_SIZE];
+            randomGenerator.Squeeze(publicKeySeed, 40);
+
+            HqcKeccakRandomGenerator randomPublic = new HqcKeccakRandomGenerator(256);
+            randomPublic.SeedExpanderInit(publicKeySeed, publicKeySeed.Length);
+
+            ulong[] hLongBytes = new ulong[N_BYTE_64];
+            GeneratePublicKeyH(hLongBytes, randomPublic);
+
+            // 3. Compute s
+            ulong[] s = new ulong[N_BYTE_64];
+            GF2PolynomialCalculator.ModMult(s, yPos, hLongBytes, w, n, N_BYTE_64, we, secretKeySeedExpander);
+            GF2PolynomialCalculator.AddULongs(s, s, xLongBytes);
+            byte[] sBytes = new byte[N_BYTE];
+            Utils.FromULongArrayToByteArray(sBytes, s);
+
+            byte[] tmpPk = Arrays.Concatenate(publicKeySeed, sBytes);
+            byte[] tmpSk = Arrays.Concatenate(secretKeySeed, tmpPk);
+
+            Array.Copy(tmpPk, 0, pk, 0, tmpPk.Length);
+            Array.Copy(tmpSk, 0, sk, 0, tmpSk.Length);
+        }
+
+        /**
+         * HQC Encapsulation
+         * - Input: pk, seed
+         * - Output: c = (u,v,d), K
+         *
+         * @param u    u
+         * @param v    v
+         * @param d    d
+         * @param K    session key
+         * @param pk   public key
+         * @param seed seed
+         **/
+        public void Encaps(byte[] u, byte[] v, byte[] K, byte[] d, byte[] pk, byte[] seed)
+        {
+            // 1. Randomly generate m
+            byte[] m = new byte[K_BYTE];
+
+            // TODO: no way to gen m without seed and gen skseed, pkseed. In reference implementation they use the same
+            byte[] secretKeySeed = new byte[SEED_SIZE];
+            HqcKeccakRandomGenerator randomGenerator = new HqcKeccakRandomGenerator(256);
+            randomGenerator.RandomGeneratorInit(seed, null, seed.Length, 0);
+            randomGenerator.Squeeze(secretKeySeed, 40);
+
+            byte[] publicKeySeed = new byte[SEED_SIZE];
+            randomGenerator.Squeeze(publicKeySeed, 40);
+
+            // gen m
+            randomGenerator.Squeeze(m, K_BYTE);
+
+            // 2. Generate theta
+            byte[] theta = new byte[SHA512_BYTES];
+            HqcKeccakRandomGenerator shakeDigest = new HqcKeccakRandomGenerator(256);
+            shakeDigest.SHAKE256_512_ds(theta, m, m.Length, new byte[] { G_FCT_DOMAIN });
+
+            // 3. Generate ciphertext c = (u,v)
+            // Extract public keys
+            ulong[] h = new ulong[N_BYTE_64];
+            byte[] s = new byte[N_BYTE];
+            ExtractPublicKeys(h, s, pk);
+
+            ulong[] vTmp = new ulong[N1N2_BYTE_64];
+            Encrypt(u, vTmp, h, s, m, theta);
+            Utils.FromULongArrayToByteArray(v, vTmp);
+
+            // 4. Compute d
+            shakeDigest.SHAKE256_512_ds(d, m, m.Length, new byte[] { H_FCT_DOMAIN });
+
+            // 5. Compute session key K
+            byte[] hashInputK = new byte[K_BYTE + N_BYTE + N1N2_BYTE];
+            hashInputK = Arrays.Concatenate(m, u);
+            hashInputK = Arrays.Concatenate(hashInputK, v);
+            shakeDigest.SHAKE256_512_ds(K, hashInputK, hashInputK.Length, new byte[] { K_FCT_DOMAIN });
+        }
+
+        /**
+         * HQC Decapsulation
+         * - Input: ct, sk
+         * - Output: ss
+         *
+         * @param ss session key
+         * @param ct ciphertext
+         * @param sk secret key
+         **/
+        public void Decaps(byte[] ss, byte[] ct, byte[] sk)
+        {
+            //Extract Y and Public Keys from sk
+            int[] yPos = new int[this.w];
+            byte[] pk = new byte[40 + N_BYTE];
+            ExtractKeysFromSecretKeys(yPos, pk, sk);
+
+            // Extract u, v, d from ciphertext
+            byte[] u = new byte[N_BYTE];
+            byte[] v = new byte[N1N2_BYTE];
+            byte[] d = new byte[SHA512_BYTES];
+            HqcEngine.ExtractCiphertexts(u, v, d, ct);
+
+            // 1. Decrypt -> m'
+            byte[] mPrimeBytes = new byte[k];
+            Decrypt(mPrimeBytes, mPrimeBytes, u, v, yPos);
+
+            // 2. Compute theta'
+            byte[] theta = new byte[SHA512_BYTES];
+            HqcKeccakRandomGenerator shakeDigest = new HqcKeccakRandomGenerator(256);
+            shakeDigest.SHAKE256_512_ds(theta, mPrimeBytes, mPrimeBytes.Length, new byte[] { G_FCT_DOMAIN });
+
+            // 3. Compute c' = Enc(pk, m', theta')
+            // Extract public keys
+            ulong[] h = new ulong[N_BYTE_64];
+            byte[] s = new byte[N_BYTE];
+            ExtractPublicKeys(h, s, pk);
+
+            byte[] u2Bytes = new byte[N_BYTE];
+            byte[] v2Bytes = new byte[N1N2_BYTE];
+            ulong[] vTmp = new ulong[N1N2_BYTE_64];
+            Encrypt(u2Bytes, vTmp, h, s, mPrimeBytes, theta);
+            Utils.FromULongArrayToByteArray(v2Bytes, vTmp);
+
+            // 4. Compute d' = H(m')
+            byte[] dPrime = new byte[SHA512_BYTES];
+            shakeDigest.SHAKE256_512_ds(dPrime, mPrimeBytes, mPrimeBytes.Length, new byte[] { H_FCT_DOMAIN });
+
+            // 5. Compute session key KPrime
+            byte[] hashInputK = new byte[K_BYTE + N_BYTE + N1N2_BYTE];
+            hashInputK = Arrays.Concatenate(mPrimeBytes, u);
+            hashInputK = Arrays.Concatenate(hashInputK, v);
+            shakeDigest.SHAKE256_512_ds(ss, hashInputK, hashInputK.Length, new byte[] { K_FCT_DOMAIN });
+
+            int result = 1;
+            // Compare u, v, d
+            if (!Arrays.AreEqual(u, u2Bytes))
+            {
+                result = 0;
+            }
+
+            if (!Arrays.AreEqual(v, v2Bytes))
+            {
+                result = 0;
+            }
+
+            if (!Arrays.AreEqual(d, dPrime))
+            {
+                result = 0;
+            }
+
+            if (result == 0)
+            { //abort
+                for (int i = 0; i < GetSessionKeySize(); i++)
+                {
+                    ss[i] = 0;
+                }
+            }
+        }
+
+        internal int GetSessionKeySize()
+        {
+            return SHA512_BYTES;
+        }
+
+        /**
+         * HQC Encryption
+         * - Input: (h,s, m)
+         * - Output: (u,v) = c
+         *
+         * @param h public key
+         * @param s public key
+         * @param m message
+         * @param u ciphertext
+         * @param v ciphertext
+         **/
+        private void Encrypt(byte[] u, ulong[] v, ulong[] h, byte[] s, byte[] m, byte[] theta)
+        {
+            // Randomly generate e, r1, r2
+            HqcKeccakRandomGenerator randomGenerator = new HqcKeccakRandomGenerator(256);
+            randomGenerator.SeedExpanderInit(theta, SEED_SIZE);
+            ulong[] e = new ulong[N_BYTE_64];
+            ulong[] r1 = new ulong[N_BYTE_64];
+            int[] r2 = new int[wr];
+            GenerateSecretKey(r1, randomGenerator, wr);
+            GenerateSecretKeyByCoordinates(r2, randomGenerator, wr);
+            GenerateSecretKey(e, randomGenerator, we);
+
+            // Calculate u
+            ulong[] uLong = new ulong[N_BYTE_64];
+            GF2PolynomialCalculator.ModMult(uLong, r2, h, wr, n, N_BYTE_64, we, randomGenerator);
+            GF2PolynomialCalculator.AddULongs(uLong, uLong, r1);
+            Utils.FromULongArrayToByteArray(u, uLong);
+
+            // Calculate v
+            // encode m
+            byte[] res = new byte[n1];
+            ulong[] vLong = new ulong[N1N2_BYTE_64];
+            ulong[] tmpVLong = new ulong[N_BYTE_64];
+            ReedSolomon.Encode(res, m, K_BYTE * 8, n1, k, g, generatorPoly);
+            ReedMuller.Encode(vLong, res, n1, mulParam);
+            Array.Copy(vLong, 0, tmpVLong, 0, vLong.Length);
+
+            //Compute v
+            ulong[] sLong = new ulong[N_BYTE_64];
+            Utils.FromByteArrayToULongArray(sLong, s);
+
+            ulong[] tmpLong = new ulong[N_BYTE_64];
+            GF2PolynomialCalculator.ModMult(tmpLong, r2, sLong, wr, n, N_BYTE_64, we, randomGenerator);
+            GF2PolynomialCalculator.AddULongs(tmpLong, tmpLong, tmpVLong);
+            GF2PolynomialCalculator.AddULongs(tmpLong, tmpLong, e);
+
+            Utils.ResizeArray(v, n1n2, tmpLong, n, N1N2_BYTE_64, N1N2_BYTE_64);
+        }
+
+        private void Decrypt(byte[] output, byte[] m, byte[] u, byte[] v, int[] y)
+        {
+            byte[] tmpSeed = new byte[SEED_SIZE];
+            HqcKeccakRandomGenerator randomGenerator = new HqcKeccakRandomGenerator(256);
+            randomGenerator.SeedExpanderInit(tmpSeed, SEED_SIZE);
+
+            ulong[] uLongs = new ulong[N_BYTE_64];
+            Utils.FromByteArrayToULongArray(uLongs, u);
+
+            ulong[] vLongs = new ulong[N1N2_BYTE_64];
+            Utils.FromByteArrayToULongArray(vLongs, v);
+
+            ulong[] tmpV = new ulong[N_BYTE_64];
+            Array.Copy(vLongs, 0, tmpV, 0, vLongs.Length);
+
+            ulong[] tmpLong = new ulong[N_BYTE_64];
+            GF2PolynomialCalculator.ModMult(tmpLong, y, uLongs, w, n, N_BYTE_64, we, randomGenerator);
+            GF2PolynomialCalculator.AddULongs(tmpLong, tmpLong, tmpV);
+
+            // Decode res
+            byte[] tmp = new byte[n1];
+            ReedMuller.Decode(tmp, tmpLong, n1, mulParam);
+            ReedSolomon.Decode(m, tmp, n1, fft, delta, k, g);
+
+            Array.Copy(m, 0, output, 0, output.Length);
+        }
+
+        private void GenerateSecretKey(ulong[] output, HqcKeccakRandomGenerator random, int w)
+        {
+            int[] tmp = new int[w];
+            GenerateSecretKeyByCoordinates(tmp, random, w);
+
+            for (int i = 0; i < w; ++i)
+            {
+                int index = tmp[i] / 64;
+                int pos = tmp[i] % 64;
+                ulong t = 1UL << pos;
+                output[index] |= t;
+            }
+        }
+
+        private void GenerateSecretKeyByCoordinates(int[] output, HqcKeccakRandomGenerator random, int w)
+        {
+            int randomByteSize = 3 * w;
+            byte[] randomBytes = new byte[3 * this.wr];
+            int inc;
+
+            int i = 0;
+            int j = randomByteSize;
+            while (i < w)
+            {
+                do
+                {
+                    if (j == randomByteSize)
+                    {
+                        random.ExpandSeed(randomBytes, randomByteSize);
+
+                        j = 0;
+                    }
+
+                    output[i] = (randomBytes[j++] & 0xff) << 16;
+                    output[i] |= (randomBytes[j++] & 0xff) << 8;
+                    output[i] |= (randomBytes[j++] & 0xff);
+
+                }
+                while (output[i] >= this.rejectionThreshold);
+
+                output[i] = output[i] % this.n;
+                inc = 1;
+                for (int k = 0; k < i; k++)
+                {
+                    if (output[k] == output[i])
+                    {
+                        inc = 0;
+                    }
+                }
+                i += inc;
+            }
+        }
+
+        void GeneratePublicKeyH(ulong[] output, HqcKeccakRandomGenerator random)
+        {
+            byte[] randBytes = new byte[N_BYTE];
+            random.ExpandSeed(randBytes, N_BYTE);
+            ulong[] tmp = new ulong[N_BYTE_64];
+            Utils.FromByteArrayToULongArray(tmp, randBytes);
+            tmp[N_BYTE_64 - 1] &= Utils.BitMask((ulong)n, 64);
+            Array.Copy(tmp, 0, output, 0, output.Length);
+        }
+
+        private void ExtractPublicKeys(ulong[] h, byte[] s, byte[] pk)
+        {
+            byte[] publicKeySeed = new byte[SEED_SIZE];
+            Array.Copy(pk, 0, publicKeySeed, 0, publicKeySeed.Length);
+
+            HqcKeccakRandomGenerator randomPublic = new HqcKeccakRandomGenerator(256);
+            randomPublic.SeedExpanderInit(publicKeySeed, publicKeySeed.Length);
+
+            ulong[] hLongBytes = new ulong[N_BYTE_64];
+            GeneratePublicKeyH(hLongBytes, randomPublic);
+
+            Array.Copy(hLongBytes, 0, h, 0, h.Length);
+            Array.Copy(pk, 40, s, 0, s.Length);
+        }
+
+        private void ExtractKeysFromSecretKeys(int[] y, byte[] pk, byte[] sk)
+        {
+            byte[] secretKeySeed = new byte[SEED_SIZE];
+            Array.Copy(sk, 0, secretKeySeed, 0, secretKeySeed.Length);
+
+            // Randomly generate secret keys x, y
+            HqcKeccakRandomGenerator secretKeySeedExpander = new HqcKeccakRandomGenerator(256);
+            secretKeySeedExpander.SeedExpanderInit(secretKeySeed, secretKeySeed.Length);
+
+            ulong[] xLongBytes = new ulong[N_BYTE_64];
+            int[] yPos = new int[this.w];
+
+            GenerateSecretKey(xLongBytes, secretKeySeedExpander, w);
+            GenerateSecretKeyByCoordinates(yPos, secretKeySeedExpander, w);
+
+            Array.Copy(yPos, 0, y, 0, yPos.Length);
+            Array.Copy(sk, SEED_SIZE, pk, 0, pk.Length);
+        }
+
+        private static void ExtractCiphertexts(byte[] u, byte[] v, byte[] d, byte[] ct)
+        {
+            Array.Copy(ct, 0, u, 0, u.Length);
+            Array.Copy(ct, u.Length, v, 0, v.Length);
+            Array.Copy(ct, u.Length + v.Length, d, 0, d.Length);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcKeccakRandomGenerator.cs b/crypto/src/pqc/crypto/hqc/HqcKeccakRandomGenerator.cs
new file mode 100644
index 000000000..090f5a9c0
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcKeccakRandomGenerator.cs
@@ -0,0 +1,321 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class HqcKeccakRandomGenerator
+    {
+        private static readonly ulong[] KeccakRoundConstants =
+        {
+            0x0000000000000001L, 0x0000000000008082L, 0x800000000000808aL, 0x8000000080008000L,
+            0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L, 0x8000000000008009L,
+            0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL,
+            0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L,
+            0x8000000000008002L, 0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL,
+            0x8000000080008081L, 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L
+        };
+
+        protected long[] state = new long[26];
+        protected byte[] dataQueue = new byte[192];
+        protected int rate;
+        protected int bitsInQueue;
+        protected int fixedOutputLength;
+        protected bool squeezing;
+
+        public HqcKeccakRandomGenerator()
+        {
+            Init(288);
+        }
+
+        public HqcKeccakRandomGenerator(int bitLength)
+        {
+            Init(bitLength);
+        }
+
+        private void Init(int bitLength)
+        {
+            switch (bitLength)
+            {
+            case 128:
+            case 224:
+            case 256:
+            case 288:
+            case 384:
+            case 512:
+                InitSponge(1600 - (bitLength << 1));
+                break;
+            default:
+                throw new ArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512.");
+            }
+        }
+
+        private void InitSponge(int rate)
+        {
+            if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0))
+                throw new InvalidOperationException("invalid rate value");
+
+            this.rate = rate;
+            for (int i = 0; i < state.Length; ++i)
+            {
+                state[i] = 0L;
+            }
+            Arrays.Fill(this.dataQueue, 0);
+            this.bitsInQueue = 0;
+            this.squeezing = false;
+            this.fixedOutputLength = (1600 - rate) / 2;
+        }
+
+        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 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];
+
+            for (int i = 0; i < 24; i++)
+            {
+                // theta
+                long c0 = a00 ^ a05 ^ a10 ^ a15 ^ a20;
+                long c1 = a01 ^ a06 ^ a11 ^ a16 ^ a21;
+                long c2 = a02 ^ a07 ^ a12 ^ a17 ^ a22;
+                long c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23;
+                long c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24;
+
+                long d1 = Longs.RotateLeft(c1, 1) ^ c4;
+                long d2 = Longs.RotateLeft(c2, 1) ^ c0;
+                long d3 = Longs.RotateLeft(c3, 1) ^ c1;
+                long d4 = Longs.RotateLeft(c4, 1) ^ c2;
+                long d0 = Longs.RotateLeft(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;
+
+                // rho/pi
+                c1  = Longs.RotateLeft(a01,  1);
+                a01 = Longs.RotateLeft(a06, 44);
+                a06 = Longs.RotateLeft(a09, 20);
+                a09 = Longs.RotateLeft(a22, 61);
+                a22 = Longs.RotateLeft(a14, 39);
+                a14 = Longs.RotateLeft(a20, 18);
+                a20 = Longs.RotateLeft(a02, 62);
+                a02 = Longs.RotateLeft(a12, 43);
+                a12 = Longs.RotateLeft(a13, 25);
+                a13 = Longs.RotateLeft(a19,  8);
+                a19 = Longs.RotateLeft(a23, 56);
+                a23 = Longs.RotateLeft(a15, 41);
+                a15 = Longs.RotateLeft(a04, 27);
+                a04 = Longs.RotateLeft(a24, 14);
+                a24 = Longs.RotateLeft(a21,  2);
+                a21 = Longs.RotateLeft(a08, 55);
+                a08 = Longs.RotateLeft(a16, 45);
+                a16 = Longs.RotateLeft(a05, 36);
+                a05 = Longs.RotateLeft(a03, 28);
+                a03 = Longs.RotateLeft(a18, 21);
+                a18 = Longs.RotateLeft(a17, 15);
+                a17 = Longs.RotateLeft(a11, 10);
+                a11 = Longs.RotateLeft(a07,  6);
+                a07 = Longs.RotateLeft(a10,  3);
+                a10 = c1;
+
+                // chi
+                c0 = a00 ^ (~a01 & a02);
+                c1 = a01 ^ (~a02 & a03);
+                a02 ^= ~a03 & a04;
+                a03 ^= ~a04 & a00;
+                a04 ^= ~a00 & a01;
+                a00 = c0;
+                a01 = c1;
+
+                c0 = a05 ^ (~a06 & a07);
+                c1 = a06 ^ (~a07 & a08);
+                a07 ^= ~a08 & a09;
+                a08 ^= ~a09 & a05;
+                a09 ^= ~a05 & a06;
+                a05 = c0;
+                a06 = c1;
+
+                c0 = a10 ^ (~a11 & a12);
+                c1 = a11 ^ (~a12 & a13);
+                a12 ^= ~a13 & a14;
+                a13 ^= ~a14 & a10;
+                a14 ^= ~a10 & a11;
+                a10 = c0;
+                a11 = c1;
+
+                c0 = a15 ^ (~a16 & a17);
+                c1 = a16 ^ (~a17 & a18);
+                a17 ^= ~a18 & a19;
+                a18 ^= ~a19 & a15;
+                a19 ^= ~a15 & a16;
+                a15 = c0;
+                a16 = c1;
+
+                c0 = a20 ^ (~a21 & a22);
+                c1 = a21 ^ (~a22 & a23);
+                a22 ^= ~a23 & a24;
+                a23 ^= ~a24 & a20;
+                a24 ^= ~a20 & a21;
+                a20 = c0;
+                a21 = c1;
+
+                // iota
+                a00 ^= (long) 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;
+        }
+
+        private void KeccakIncAbsorb(byte[] input, int inputLen)
+        {
+            if (input == null)
+            {
+                return;
+            }
+
+            int count = 0;
+            int rateBytes = rate >> 3;
+            while (inputLen + state[25] >= rateBytes)
+            {
+                for (int i = 0; i < rateBytes - state[25]; i++)
+                {
+                    int tmp = (int)(state[25] + i) >> 3;
+                    state[tmp] ^= (long) (((ulong) (input[i + count] & 0xff)) << (int) (8 * ((state[25] + i) & 0x07)));
+                }
+                inputLen -= (int) (rateBytes - state[25]);
+                count += (int) (rateBytes - state[25]);
+                state[25] = 0;
+                KeccakPermutation();
+            }
+
+            for (int i = 0; i < inputLen; i++)
+            {
+                int tmp = (int)(state[25] + i) >> 3;
+                state[tmp] ^= (long) (((ulong) (input[i + count] & 0xff)) << (int) (8 * ((state[25] + i) & 0x07)));
+            }
+
+            state[25] += inputLen;
+        }
+
+        private void KeccakIncFinalize(int p)
+        {
+            int rateBytes = rate >> 3;
+
+            state[(int)state[25] >> 3] ^= (long) (((ulong) (p)) << (int) (8 * ((state[25]) & 0x07)));
+            state[(rateBytes - 1) >> 3] ^=((long) (128)) << (8 * ((rateBytes - 1) & 0x07));
+
+
+            state[25] = 0;
+        }
+
+        private void KeccakIncSqueeze(byte[] output, int outLen)
+        {
+            int rateBytes = rate >> 3;
+            int i;
+            for (i = 0; i < outLen && i < state[25]; i++)
+            {
+                output[i] = (byte)(state[(int)((rateBytes - state[25] + i) >> 3)] >> (int) (8 * ((rateBytes - state[25] + i) & 0x07)));
+            }
+
+            int count = i;
+            outLen -= i;
+            state[25] -= i;
+
+            while (outLen > 0)
+            {
+                KeccakPermutation();
+
+                for (i = 0; i < outLen && i < rateBytes; i++)
+                {
+                    byte t = (byte)(state[i >> 3] >> (8 * (i & 0x07)));
+                    output[count + i] = (byte)(state[i >> 3] >> (8 * (i & 0x07)));
+                }
+                count = count + i;
+                outLen -= i;
+                state[25] = rateBytes - i;
+            }
+        }
+
+        public void Squeeze(byte[] output, int outLen)
+        {
+            KeccakIncSqueeze(output, outLen);
+        }
+
+        public void RandomGeneratorInit(byte[] entropyInput, byte[] personalizationString, int entropyLen, int perLen)
+        {
+            byte[] domain = new byte[] { 1 };
+            KeccakIncAbsorb(entropyInput, entropyLen);
+            KeccakIncAbsorb(personalizationString, perLen);
+            KeccakIncAbsorb(domain, domain.Length);
+            KeccakIncFinalize(0x1F);
+        }
+
+        public void SeedExpanderInit(byte[] seed, int seedLen)
+        {
+            byte[] domain = new byte[] { 2 };
+            KeccakIncAbsorb(seed, seedLen);
+            KeccakIncAbsorb(domain, 1);
+            KeccakIncFinalize(0x1F);
+        }
+
+        public void ExpandSeed(byte[] output, int outLen)
+        {
+            int bSize = 8;
+            int r = outLen % bSize;
+            byte[] tmp = new byte[bSize];
+            KeccakIncSqueeze(output, outLen - r);
+
+            if (r != 0)
+            {
+                KeccakIncSqueeze(tmp, bSize);
+                int count = outLen - r;
+                for (int i = 0; i < r; i++)
+                {
+                    output[count + i] = tmp[i];
+                }
+            }
+        }
+
+        public void SHAKE256_512_ds(byte[] output, byte[] input, int inLen, byte[] domain)
+        {
+            for (int i = 0; i < state.Length; i++)
+            {
+                state[i] = 0;
+            }
+            KeccakIncAbsorb(input, inLen);
+            KeccakIncAbsorb(domain, domain.Length);
+            KeccakIncFinalize(0x1F);
+            KeccakIncSqueeze(output, 512 / 8);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcKemExtractor.cs b/crypto/src/pqc/crypto/hqc/HqcKemExtractor.cs
new file mode 100644
index 000000000..32890f80b
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcKemExtractor.cs
@@ -0,0 +1,37 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    public class HqcKemExtractor : IEncapsulatedSecretExtractor
+    {
+        private HqcEngine engine;
+
+        private HqcKeyParameters key;
+
+        public HqcKemExtractor(HqcPrivateKeyParameters privParams)
+        {
+            this.key = privParams;
+            InitCipher(key.Parameters);
+        }
+
+        private void InitCipher(HqcParameters param)
+        {
+            engine = param.Engine;
+        }
+
+        
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            byte[] session_key = new byte[engine.GetSessionKeySize()];
+            HqcPrivateKeyParameters secretKey = (HqcPrivateKeyParameters)key;
+            byte[] sk = secretKey.PrivateKey;
+
+            engine.Decaps(session_key, encapsulation, sk);
+
+            return session_key;
+        }
+
+        public int EncapsulationLength => key.Parameters.NBytes + key.Parameters.N1n2Bytes + 64;
+ 
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcKemGenerator.cs b/crypto/src/pqc/crypto/hqc/HqcKemGenerator.cs
new file mode 100644
index 000000000..918dadd38
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcKemGenerator.cs
@@ -0,0 +1,89 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    public class HqcKemGenerator : IEncapsulatedSecretGenerator
+    {
+        private SecureRandom sr;
+        public HqcKemGenerator(SecureRandom random)
+        {
+            this.sr = random;
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            HqcPublicKeyParameters key = (HqcPublicKeyParameters)recipientKey;
+            HqcEngine engine = key.Parameters.Engine;
+
+            byte[] K = new byte[key.Parameters.Sha512Bytes];
+            byte[] u = new byte[key.Parameters.NBytes];
+            byte[] v = new byte[key.Parameters.N1n2Bytes];
+            byte[] d = new byte[key.Parameters.Sha512Bytes];
+            byte[] pk = key.PublicKey;
+            byte[] seed = new byte[48];
+
+            sr.NextBytes(seed);
+
+            engine.Encaps(u, v, K, d, pk, seed);
+
+            byte[] cipherText = Arrays.Concatenate(u, v);
+            cipherText = Arrays.Concatenate(cipherText, d);
+
+            return new HqcKemGenerator.SecretWithEncapsulationImpl(K, cipherText);
+        }
+
+        private class SecretWithEncapsulationImpl : ISecretWithEncapsulation
+        {
+            private volatile bool hasBeenDestroyed = false;
+
+            private byte[] sessionKey;
+            private byte[] cipher_text;
+
+            public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
+            {
+                this.sessionKey = sessionKey;
+                this.cipher_text = cipher_text;
+            }
+
+            public byte[] GetSecret()
+            {
+                CheckDestroyed();
+                return Arrays.Clone(sessionKey);
+            }
+
+            public byte[] GetEncapsulation()
+            {
+                CheckDestroyed();
+
+                return Arrays.Clone(cipher_text);
+            }
+
+            public void Dispose()
+            {
+                if (!hasBeenDestroyed)
+                {
+                    hasBeenDestroyed = true;
+                    Arrays.Clear(sessionKey);
+                    Arrays.Clear(cipher_text);
+                }
+            }
+
+            public bool IsDestroyed()
+            {
+                return hasBeenDestroyed;
+            }
+
+            void CheckDestroyed()
+            {
+                if (IsDestroyed())
+                {
+                    throw new Exception("data has been destroyed");
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcKeyGenerationParameters.cs b/crypto/src/pqc/crypto/hqc/HqcKeyGenerationParameters.cs
new file mode 100644
index 000000000..e548e1999
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcKeyGenerationParameters.cs
@@ -0,0 +1,19 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    public class HqcKeyGenerationParameters : KeyGenerationParameters
+    {
+        private HqcParameters param;
+
+        public HqcKeyGenerationParameters(
+            SecureRandom random,
+            HqcParameters param) : base(random, 256)
+            {
+                this.param = param;
+            }
+
+            public HqcParameters Parameters => param;
+        }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcKeyPairGenerator.cs b/crypto/src/pqc/crypto/hqc/HqcKeyPairGenerator.cs
new file mode 100644
index 000000000..243b0d850
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcKeyPairGenerator.cs
@@ -0,0 +1,69 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+
+{
+    public class HqcKeyPairGenerator : IAsymmetricCipherKeyPairGenerator
+    {
+        private int n;
+
+        private int k;
+
+        private int delta;
+
+        private int w;
+
+        private int wr;
+
+        private int we;
+        private int N_BYTE;
+        private HqcKeyGenerationParameters hqcKeyGenerationParameters;
+
+        private SecureRandom random;
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            byte[] seed = new byte[48];
+            random.NextBytes(seed);
+            return GenKeyPair(seed);
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPairWithSeed(byte[] seed)
+        {
+            return GenKeyPair(seed);
+        }
+
+        public void Init(KeyGenerationParameters parameters)
+        {
+            this.hqcKeyGenerationParameters = (HqcKeyGenerationParameters)parameters;
+            this.random = parameters.Random;
+
+            // get parameters
+            this.n = this.hqcKeyGenerationParameters.Parameters.N;
+            this.k = this.hqcKeyGenerationParameters.Parameters.K;
+            this.delta = this.hqcKeyGenerationParameters.Parameters.Delta;
+            this.w = this.hqcKeyGenerationParameters.Parameters.W;
+            this.wr = this.hqcKeyGenerationParameters.Parameters.Wr;
+            this.we = this.hqcKeyGenerationParameters.Parameters.We;
+            this.N_BYTE = (n + 7) / 8;
+        }
+        private AsymmetricCipherKeyPair GenKeyPair(byte[] seed)
+        {
+            HqcEngine engine = hqcKeyGenerationParameters.Parameters.Engine;
+            byte[] pk = new byte[40 + N_BYTE];
+            byte[] sk = new byte[40 + 40 + N_BYTE];
+
+            engine.GenKeyPair(pk, sk, seed);
+
+            // form keys
+            HqcPublicKeyParameters publicKey = new HqcPublicKeyParameters(hqcKeyGenerationParameters.Parameters, pk);
+            HqcPrivateKeyParameters privateKey = new HqcPrivateKeyParameters(hqcKeyGenerationParameters.Parameters, sk);
+
+            return new AsymmetricCipherKeyPair(publicKey, privateKey);
+        }
+
+       
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcKeyParameters.cs b/crypto/src/pqc/crypto/hqc/HqcKeyParameters.cs
new file mode 100644
index 000000000..8fc900a64
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcKeyParameters.cs
@@ -0,0 +1,19 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    public class HqcKeyParameters : AsymmetricKeyParameter
+    {
+        private HqcParameters param;
+
+        public HqcKeyParameters(
+            bool isPrivate,
+            HqcParameters param) : base(isPrivate)
+        {
+            this.param = param;
+        }
+
+        public HqcParameters Parameters => param;
+       
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcParameters.cs b/crypto/src/pqc/crypto/hqc/HqcParameters.cs
new file mode 100644
index 000000000..da5948296
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcParameters.cs
@@ -0,0 +1,71 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+
+{
+    public class HqcParameters : ICipherParameters
+    {
+        // 128 bits security
+        public static HqcParameters hqc128 = new HqcParameters("hqc128", 17669, 46, 384, 16, 31, 15, 66, 75, 75, 16767881, 4, new int[] { 89, 69, 153, 116, 176, 117, 111, 75, 73, 233, 242, 233, 65, 210, 21, 139, 103, 173, 67, 118, 105, 210, 174, 110, 74, 69, 228, 82, 255, 181, 1 }, 128);
+
+        // 192 bits security
+        public static HqcParameters hqc192 = new HqcParameters("hqc192", 35851, 56, 640, 24, 33, 16, 100, 114, 114, 16742417, 5, new int[] { 45, 216, 239, 24, 253, 104, 27, 40, 107, 50, 163, 210, 227, 134, 224, 158, 119, 13, 158, 1, 238, 164, 82, 43, 15, 232, 246, 142, 50, 189, 29, 232, 1 }, 192);
+
+        // 256 bits security
+        public static HqcParameters hqc256 = new HqcParameters("hqc256", 57637, 90, 640, 32, 59, 29, 131, 149, 149, 16772367, 5, new int[] { 49, 167, 49, 39, 200, 121, 124, 91, 240, 63, 148, 71, 150, 123, 87, 101, 32, 215, 159, 71, 201, 115, 97, 210, 186, 183, 141, 217, 123, 12, 31, 243, 180, 219, 152, 239, 99, 141, 4, 246, 191, 144, 8, 232, 47, 27, 141, 178, 130, 64, 124, 47, 39, 188, 216, 48, 199, 187, 1 }, 256);
+
+        private string name;
+        private int n;
+        private int n1;
+        private int n2;
+        private int k;
+        private int g;
+        private int delta;
+        private int w;
+        private int wr;
+        private int we;
+        private int utilRejectionThreshold;
+        private int fft;
+
+        private int[] generatorPoly;
+        private int defaultKeySize;
+
+        internal const int PARAM_M = 8;
+        internal const int GF_MUL_ORDER = 255;
+
+        private HqcEngine hqcEngine;
+
+        private HqcParameters(string name, int n, int n1, int n2, int k, int g, int delta, int w, int wr, int we, int utilRejectionThreshold, int fft, int[] generatorPoly, int defaultKeySize)
+        {
+            this.name = name;
+            this.n = n;
+            this.n1 = n1;
+            this.n2 = n2;
+            this.k = k;
+            this.delta = delta;
+            this.w = w;
+            this.wr = wr;
+            this.we = we;
+            this.generatorPoly = generatorPoly;
+            this.g = g;
+            this.utilRejectionThreshold = utilRejectionThreshold;
+            this.fft = fft;
+            this.defaultKeySize = defaultKeySize;
+            hqcEngine = new HqcEngine(n, n1, n2, k, g, delta, w, wr, we, utilRejectionThreshold, fft, generatorPoly);
+        }
+
+        public int N => n;
+        public int K => k;
+        public int Delta => delta;
+        public int W => w;
+        public int Wr => wr;
+        public  int We => we;
+        public int N1 => n1;
+        public int N2 => n2;
+        public int Sha512Bytes => 512 / 8;
+        public int NBytes => (n + 7) / 8;
+        public int N1n2Bytes => (n1 * n2 + 7) / 8;
+        public int DefaultKeySize => defaultKeySize;
+        internal HqcEngine Engine => hqcEngine;
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcPrivateKeyParameters.cs b/crypto/src/pqc/crypto/hqc/HqcPrivateKeyParameters.cs
new file mode 100644
index 000000000..96963e53c
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcPrivateKeyParameters.cs
@@ -0,0 +1,20 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    public class HqcPrivateKeyParameters : HqcKeyParameters
+    {
+        private byte[] sk;
+
+        public HqcPrivateKeyParameters(HqcParameters param, byte[] sk) : base(true, param)
+        {
+            this.sk = Arrays.Clone(sk);
+        }
+
+        public byte[] PrivateKey => Arrays.Clone(sk);
+        public byte[] GetEncoded()
+        {
+            return PrivateKey;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/HqcPublicKeyParameters.cs b/crypto/src/pqc/crypto/hqc/HqcPublicKeyParameters.cs
new file mode 100644
index 000000000..a87d24704
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/HqcPublicKeyParameters.cs
@@ -0,0 +1,21 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    public class HqcPublicKeyParameters : HqcKeyParameters
+    {
+        private byte[] pk;
+
+        public HqcPublicKeyParameters(HqcParameters param, byte[] pk) : base(false, param)
+        {
+            this.pk = Arrays.Clone(pk);
+        }
+
+        public byte[] PublicKey => Arrays.Clone(pk);
+
+        public byte[] GetEncoded()
+        {
+            return PublicKey;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/ReedMuller.cs b/crypto/src/pqc/crypto/hqc/ReedMuller.cs
new file mode 100644
index 000000000..5f1f8e2bf
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/ReedMuller.cs
@@ -0,0 +1,196 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class ReedMuller
+    {
+        internal class Codeword
+        {
+            internal int[] type32;
+            internal int[] type8;
+
+            public Codeword()
+            {
+                this.type32 = new int[4];
+                this.type8 = new int[16];
+            }
+        }
+
+        static void EncodeSub(Codeword codeword, int m)
+        {
+
+            int word1;
+            word1 = Bit0Mask(m >> 7);
+
+            word1 ^= (int) (Bit0Mask(m >> 0) & 0xaaaaaaaa);
+            word1 ^= (int) (Bit0Mask(m >> 1) & 0xcccccccc);
+            word1 ^= (int) (Bit0Mask(m >> 2) & 0xf0f0f0f0);
+            word1 ^= (int) (Bit0Mask(m >> 3) & 0xff00ff00);
+            word1 ^= (int) (Bit0Mask(m >> 4) & 0xffff0000);
+
+            codeword.type32[0] = word1;
+
+            word1 ^= Bit0Mask(m >> 5);
+            codeword.type32[1] = word1;
+
+            word1 ^= Bit0Mask(m >> 6);
+            codeword.type32[3] = word1;
+
+            word1 ^= Bit0Mask(m >> 5);
+            codeword.type32[2] = word1;
+        }
+
+        private static void HadamardTransform(int[] srcCode, int[] desCode)
+        {
+            int[] srcCodeCopy = Arrays.Clone(srcCode);
+            int[] desCodeCopy = Arrays.Clone(desCode);
+
+            for (int i = 0; i < 7; i++)
+            {
+                for (int j = 0; j < 64; j++)
+                {
+                    desCodeCopy[j] = srcCodeCopy[2 * j] + srcCodeCopy[2 * j + 1];
+                    desCodeCopy[j + 64] = srcCodeCopy[2 * j] - srcCodeCopy[2 * j + 1];
+                }
+
+                //swap srcCode and desCode
+                int[] tmp = srcCodeCopy; srcCodeCopy = desCodeCopy; desCodeCopy = tmp;
+            }
+
+            // swap
+            Array.Copy(desCodeCopy, 0, srcCode, 0, srcCode.Length);
+            Array.Copy(srcCodeCopy, 0, desCode, 0, desCode.Length);
+        }
+
+
+        private static void ExpandThenSum(int[] desCode, Codeword[] srcCode, int off, int mulParam)
+        {
+            for (int i = 0; i < 4; i++)
+            {
+                for (int j = 0; j < 32; j++)
+                {
+                    long ii = srcCode[0 + off].type32[i] >> j & 1;
+                    desCode[i * 32 + j] = srcCode[0 + off].type32[i] >> j & 1;
+                }
+            }
+
+            for (int i = 1; i < mulParam; i++)
+            {
+                for (int j = 0; j < 4; j++)
+                {
+                    for (int k = 0; k < 32; k++)
+                    {
+                        desCode[j * 32 + k] += srcCode[i + off].type32[j] >> k & 1;
+
+                    }
+                }
+            }
+
+        }
+
+        private static int FindPeaks(int[] input)
+        {
+            int peakAbsVal = 0;
+            int peakVal = 0;
+            int peakPos = 0;
+
+            for (int i = 0; i < 128; i++)
+            {
+                int t = input[i];
+                int posMask = t > 0 ? -1 : 0;
+                int abs = (posMask & t) | (~posMask & -t);
+
+                peakVal = abs > peakAbsVal ? t : peakVal;
+                peakPos = abs > peakAbsVal ? i : peakPos;
+                peakAbsVal = abs > peakAbsVal ? abs : peakAbsVal;
+            }
+            int tmp = peakVal > 0 ? 1 : 0;
+            peakPos |= 128 * tmp;
+            return peakPos;
+        }
+
+
+        private static int Bit0Mask(int b)
+        {
+            return (int) ((-(b & 1)) & 0xffffffff);
+        }
+
+        public static void Encode(ulong[] codeword, byte[] m, int n1, int mulParam)
+        {
+            byte[] mBytes = Arrays.Clone(m);
+
+            Codeword[] codewordCopy = new Codeword[n1 * mulParam];
+            for (int i = 0; i < codewordCopy.Length; i++)
+            {
+                codewordCopy[i] = new Codeword();
+            }
+
+            for (int i = 0; i < n1; i++)
+            {
+                int pos = i * mulParam;
+                EncodeSub(codewordCopy[pos], mBytes[i]);
+
+                for (int j = 1; j < mulParam; j++)
+                {
+                    codewordCopy[pos + j] = codewordCopy[pos];
+                }
+            }
+
+
+            int[] cwd64 = new int[codewordCopy.Length * 4];
+            int off = 0;
+            for (int i = 0; i < codewordCopy.Length; i++)
+            {
+                Array.Copy(codewordCopy[i].type32, 0, cwd64, off, codewordCopy[i].type32.Length);
+                off += 4;
+            }
+
+            Utils.FromByte32ArrayToULongArray(codeword, cwd64);
+        }
+
+        public static void Decode(byte[] m, ulong[] codeword, int n1, int mulParam)
+        {
+            byte[] mBytes = Arrays.Clone(m);
+
+            Codeword[] codewordCopy = new Codeword[codeword.Length / 2]; // because each codewordCopy has a 32 bit array size 4
+            int[] byteCodeWords = new int[codeword.Length * 2];
+            Utils.FromULongArrayToByte32Array(byteCodeWords, codeword);
+
+            for (int i = 0; i < codewordCopy.Length; i++)
+            {
+                codewordCopy[i] = new Codeword();
+                for (int j = 0; j < 4; j++)
+                {
+                    codewordCopy[i].type32[j] = byteCodeWords[i * 4 + j];
+                }
+            }
+
+            int[] expandedCodeword = new int[128];
+
+
+            for (int i = 0; i < n1; i++)
+            {
+                ExpandThenSum(expandedCodeword, codewordCopy, i * mulParam, mulParam);
+
+
+                int[] tmp = new int[128];
+                HadamardTransform(expandedCodeword, tmp);
+
+                tmp[0] -= 64 * mulParam;
+                mBytes[i] = (byte)FindPeaks(tmp);
+            }
+
+            int[] cwd64 = new int[codewordCopy.Length * 4];
+            int off = 0;
+            for (int i = 0; i < codewordCopy.Length; i++)
+            {
+                Array.Copy(codewordCopy[i].type32, 0, cwd64, off, codewordCopy[i].type32.Length);
+                off += 4;
+            }
+            Utils.FromByte32ArrayToULongArray(codeword, cwd64);
+            Array.Copy(mBytes, 0, m, 0, m.Length);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/ReedSolomon.cs b/crypto/src/pqc/crypto/hqc/ReedSolomon.cs
new file mode 100644
index 000000000..25a8c7997
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/ReedSolomon.cs
@@ -0,0 +1,263 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class ReedSolomon
+    {
+        static int[,] alpha128 = new int[30,45] { { 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193 }, { 4, 16, 64, 29, 116, 205, 19, 76, 45, 180, 234, 143, 6, 24, 96, 157, 78, 37, 148, 106, 181, 238, 159, 70, 5, 20, 80, 93, 105, 185, 222, 95, 97, 153, 94, 101, 137, 30, 120, 253, 211, 107, 177, 254, 223 }, { 8, 64, 58, 205, 38, 45, 117, 143, 12, 96, 39, 37, 53, 181, 193, 70, 10, 80, 186, 185, 161, 97, 47, 101, 15, 120, 231, 107, 127, 223, 182, 217, 134, 68, 26, 208, 206, 62, 237, 59, 197, 102, 23, 184, 169 }, { 16, 29, 205, 76, 180, 143, 24, 157, 37, 106, 238, 70, 20, 93, 185, 95, 153, 101, 30, 253, 107, 254, 91, 217, 17, 13, 208, 129, 248, 59, 151, 133, 184, 79, 132, 168, 82, 73, 228, 230, 198, 252, 123, 227, 150 }, { 32, 116, 38, 180, 3, 96, 156, 106, 193, 5, 160, 185, 190, 94, 15, 253, 214, 223, 226, 17, 26, 103, 124, 59, 51, 46, 169, 132, 77, 85, 114, 230, 145, 215, 255, 150, 55, 174, 100, 28, 167, 89, 239, 172, 36 }, { 64, 205, 45, 143, 96, 37, 181, 70, 80, 185, 97, 101, 120, 107, 223, 217, 68, 208, 62, 59, 102, 184, 33, 168, 85, 228, 191, 252, 241, 150, 110, 130, 7, 221, 89, 195, 138, 61, 251, 44, 207, 173, 8, 58, 38 }, { 128, 19, 117, 24, 156, 181, 140, 93, 161, 94, 60, 107, 163, 67, 26, 129, 147, 102, 109, 132, 41, 57, 209, 252, 255, 98, 87, 200, 224, 89, 155, 18, 245, 11, 233, 173, 16, 232, 45, 3, 157, 53, 159, 40, 185 }, { 29, 76, 143, 157, 106, 70, 93, 95, 101, 253, 254, 217, 13, 129, 59, 133, 79, 168, 73, 230, 252, 227, 149, 130, 28, 81, 195, 18, 247, 44, 27, 2, 58, 152, 3, 39, 212, 140, 186, 190, 202, 231, 225, 175, 26 }, { 58, 45, 12, 37, 193, 80, 161, 101, 231, 223, 134, 208, 237, 102, 169, 168, 146, 191, 179, 150, 87, 7, 166, 195, 36, 251, 125, 173, 64, 38, 143, 39, 181, 10, 185, 47, 120, 127, 217, 26, 62, 197, 184, 21, 85 }, { 116, 180, 96, 106, 5, 185, 94, 253, 223, 17, 103, 59, 46, 132, 85, 230, 215, 150, 174, 28, 89, 172, 244, 44, 108, 32, 38, 3, 156, 193, 160, 190, 15, 214, 226, 26, 124, 51, 169, 77, 114, 145, 255, 55, 100 }, { 232, 234, 39, 238, 160, 97, 60, 254, 134, 103, 118, 184, 84, 57, 145, 227, 220, 7, 162, 172, 245, 176, 71, 58, 180, 192, 181, 40, 95, 15, 177, 175, 208, 147, 46, 21, 73, 99, 241, 55, 200, 166, 43, 122, 44 }, { 205, 143, 37, 70, 185, 101, 107, 217, 208, 59, 184, 168, 228, 252, 150, 130, 221, 195, 61, 44, 173, 58, 117, 39, 193, 186, 47, 231, 182, 26, 237, 23, 21, 146, 145, 219, 87, 56, 242, 36, 139, 54, 64, 45, 96 }, { 135, 6, 53, 20, 190, 120, 163, 13, 237, 46, 84, 228, 229, 98, 100, 81, 69, 251, 131, 32, 45, 192, 238, 186, 94, 187, 217, 189, 236, 169, 82, 209, 241, 220, 28, 242, 72, 22, 173, 116, 201, 37, 140, 222, 15 }, { 19, 24, 181, 93, 94, 107, 67, 129, 102, 132, 57, 252, 98, 200, 89, 18, 11, 173, 232, 3, 53, 40, 194, 231, 226, 189, 197, 158, 170, 145, 75, 25, 166, 69, 235, 54, 29, 234, 37, 5, 95, 120, 91, 52, 59 }, { 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145 }, { 76, 157, 70, 95, 253, 217, 129, 133, 168, 230, 227, 130, 81, 18, 44, 2, 152, 39, 140, 190, 231, 175, 31, 23, 77, 209, 219, 25, 162, 36, 88, 4, 45, 78, 5, 97, 211, 67, 62, 46, 154, 191, 171, 50, 89 }, { 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1 }, { 45, 37, 80, 101, 223, 208, 102, 168, 191, 150, 7, 195, 251, 173, 38, 39, 10, 47, 127, 26, 197, 21, 115, 219, 100, 242, 245, 54, 205, 96, 70, 97, 107, 68, 59, 33, 228, 241, 130, 89, 61, 207, 58, 12, 193 }, { 90, 148, 186, 30, 226, 62, 109, 73, 179, 174, 162, 61, 131, 232, 96, 140, 153, 127, 52, 51, 168, 99, 98, 56, 172, 22, 8, 234, 212, 185, 240, 67, 237, 79, 114, 241, 25, 121, 245, 108, 19, 39, 20, 188, 223 }, { 180, 106, 185, 253, 17, 59, 132, 230, 150, 28, 172, 44, 32, 3, 193, 190, 214, 26, 51, 77, 145, 55, 167, 36, 233, 116, 96, 5, 94, 223, 103, 46, 85, 215, 174, 89, 244, 108, 38, 156, 160, 15, 226, 124, 169 }, { 117, 181, 161, 107, 26, 102, 41, 252, 87, 89, 245, 173, 45, 53, 185, 231, 68, 197, 168, 145, 110, 166, 61, 54, 38, 37, 186, 120, 134, 59, 21, 191, 196, 221, 36, 207, 205, 39, 80, 15, 217, 237, 33, 115, 150 }, { 234, 238, 97, 254, 103, 184, 57, 227, 7, 172, 176, 58, 192, 40, 15, 175, 147, 21, 99, 55, 166, 122, 216, 45, 106, 222, 107, 52, 133, 85, 123, 50, 195, 11, 32, 12, 140, 188, 182, 124, 158, 115, 49, 224, 36 }, { 201, 159, 47, 91, 124, 33, 209, 149, 166, 244, 71, 117, 238, 194, 223, 31, 79, 115, 98, 167, 61, 216, 90, 181, 190, 254, 206, 218, 213, 150, 224, 72, 54, 152, 106, 161, 177, 189, 184, 114, 171, 56, 18, 131, 38 }, { 143, 70, 101, 217, 59, 168, 252, 130, 195, 44, 58, 39, 186, 231, 26, 23, 146, 219, 56, 36, 54, 45, 181, 97, 223, 62, 33, 191, 110, 89, 251, 8, 12, 10, 15, 134, 197, 41, 179, 100, 86, 125, 205, 37, 185 }, { 3, 5, 15, 17, 51, 85, 255, 28, 36, 108, 180, 193, 94, 226, 59, 77, 215, 100, 172, 233, 38, 106, 190, 223, 124, 132, 145, 174, 239, 44, 116, 156, 185, 214, 103, 169, 230, 55, 89, 235, 32, 96, 160, 253, 26 }, { 6, 20, 120, 13, 46, 228, 98, 81, 251, 32, 192, 186, 187, 189, 169, 209, 220, 242, 22, 116, 37, 222, 254, 62, 132, 63, 130, 43, 250, 38, 212, 194, 182, 147, 77, 179, 141, 9, 54, 180, 159, 101, 67, 151, 85 }, { 12, 80, 231, 208, 169, 191, 87, 195, 125, 38, 181, 47, 217, 197, 85, 219, 221, 245, 8, 96, 186, 107, 206, 33, 145, 130, 86, 207, 45, 193, 101, 134, 102, 146, 150, 166, 251, 64, 39, 185, 127, 62, 21, 252, 100 }, { 24, 93, 107, 129, 132, 252, 200, 18, 173, 3, 40, 231, 189, 158, 145, 25, 69, 54, 234, 5, 120, 52, 218, 191, 174, 43, 207, 90, 35, 15, 136, 92, 115, 220, 239, 125, 76, 238, 101, 17, 133, 228, 149, 121, 44 }, { 48, 105, 127, 248, 77, 241, 224, 247, 64, 156, 95, 182, 236, 170, 150, 162, 11, 205, 212, 94, 134, 133, 213, 110, 239, 250, 45, 35, 30, 26, 218, 99, 130, 69, 108, 143, 40, 211, 206, 132, 229, 7, 144, 2, 96 }, { 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15 } };
+        static int[,] alpha192 = new int[32,55]  { { 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160 }, { 4, 16, 64, 29, 116, 205, 19, 76, 45, 180, 234, 143, 6, 24, 96, 157, 78, 37, 148, 106, 181, 238, 159, 70, 5, 20, 80, 93, 105, 185, 222, 95, 97, 153, 94, 101, 137, 30, 120, 253, 211, 107, 177, 254, 223, 91, 113, 217, 67, 17, 68, 13, 52, 208, 103 }, { 8, 64, 58, 205, 38, 45, 117, 143, 12, 96, 39, 37, 53, 181, 193, 70, 10, 80, 186, 185, 161, 97, 47, 101, 15, 120, 231, 107, 127, 223, 182, 217, 134, 68, 26, 208, 206, 62, 237, 59, 197, 102, 23, 184, 169, 33, 21, 168, 41, 85, 146, 228, 115, 191, 145 }, { 16, 29, 205, 76, 180, 143, 24, 157, 37, 106, 238, 70, 20, 93, 185, 95, 153, 101, 30, 253, 107, 254, 91, 217, 17, 13, 208, 129, 248, 59, 151, 133, 184, 79, 132, 168, 82, 73, 228, 230, 198, 252, 123, 227, 150, 149, 165, 130, 200, 28, 221, 81, 121, 195, 172 }, { 32, 116, 38, 180, 3, 96, 156, 106, 193, 5, 160, 185, 190, 94, 15, 253, 214, 223, 226, 17, 26, 103, 124, 59, 51, 46, 169, 132, 77, 85, 114, 230, 145, 215, 255, 150, 55, 174, 100, 28, 167, 89, 239, 172, 36, 244, 235, 44, 233, 108, 1, 32, 116, 38, 180 }, { 64, 205, 45, 143, 96, 37, 181, 70, 80, 185, 97, 101, 120, 107, 223, 217, 68, 208, 62, 59, 102, 184, 33, 168, 85, 228, 191, 252, 241, 150, 110, 130, 7, 221, 89, 195, 138, 61, 251, 44, 207, 173, 8, 58, 38, 117, 12, 39, 53, 193, 10, 186, 161, 47, 15 }, { 128, 19, 117, 24, 156, 181, 140, 93, 161, 94, 60, 107, 163, 67, 26, 129, 147, 102, 109, 132, 41, 57, 209, 252, 255, 98, 87, 200, 224, 89, 155, 18, 245, 11, 233, 173, 16, 232, 45, 3, 157, 53, 159, 40, 185, 194, 137, 231, 254, 226, 68, 189, 248, 197, 46 }, { 29, 76, 143, 157, 106, 70, 93, 95, 101, 253, 254, 217, 13, 129, 59, 133, 79, 168, 73, 230, 252, 227, 149, 130, 28, 81, 195, 18, 247, 44, 27, 2, 58, 152, 3, 39, 212, 140, 186, 190, 202, 231, 225, 175, 26, 31, 118, 23, 158, 77, 146, 209, 229, 219, 55 }, { 58, 45, 12, 37, 193, 80, 161, 101, 231, 223, 134, 208, 237, 102, 169, 168, 146, 191, 179, 150, 87, 7, 166, 195, 36, 251, 125, 173, 64, 38, 143, 39, 181, 10, 185, 47, 120, 127, 217, 26, 62, 197, 184, 21, 85, 115, 252, 219, 110, 100, 221, 242, 138, 245, 44 }, { 116, 180, 96, 106, 5, 185, 94, 253, 223, 17, 103, 59, 46, 132, 85, 230, 215, 150, 174, 28, 89, 172, 244, 44, 108, 32, 38, 3, 156, 193, 160, 190, 15, 214, 226, 26, 124, 51, 169, 77, 114, 145, 255, 55, 100, 167, 239, 36, 235, 233, 1, 116, 180, 96, 106 }, { 232, 234, 39, 238, 160, 97, 60, 254, 134, 103, 118, 184, 84, 57, 145, 227, 220, 7, 162, 172, 245, 176, 71, 58, 180, 192, 181, 40, 95, 15, 177, 175, 208, 147, 46, 21, 73, 99, 241, 55, 200, 166, 43, 122, 44, 216, 128, 45, 48, 106, 10, 222, 202, 107, 226 }, { 205, 143, 37, 70, 185, 101, 107, 217, 208, 59, 184, 168, 228, 252, 150, 130, 221, 195, 61, 44, 173, 58, 117, 39, 193, 186, 47, 231, 182, 26, 237, 23, 21, 146, 145, 219, 87, 56, 242, 36, 139, 54, 64, 45, 96, 181, 80, 97, 120, 223, 68, 62, 102, 33, 85 }, { 135, 6, 53, 20, 190, 120, 163, 13, 237, 46, 84, 228, 229, 98, 100, 81, 69, 251, 131, 32, 45, 192, 238, 186, 94, 187, 217, 189, 236, 169, 82, 209, 241, 220, 28, 242, 72, 22, 173, 116, 201, 37, 140, 222, 15, 254, 34, 62, 204, 132, 146, 63, 75, 130, 167 }, { 19, 24, 181, 93, 94, 107, 67, 129, 102, 132, 57, 252, 98, 200, 89, 18, 11, 173, 232, 3, 53, 40, 194, 231, 226, 189, 197, 158, 170, 145, 75, 25, 166, 69, 235, 54, 29, 234, 37, 5, 95, 120, 91, 52, 59, 218, 82, 191, 227, 174, 221, 43, 247, 207, 32 }, { 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185 }, { 76, 157, 70, 95, 253, 217, 129, 133, 168, 230, 227, 130, 81, 18, 44, 2, 152, 39, 140, 190, 231, 175, 31, 23, 77, 209, 219, 25, 162, 36, 88, 4, 45, 78, 5, 97, 211, 67, 62, 46, 154, 191, 171, 50, 89, 72, 176, 8, 90, 156, 10, 194, 187, 134, 124 }, { 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215 }, { 45, 37, 80, 101, 223, 208, 102, 168, 191, 150, 7, 195, 251, 173, 38, 39, 10, 47, 127, 26, 197, 21, 115, 219, 100, 242, 245, 54, 205, 96, 70, 97, 107, 68, 59, 33, 228, 241, 130, 89, 61, 207, 58, 12, 193, 161, 231, 134, 237, 169, 146, 179, 87, 166, 36 }, { 90, 148, 186, 30, 226, 62, 109, 73, 179, 174, 162, 61, 131, 232, 96, 140, 153, 127, 52, 51, 168, 99, 98, 56, 172, 22, 8, 234, 212, 185, 240, 67, 237, 79, 114, 241, 25, 121, 245, 108, 19, 39, 20, 188, 223, 189, 133, 41, 63, 55, 221, 9, 176, 64, 3 }, { 180, 106, 185, 253, 17, 59, 132, 230, 150, 28, 172, 44, 32, 3, 193, 190, 214, 26, 51, 77, 145, 55, 167, 36, 233, 116, 96, 5, 94, 223, 103, 46, 85, 215, 174, 89, 244, 108, 38, 156, 160, 15, 226, 124, 169, 114, 255, 100, 239, 235, 1, 180, 106, 185, 253 }, { 117, 181, 161, 107, 26, 102, 41, 252, 87, 89, 245, 173, 45, 53, 185, 231, 68, 197, 168, 145, 110, 166, 61, 54, 38, 37, 186, 120, 134, 59, 21, 191, 196, 221, 36, 207, 205, 39, 80, 15, 217, 237, 33, 115, 150, 56, 138, 125, 58, 96, 10, 101, 182, 62, 169 }, { 234, 238, 97, 254, 103, 184, 57, 227, 7, 172, 176, 58, 192, 40, 15, 175, 147, 21, 99, 55, 166, 122, 216, 45, 106, 222, 107, 52, 133, 85, 123, 50, 195, 11, 32, 12, 140, 188, 182, 124, 158, 115, 49, 224, 36, 131, 19, 37, 105, 253, 68, 151, 154, 252, 174 }, { 201, 159, 47, 91, 124, 33, 209, 149, 166, 244, 71, 117, 238, 194, 223, 31, 79, 115, 98, 167, 61, 216, 90, 181, 190, 254, 206, 218, 213, 150, 224, 72, 54, 152, 106, 161, 177, 189, 184, 114, 171, 56, 18, 131, 38, 148, 111, 107, 104, 46, 146, 227, 14, 138, 233 }, { 143, 70, 101, 217, 59, 168, 252, 130, 195, 44, 58, 39, 186, 231, 26, 23, 146, 219, 56, 36, 54, 45, 181, 97, 223, 62, 33, 191, 110, 89, 251, 8, 12, 10, 15, 134, 197, 41, 179, 100, 86, 125, 205, 37, 185, 107, 208, 184, 228, 150, 221, 61, 173, 117, 193 }, { 3, 5, 15, 17, 51, 85, 255, 28, 36, 108, 180, 193, 94, 226, 59, 77, 215, 100, 172, 233, 38, 106, 190, 223, 124, 132, 145, 174, 239, 44, 116, 156, 185, 214, 103, 169, 230, 55, 89, 235, 32, 96, 160, 253, 26, 46, 114, 150, 167, 244, 1, 3, 5, 15, 17 }, { 6, 20, 120, 13, 46, 228, 98, 81, 251, 32, 192, 186, 187, 189, 169, 209, 220, 242, 22, 116, 37, 222, 254, 62, 132, 63, 130, 43, 250, 38, 212, 194, 182, 147, 77, 179, 141, 9, 54, 180, 159, 101, 67, 151, 85, 227, 112, 61, 142, 3, 10, 60, 136, 23, 114 }, { 12, 80, 231, 208, 169, 191, 87, 195, 125, 38, 181, 47, 217, 197, 85, 219, 221, 245, 8, 96, 186, 107, 206, 33, 145, 130, 86, 207, 45, 193, 101, 134, 102, 146, 150, 166, 251, 64, 39, 185, 127, 62, 21, 252, 100, 138, 54, 117, 70, 15, 68, 23, 228, 196, 89 }, { 24, 93, 107, 129, 132, 252, 200, 18, 173, 3, 40, 231, 189, 158, 145, 25, 69, 54, 234, 5, 120, 52, 218, 191, 174, 43, 207, 90, 35, 15, 136, 92, 115, 220, 239, 125, 76, 238, 101, 17, 133, 228, 149, 121, 44, 135, 212, 47, 175, 51, 146, 49, 162, 139, 116 }, { 48, 105, 127, 248, 77, 241, 224, 247, 64, 156, 95, 182, 236, 170, 150, 162, 11, 205, 212, 94, 134, 133, 213, 110, 239, 250, 45, 35, 30, 26, 218, 99, 130, 69, 108, 143, 40, 211, 206, 132, 229, 7, 144, 2, 96, 210, 254, 237, 154, 255, 221, 243, 128, 37, 190 }, { 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59 }, { 192, 222, 182, 151, 114, 110, 155, 27, 143, 160, 177, 237, 82, 75, 89, 88, 152, 70, 240, 103, 21, 123, 224, 251, 116, 212, 101, 136, 218, 145, 200, 144, 8, 78, 190, 217, 204, 183, 87, 172, 216, 12, 105, 225, 59, 170, 98, 242, 250, 180, 10, 211, 31, 168, 255 }, { 157, 95, 217, 133, 230, 130, 18, 2, 39, 190, 175, 23, 209, 25, 36, 4, 78, 97, 67, 46, 191, 50, 72, 8, 156, 194, 134, 92, 99, 100, 144, 16, 37, 153, 17, 184, 198, 200, 61, 32, 74, 47, 34, 109, 145, 141, 122, 64, 148, 94, 68, 218, 63, 7, 244 } };
+        static int[,] alpha256 = new int[58,89]  { { 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225 }, { 4, 16, 64, 29, 116, 205, 19, 76, 45, 180, 234, 143, 6, 24, 96, 157, 78, 37, 148, 106, 181, 238, 159, 70, 5, 20, 80, 93, 105, 185, 222, 95, 97, 153, 94, 101, 137, 30, 120, 253, 211, 107, 177, 254, 223, 91, 113, 217, 67, 17, 68, 13, 52, 208, 103, 129, 62, 248, 199, 59, 236, 151, 102, 133, 46, 184, 218, 79, 33, 132, 42, 168, 154, 82, 85, 73, 57, 228, 183, 230, 191, 198, 63, 252, 215, 123, 241, 227, 171 }, { 8, 64, 58, 205, 38, 45, 117, 143, 12, 96, 39, 37, 53, 181, 193, 70, 10, 80, 186, 185, 161, 97, 47, 101, 15, 120, 231, 107, 127, 223, 182, 217, 134, 68, 26, 208, 206, 62, 237, 59, 197, 102, 23, 184, 169, 33, 21, 168, 41, 85, 146, 228, 115, 191, 145, 252, 179, 241, 219, 150, 196, 110, 87, 130, 100, 7, 56, 221, 166, 89, 242, 195, 86, 138, 36, 61, 245, 251, 139, 44, 125, 207, 54, 173, 1, 8, 64, 58, 205 }, { 16, 29, 205, 76, 180, 143, 24, 157, 37, 106, 238, 70, 20, 93, 185, 95, 153, 101, 30, 253, 107, 254, 91, 217, 17, 13, 208, 129, 248, 59, 151, 133, 184, 79, 132, 168, 82, 73, 228, 230, 198, 252, 123, 227, 150, 149, 165, 130, 200, 28, 221, 81, 121, 195, 172, 18, 61, 247, 203, 44, 250, 27, 173, 2, 32, 58, 135, 152, 117, 3, 48, 39, 74, 212, 193, 140, 40, 186, 111, 190, 47, 202, 60, 231, 214, 225, 182, 175, 34 }, { 32, 116, 38, 180, 3, 96, 156, 106, 193, 5, 160, 185, 190, 94, 15, 253, 214, 223, 226, 17, 26, 103, 124, 59, 51, 46, 169, 132, 77, 85, 114, 230, 145, 215, 255, 150, 55, 174, 100, 28, 167, 89, 239, 172, 36, 244, 235, 44, 233, 108, 1, 32, 116, 38, 180, 3, 96, 156, 106, 193, 5, 160, 185, 190, 94, 15, 253, 214, 223, 226, 17, 26, 103, 124, 59, 51, 46, 169, 132, 77, 85, 114, 230, 145, 215, 255, 150, 55, 174 }, { 64, 205, 45, 143, 96, 37, 181, 70, 80, 185, 97, 101, 120, 107, 223, 217, 68, 208, 62, 59, 102, 184, 33, 168, 85, 228, 191, 252, 241, 150, 110, 130, 7, 221, 89, 195, 138, 61, 251, 44, 207, 173, 8, 58, 38, 117, 12, 39, 53, 193, 10, 186, 161, 47, 15, 231, 127, 182, 134, 26, 206, 237, 197, 23, 169, 21, 41, 146, 115, 145, 179, 219, 196, 87, 100, 56, 166, 242, 86, 36, 245, 139, 125, 54, 1, 64, 205, 45, 143 }, { 128, 19, 117, 24, 156, 181, 140, 93, 161, 94, 60, 107, 163, 67, 26, 129, 147, 102, 109, 132, 41, 57, 209, 252, 255, 98, 87, 200, 224, 89, 155, 18, 245, 11, 233, 173, 16, 232, 45, 3, 157, 53, 159, 40, 185, 194, 137, 231, 254, 226, 68, 189, 248, 197, 46, 158, 168, 170, 183, 145, 123, 75, 110, 25, 28, 166, 249, 69, 61, 235, 176, 54, 2, 29, 38, 234, 48, 37, 119, 5, 186, 95, 188, 120, 214, 91, 134, 52, 31 }, { 29, 76, 143, 157, 106, 70, 93, 95, 101, 253, 254, 217, 13, 129, 59, 133, 79, 168, 73, 230, 252, 227, 149, 130, 28, 81, 195, 18, 247, 44, 27, 2, 58, 152, 3, 39, 212, 140, 186, 190, 202, 231, 225, 175, 26, 31, 118, 23, 158, 77, 146, 209, 229, 219, 55, 25, 56, 162, 155, 36, 243, 88, 54, 4, 116, 45, 6, 78, 181, 5, 105, 97, 137, 211, 223, 67, 52, 62, 236, 46, 33, 154, 57, 191, 215, 171, 110, 50, 112 }, { 58, 45, 12, 37, 193, 80, 161, 101, 231, 223, 134, 208, 237, 102, 169, 168, 146, 191, 179, 150, 87, 7, 166, 195, 36, 251, 125, 173, 64, 38, 143, 39, 181, 10, 185, 47, 120, 127, 217, 26, 62, 197, 184, 21, 85, 115, 252, 219, 110, 100, 221, 242, 138, 245, 44, 54, 8, 205, 117, 96, 53, 70, 186, 97, 15, 107, 182, 68, 206, 59, 23, 33, 41, 228, 145, 241, 196, 130, 56, 89, 86, 61, 139, 207, 1, 58, 45, 12, 37 }, { 116, 180, 96, 106, 5, 185, 94, 253, 223, 17, 103, 59, 46, 132, 85, 230, 215, 150, 174, 28, 89, 172, 244, 44, 108, 32, 38, 3, 156, 193, 160, 190, 15, 214, 226, 26, 124, 51, 169, 77, 114, 145, 255, 55, 100, 167, 239, 36, 235, 233, 1, 116, 180, 96, 106, 5, 185, 94, 253, 223, 17, 103, 59, 46, 132, 85, 230, 215, 150, 174, 28, 89, 172, 244, 44, 108, 32, 38, 3, 156, 193, 160, 190, 15, 214, 226, 26, 124, 51 }, { 232, 234, 39, 238, 160, 97, 60, 254, 134, 103, 118, 184, 84, 57, 145, 227, 220, 7, 162, 172, 245, 176, 71, 58, 180, 192, 181, 40, 95, 15, 177, 175, 208, 147, 46, 21, 73, 99, 241, 55, 200, 166, 43, 122, 44, 216, 128, 45, 48, 106, 10, 222, 202, 107, 226, 52, 237, 133, 66, 85, 209, 123, 196, 50, 167, 195, 144, 11, 54, 32, 76, 12, 148, 140, 185, 188, 211, 182, 13, 124, 102, 158, 82, 115, 215, 49, 130, 224, 249 }, { 205, 143, 37, 70, 185, 101, 107, 217, 208, 59, 184, 168, 228, 252, 150, 130, 221, 195, 61, 44, 173, 58, 117, 39, 193, 186, 47, 231, 182, 26, 237, 23, 21, 146, 145, 219, 87, 56, 242, 36, 139, 54, 64, 45, 96, 181, 80, 97, 120, 223, 68, 62, 102, 33, 85, 191, 241, 110, 7, 89, 138, 251, 207, 8, 38, 12, 53, 10, 161, 15, 127, 134, 206, 197, 169, 41, 115, 179, 196, 100, 166, 86, 245, 125, 1, 205, 143, 37, 70 }, { 135, 6, 53, 20, 190, 120, 163, 13, 237, 46, 84, 228, 229, 98, 100, 81, 69, 251, 131, 32, 45, 192, 238, 186, 94, 187, 217, 189, 236, 169, 82, 209, 241, 220, 28, 242, 72, 22, 173, 116, 201, 37, 140, 222, 15, 254, 34, 62, 204, 132, 146, 63, 75, 130, 167, 43, 245, 250, 4, 38, 24, 212, 80, 194, 253, 182, 52, 147, 184, 77, 183, 179, 149, 141, 89, 9, 203, 54, 128, 180, 39, 159, 210, 101, 214, 67, 206, 151, 158 }, { 19, 24, 181, 93, 94, 107, 67, 129, 102, 132, 57, 252, 98, 200, 89, 18, 11, 173, 232, 3, 53, 40, 194, 231, 226, 189, 197, 158, 170, 145, 75, 25, 166, 69, 235, 54, 29, 234, 37, 5, 95, 120, 91, 52, 59, 218, 82, 191, 227, 174, 221, 43, 247, 207, 32, 90, 39, 35, 111, 15, 225, 136, 237, 92, 77, 115, 246, 220, 56, 239, 122, 125, 4, 76, 96, 238, 105, 101, 177, 17, 62, 133, 42, 228, 215, 149, 7, 121, 72 }, { 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185, 15, 223, 26, 59, 169, 85, 145, 150, 100, 89, 36, 44, 1, 38, 96, 193, 185 }, { 76, 157, 70, 95, 253, 217, 129, 133, 168, 230, 227, 130, 81, 18, 44, 2, 152, 39, 140, 190, 231, 175, 31, 23, 77, 209, 219, 25, 162, 36, 88, 4, 45, 78, 5, 97, 211, 67, 62, 46, 154, 191, 171, 50, 89, 72, 176, 8, 90, 156, 10, 194, 187, 134, 124, 92, 41, 99, 75, 100, 178, 144, 125, 16, 180, 37, 20, 153, 107, 17, 248, 184, 82, 198, 150, 200, 121, 61, 250, 32, 117, 74, 40, 47, 214, 34, 237, 109, 164 }, { 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11, 1, 152, 78, 10, 153, 214, 68, 147, 79, 146, 215, 220, 221, 69, 11 }, { 45, 37, 80, 101, 223, 208, 102, 168, 191, 150, 7, 195, 251, 173, 38, 39, 10, 47, 127, 26, 197, 21, 115, 219, 100, 242, 245, 54, 205, 96, 70, 97, 107, 68, 59, 33, 228, 241, 130, 89, 61, 207, 58, 12, 193, 161, 231, 134, 237, 169, 146, 179, 87, 166, 36, 125, 64, 143, 181, 185, 120, 217, 62, 184, 85, 252, 110, 221, 138, 44, 8, 117, 53, 186, 15, 182, 206, 23, 41, 145, 196, 56, 86, 139, 1, 45, 37, 80, 101 }, { 90, 148, 186, 30, 226, 62, 109, 73, 179, 174, 162, 61, 131, 232, 96, 140, 153, 127, 52, 51, 168, 99, 98, 56, 172, 22, 8, 234, 212, 185, 240, 67, 237, 79, 114, 241, 25, 121, 245, 108, 19, 39, 20, 188, 223, 189, 133, 41, 63, 55, 221, 9, 176, 64, 3, 238, 161, 211, 34, 59, 66, 183, 219, 200, 239, 251, 71, 152, 37, 160, 137, 182, 129, 92, 85, 229, 165, 166, 72, 233, 58, 24, 35, 97, 214, 13, 197, 42, 209 }, { 180, 106, 185, 253, 17, 59, 132, 230, 150, 28, 172, 44, 32, 3, 193, 190, 214, 26, 51, 77, 145, 55, 167, 36, 233, 116, 96, 5, 94, 223, 103, 46, 85, 215, 174, 89, 244, 108, 38, 156, 160, 15, 226, 124, 169, 114, 255, 100, 239, 235, 1, 180, 106, 185, 253, 17, 59, 132, 230, 150, 28, 172, 44, 32, 3, 193, 190, 214, 26, 51, 77, 145, 55, 167, 36, 233, 116, 96, 5, 94, 223, 103, 46, 85, 215, 174, 89, 244, 108 }, { 117, 181, 161, 107, 26, 102, 41, 252, 87, 89, 245, 173, 45, 53, 185, 231, 68, 197, 168, 145, 110, 166, 61, 54, 38, 37, 186, 120, 134, 59, 21, 191, 196, 221, 36, 207, 205, 39, 80, 15, 217, 237, 33, 115, 150, 56, 138, 125, 58, 96, 10, 101, 182, 62, 169, 228, 219, 7, 86, 44, 64, 12, 70, 47, 223, 206, 184, 146, 241, 100, 195, 139, 8, 143, 193, 97, 127, 208, 23, 85, 179, 130, 242, 251, 1, 117, 181, 161, 107 }, { 234, 238, 97, 254, 103, 184, 57, 227, 7, 172, 176, 58, 192, 40, 15, 175, 147, 21, 99, 55, 166, 122, 216, 45, 106, 222, 107, 52, 133, 85, 123, 50, 195, 11, 32, 12, 140, 188, 182, 124, 158, 115, 49, 224, 36, 131, 19, 37, 105, 253, 68, 151, 154, 252, 174, 121, 251, 2, 201, 193, 194, 225, 206, 109, 114, 219, 14, 69, 125, 116, 157, 80, 30, 67, 59, 42, 198, 110, 81, 244, 173, 90, 212, 161, 214, 104, 23, 170, 246 }, { 201, 159, 47, 91, 124, 33, 209, 149, 166, 244, 71, 117, 238, 194, 223, 31, 79, 115, 98, 167, 61, 216, 90, 181, 190, 254, 206, 218, 213, 150, 224, 72, 54, 152, 106, 161, 177, 189, 184, 114, 171, 56, 18, 131, 38, 148, 111, 107, 104, 46, 146, 227, 14, 138, 233, 135, 37, 210, 211, 26, 133, 170, 241, 141, 172, 125, 232, 78, 186, 253, 136, 102, 164, 123, 100, 43, 88, 58, 157, 160, 120, 34, 151, 41, 215, 25, 195, 22, 128 }, { 143, 70, 101, 217, 59, 168, 252, 130, 195, 44, 58, 39, 186, 231, 26, 23, 146, 219, 56, 36, 54, 45, 181, 97, 223, 62, 33, 191, 110, 89, 251, 8, 12, 10, 15, 134, 197, 41, 179, 100, 86, 125, 205, 37, 185, 107, 208, 184, 228, 150, 221, 61, 173, 117, 193, 47, 182, 237, 21, 145, 87, 242, 139, 64, 96, 80, 120, 68, 102, 85, 241, 7, 138, 207, 38, 53, 161, 127, 206, 169, 115, 196, 166, 245, 1, 143, 70, 101, 217 }, { 3, 5, 15, 17, 51, 85, 255, 28, 36, 108, 180, 193, 94, 226, 59, 77, 215, 100, 172, 233, 38, 106, 190, 223, 124, 132, 145, 174, 239, 44, 116, 156, 185, 214, 103, 169, 230, 55, 89, 235, 32, 96, 160, 253, 26, 46, 114, 150, 167, 244, 1, 3, 5, 15, 17, 51, 85, 255, 28, 36, 108, 180, 193, 94, 226, 59, 77, 215, 100, 172, 233, 38, 106, 190, 223, 124, 132, 145, 174, 239, 44, 116, 156, 185, 214, 103, 169, 230, 55 }, { 6, 20, 120, 13, 46, 228, 98, 81, 251, 32, 192, 186, 187, 189, 169, 209, 220, 242, 22, 116, 37, 222, 254, 62, 132, 63, 130, 43, 250, 38, 212, 194, 182, 147, 77, 179, 141, 9, 54, 180, 159, 101, 67, 151, 85, 227, 112, 61, 142, 3, 10, 60, 136, 23, 114, 49, 166, 243, 16, 96, 93, 211, 208, 218, 230, 110, 121, 11, 58, 156, 111, 127, 31, 66, 145, 65, 155, 125, 19, 106, 97, 91, 199, 168, 215, 200, 138, 27, 90 }, { 12, 80, 231, 208, 169, 191, 87, 195, 125, 38, 181, 47, 217, 197, 85, 219, 221, 245, 8, 96, 186, 107, 206, 33, 145, 130, 86, 207, 45, 193, 101, 134, 102, 146, 150, 166, 251, 64, 39, 185, 127, 62, 21, 252, 100, 138, 54, 117, 70, 15, 68, 23, 228, 196, 89, 139, 58, 37, 161, 223, 237, 168, 179, 7, 36, 173, 143, 10, 120, 26, 184, 115, 110, 242, 44, 205, 53, 97, 182, 59, 41, 241, 56, 61, 1, 12, 80, 231, 208 }, { 24, 93, 107, 129, 132, 252, 200, 18, 173, 3, 40, 231, 189, 158, 145, 25, 69, 54, 234, 5, 120, 52, 218, 191, 174, 43, 207, 90, 35, 15, 136, 92, 115, 220, 239, 125, 76, 238, 101, 17, 133, 228, 149, 121, 44, 135, 212, 47, 175, 51, 146, 49, 162, 139, 116, 148, 97, 113, 236, 85, 171, 83, 251, 128, 156, 161, 163, 147, 41, 255, 224, 245, 16, 157, 185, 254, 248, 168, 123, 28, 61, 2, 48, 186, 214, 31, 21, 229, 141 }, { 48, 105, 127, 248, 77, 241, 224, 247, 64, 156, 95, 182, 236, 170, 150, 162, 11, 205, 212, 94, 134, 133, 213, 110, 239, 250, 45, 35, 30, 26, 218, 99, 130, 69, 108, 143, 40, 211, 206, 132, 229, 7, 144, 2, 96, 210, 254, 237, 154, 255, 221, 243, 128, 37, 190, 113, 197, 73, 49, 89, 22, 135, 181, 188, 17, 23, 183, 220, 195, 233, 90, 70, 60, 52, 169, 198, 25, 138, 216, 3, 80, 187, 129, 21, 215, 14, 61, 4, 192 }, { 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59, 85, 150, 89, 44, 38, 193, 15, 26, 169, 145, 100, 36, 1, 96, 185, 223, 59 }, { 192, 222, 182, 151, 114, 110, 155, 27, 143, 160, 177, 237, 82, 75, 89, 88, 152, 70, 240, 103, 21, 123, 224, 251, 116, 212, 101, 136, 218, 145, 200, 144, 8, 78, 190, 217, 204, 183, 87, 172, 216, 12, 105, 225, 59, 170, 98, 242, 250, 180, 10, 211, 31, 168, 255, 83, 139, 135, 238, 15, 52, 158, 252, 14, 244, 64, 74, 153, 134, 46, 209, 130, 9, 142, 96, 111, 91, 197, 57, 55, 195, 131, 201, 80, 214, 248, 41, 171, 162 }, { 157, 95, 217, 133, 230, 130, 18, 2, 39, 190, 175, 23, 209, 25, 36, 4, 78, 97, 67, 46, 191, 50, 72, 8, 156, 194, 134, 92, 99, 100, 144, 16, 37, 153, 17, 184, 198, 200, 61, 32, 74, 47, 34, 109, 145, 141, 122, 64, 148, 94, 68, 218, 63, 7, 244, 128, 53, 188, 136, 169, 126, 14, 245, 29, 106, 101, 13, 79, 252, 28, 247, 58, 212, 202, 26, 158, 229, 56, 243, 116, 181, 137, 52, 33, 215, 112, 251, 232, 119 }, { 39, 97, 134, 184, 145, 7, 245, 58, 181, 15, 208, 21, 241, 166, 44, 45, 10, 107, 237, 85, 196, 195, 54, 12, 185, 182, 102, 115, 130, 36, 8, 37, 47, 68, 169, 252, 56, 251, 205, 193, 120, 206, 168, 219, 89, 125, 117, 80, 127, 59, 146, 110, 86, 173, 96, 161, 217, 23, 191, 100, 61, 64, 53, 101, 26, 33, 179, 221, 139, 38, 70, 231, 62, 41, 150, 242, 207, 143, 186, 223, 197, 228, 87, 138, 1, 39, 97, 134, 184 }, { 78, 153, 68, 79, 215, 221, 11, 152, 10, 214, 147, 146, 220, 69, 1, 78, 153, 68, 79, 215, 221, 11, 152, 10, 214, 147, 146, 220, 69, 1, 78, 153, 68, 79, 215, 221, 11, 152, 10, 214, 147, 146, 220, 69, 1, 78, 153, 68, 79, 215, 221, 11, 152, 10, 214, 147, 146, 220, 69, 1, 78, 153, 68, 79, 215, 221, 11, 152, 10, 214, 147, 146, 220, 69, 1, 78, 153, 68, 79, 215, 221, 11, 152, 10, 214, 147, 146, 220, 69 }, { 156, 94, 26, 132, 255, 89, 233, 3, 185, 226, 46, 145, 28, 235, 38, 5, 214, 59, 114, 174, 36, 32, 106, 15, 103, 77, 150, 239, 108, 96, 190, 17, 169, 215, 167, 44, 180, 160, 223, 51, 230, 100, 244, 116, 193, 253, 124, 85, 55, 172, 1, 156, 94, 26, 132, 255, 89, 233, 3, 185, 226, 46, 145, 28, 235, 38, 5, 214, 59, 114, 174, 36, 32, 106, 15, 103, 77, 150, 239, 108, 96, 190, 17, 169, 215, 167, 44, 180, 160 }, { 37, 101, 208, 168, 150, 195, 173, 39, 47, 26, 21, 219, 242, 54, 96, 97, 68, 33, 241, 89, 207, 12, 161, 134, 169, 179, 166, 125, 143, 185, 217, 184, 252, 221, 44, 117, 186, 182, 23, 145, 56, 139, 45, 80, 223, 102, 191, 7, 251, 38, 10, 127, 197, 115, 100, 245, 205, 70, 107, 59, 228, 130, 61, 58, 193, 231, 237, 146, 87, 36, 64, 181, 120, 62, 85, 110, 138, 8, 53, 15, 206, 41, 196, 86, 1, 37, 101, 208, 168 }, { 74, 137, 206, 82, 55, 138, 16, 212, 120, 124, 73, 87, 72, 29, 193, 211, 147, 228, 25, 244, 205, 140, 177, 197, 230, 141, 251, 76, 40, 223, 204, 198, 56, 11, 180, 186, 113, 92, 252, 167, 176, 143, 111, 67, 169, 123, 162, 207, 24, 190, 68, 66, 227, 242, 108, 157, 47, 52, 84, 150, 155, 142, 37, 202, 103, 41, 149, 69, 8, 106, 60, 62, 170, 165, 36, 128, 238, 231, 199, 114, 130, 122, 232, 70, 214, 236, 115, 200, 243 }, { 148, 30, 62, 73, 174, 61, 232, 140, 127, 51, 99, 56, 22, 234, 185, 67, 79, 241, 121, 108, 39, 188, 189, 41, 55, 9, 64, 238, 211, 59, 183, 200, 251, 152, 160, 182, 92, 229, 166, 233, 24, 97, 13, 42, 150, 43, 2, 53, 60, 124, 146, 65, 122, 205, 5, 254, 102, 198, 112, 44, 201, 111, 134, 158, 255, 242, 216, 78, 101, 103, 82, 110, 18, 128, 193, 187, 118, 115, 141, 235, 45, 93, 113, 184, 215, 81, 207, 48, 194 }, { 53, 120, 237, 228, 100, 251, 45, 186, 217, 169, 241, 242, 173, 37, 15, 62, 146, 130, 245, 38, 80, 182, 184, 179, 89, 54, 39, 101, 206, 85, 87, 61, 205, 10, 223, 23, 252, 166, 207, 96, 47, 208, 41, 110, 36, 58, 70, 127, 102, 145, 221, 125, 12, 97, 26, 168, 196, 138, 64, 193, 107, 197, 191, 56, 44, 143, 161, 68, 21, 150, 86, 8, 181, 231, 59, 115, 7, 139, 117, 185, 134, 33, 219, 195, 1, 53, 120, 237, 228 }, { 106, 253, 59, 230, 28, 44, 3, 190, 26, 77, 55, 36, 116, 5, 223, 46, 215, 89, 108, 156, 15, 124, 114, 100, 235, 180, 185, 17, 132, 150, 172, 32, 193, 214, 51, 145, 167, 233, 96, 94, 103, 85, 174, 244, 38, 160, 226, 169, 255, 239, 1, 106, 253, 59, 230, 28, 44, 3, 190, 26, 77, 55, 36, 116, 5, 223, 46, 215, 89, 108, 156, 15, 124, 114, 100, 235, 180, 185, 17, 132, 150, 172, 32, 193, 214, 51, 145, 167, 233 }, { 212, 211, 197, 198, 167, 207, 157, 202, 62, 114, 200, 139, 201, 95, 26, 154, 220, 61, 19, 160, 217, 158, 171, 86, 32, 159, 127, 133, 229, 89, 216, 74, 120, 147, 230, 56, 176, 24, 47, 103, 170, 130, 243, 90, 185, 34, 42, 196, 18, 116, 10, 91, 109, 241, 239, 2, 181, 187, 151, 145, 83, 131, 39, 137, 124, 228, 141, 11, 143, 190, 52, 41, 165, 122, 38, 93, 175, 33, 75, 172, 64, 35, 254, 23, 215, 178, 173, 148, 240 }, { 181, 107, 102, 252, 89, 173, 53, 231, 197, 145, 166, 54, 37, 120, 59, 191, 221, 207, 39, 15, 237, 115, 56, 125, 96, 101, 62, 228, 7, 44, 12, 47, 206, 146, 100, 139, 143, 97, 208, 85, 130, 251, 117, 161, 26, 41, 87, 245, 45, 185, 68, 168, 110, 61, 38, 186, 134, 21, 196, 36, 205, 80, 217, 33, 150, 138, 58, 10, 182, 169, 219, 86, 64, 70, 223, 184, 241, 195, 8, 193, 127, 23, 179, 242, 1, 181, 107, 102, 252 }, { 119, 177, 23, 123, 239, 8, 159, 225, 184, 255, 43, 64, 140, 91, 169, 171, 69, 58, 20, 226, 33, 49, 18, 205, 160, 67, 21, 149, 144, 38, 105, 34, 168, 220, 244, 45, 111, 13, 41, 174, 243, 117, 95, 104, 85, 25, 203, 143, 194, 103, 146, 200, 22, 12, 94, 31, 228, 14, 176, 96, 202, 248, 115, 112, 233, 39, 30, 147, 191, 167, 27, 37, 240, 236, 145, 81, 216, 53, 211, 51, 252, 178, 142, 181, 214, 133, 179, 249, 4 }, { 238, 254, 184, 227, 172, 58, 40, 175, 21, 55, 122, 45, 222, 52, 85, 50, 11, 12, 188, 124, 115, 224, 131, 37, 253, 151, 252, 121, 2, 193, 225, 109, 219, 69, 116, 80, 67, 42, 110, 244, 90, 161, 104, 170, 100, 22, 24, 101, 248, 230, 221, 27, 74, 231, 51, 229, 242, 4, 159, 223, 218, 171, 138, 232, 160, 134, 84, 220, 245, 180, 95, 208, 73, 200, 44, 48, 202, 237, 209, 167, 54, 148, 211, 102, 215, 249, 8, 35, 163 }, { 193, 223, 169, 150, 36, 38, 185, 26, 85, 100, 44, 96, 15, 59, 145, 89, 1, 193, 223, 169, 150, 36, 38, 185, 26, 85, 100, 44, 96, 15, 59, 145, 89, 1, 193, 223, 169, 150, 36, 38, 185, 26, 85, 100, 44, 96, 15, 59, 145, 89, 1, 193, 223, 169, 150, 36, 38, 185, 26, 85, 100, 44, 96, 15, 59, 145, 89, 1, 193, 223, 169, 150, 36, 38, 185, 26, 85, 100, 44, 96, 15, 59, 145, 89, 1, 193, 223, 169, 150 }, { 159, 91, 33, 149, 244, 117, 194, 31, 115, 167, 216, 181, 254, 218, 150, 72, 152, 161, 189, 114, 56, 131, 148, 107, 46, 227, 138, 135, 210, 26, 170, 141, 125, 78, 253, 102, 123, 43, 58, 160, 34, 41, 25, 22, 96, 30, 236, 252, 249, 32, 10, 175, 84, 87, 235, 6, 101, 199, 198, 89, 2, 35, 182, 66, 55, 245, 234, 153, 62, 230, 83, 173, 119, 225, 169, 49, 144, 45, 95, 103, 228, 112, 27, 53, 214, 92, 219, 9, 19 }, { 35, 113, 21, 165, 235, 12, 137, 118, 252, 239, 128, 80, 34, 82, 100, 176, 78, 231, 133, 255, 138, 19, 111, 208, 114, 112, 54, 212, 254, 169, 98, 122, 117, 153, 124, 191, 162, 2, 70, 226, 42, 87, 203, 24, 15, 236, 229, 195, 29, 160, 68, 164, 200, 125, 156, 211, 23, 227, 9, 38, 222, 189, 228, 224, 108, 181, 225, 79, 196, 244, 234, 47, 248, 99, 89, 4, 140, 217, 84, 174, 139, 48, 30, 197, 215, 155, 58, 93, 136 }, { 70, 217, 168, 130, 44, 39, 231, 23, 219, 36, 45, 97, 62, 191, 89, 8, 10, 134, 41, 100, 125, 37, 107, 184, 150, 61, 117, 47, 237, 145, 242, 64, 80, 68, 85, 7, 207, 53, 127, 169, 196, 245, 143, 101, 59, 252, 195, 58, 186, 26, 146, 56, 54, 181, 223, 33, 110, 251, 12, 15, 197, 179, 86, 205, 185, 208, 228, 221, 173, 193, 182, 21, 87, 139, 96, 120, 102, 241, 138, 38, 161, 206, 115, 166, 1, 70, 217, 168, 130 }, { 140, 67, 41, 200, 233, 53, 254, 158, 110, 235, 48, 120, 204, 227, 36, 90, 153, 237, 63, 239, 58, 105, 104, 228, 167, 142, 70, 175, 154, 100, 250, 148, 127, 79, 55, 251, 24, 60, 102, 255, 18, 45, 194, 248, 145, 249, 29, 186, 52, 114, 221, 71, 35, 217, 77, 50, 125, 74, 177, 169, 149, 243, 12, 30, 51, 241, 9, 152, 97, 124, 198, 242, 128, 93, 26, 57, 224, 173, 159, 226, 168, 25, 176, 37, 214, 218, 196, 247, 6 }, { 5, 17, 85, 28, 108, 193, 226, 77, 100, 233, 106, 223, 132, 174, 44, 156, 214, 169, 55, 235, 96, 253, 46, 150, 244, 3, 15, 51, 255, 36, 180, 94, 59, 215, 172, 38, 190, 124, 145, 239, 116, 185, 103, 230, 89, 32, 160, 26, 114, 167, 1, 5, 17, 85, 28, 108, 193, 226, 77, 100, 233, 106, 223, 132, 174, 44, 156, 214, 169, 55, 235, 96, 253, 46, 150, 244, 3, 15, 51, 255, 36, 180, 94, 59, 215, 172, 38, 190, 124 }, { 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221, 1, 10, 68, 146, 221 }, { 20, 13, 228, 81, 32, 186, 189, 209, 242, 116, 222, 62, 63, 43, 38, 194, 147, 179, 9, 180, 101, 151, 227, 61, 3, 60, 23, 49, 243, 96, 211, 218, 110, 11, 156, 127, 66, 65, 125, 106, 91, 168, 200, 27, 193, 175, 164, 56, 71, 5, 68, 57, 83, 8, 160, 104, 115, 178, 29, 185, 129, 198, 195, 135, 190, 237, 229, 69, 45, 94, 236, 241, 72, 201, 15, 204, 75, 245, 24, 253, 184, 149, 203, 39, 214, 158, 87, 88, 148 }, { 40, 52, 115, 121, 116, 161, 248, 229, 138, 180, 202, 102, 75, 247, 96, 187, 79, 87, 176, 106, 182, 154, 14, 173, 5, 136, 228, 162, 128, 185, 31, 63, 86, 152, 94, 197, 227, 122, 12, 253, 109, 110, 22, 74, 223, 84, 200, 54, 35, 17, 146, 83, 16, 186, 103, 99, 195, 19, 194, 59, 246, 72, 143, 60, 46, 196, 203, 78, 127, 132, 25, 207, 238, 175, 85, 224, 2, 80, 104, 230, 242, 232, 95, 237, 215, 9, 117, 137, 204 }, { 80, 208, 191, 195, 38, 47, 197, 219, 245, 96, 107, 33, 130, 207, 193, 134, 146, 166, 64, 185, 62, 252, 138, 117, 15, 23, 196, 139, 37, 223, 168, 7, 173, 10, 26, 115, 242, 205, 97, 59, 241, 61, 12, 231, 169, 87, 125, 181, 217, 85, 221, 8, 186, 206, 145, 86, 45, 101, 102, 150, 251, 39, 127, 21, 100, 54, 70, 68, 228, 89, 58, 161, 237, 179, 36, 143, 120, 184, 110, 44, 53, 182, 41, 56, 1, 80, 208, 191, 195 }, { 160, 103, 145, 172, 180, 15, 46, 55, 44, 106, 226, 85, 167, 32, 185, 124, 215, 36, 3, 253, 169, 174, 233, 193, 17, 114, 89, 116, 190, 59, 255, 244, 96, 214, 132, 100, 108, 5, 26, 230, 239, 38, 94, 51, 150, 235, 156, 223, 77, 28, 1, 160, 103, 145, 172, 180, 15, 46, 55, 44, 106, 226, 85, 167, 32, 185, 124, 215, 36, 3, 253, 169, 174, 233, 193, 17, 114, 89, 116, 190, 59, 255, 244, 96, 214, 132, 100, 108, 5 }, { 93, 129, 252, 18, 3, 231, 158, 25, 54, 5, 52, 191, 43, 90, 15, 92, 220, 125, 238, 17, 228, 121, 135, 47, 51, 49, 139, 148, 113, 85, 83, 128, 161, 147, 255, 245, 157, 254, 168, 28, 2, 186, 31, 229, 36, 6, 211, 33, 50, 108, 10, 104, 99, 86, 180, 30, 184, 165, 250, 193, 34, 213, 242, 19, 94, 102, 98, 11, 53, 226, 170, 166, 29, 95, 59, 227, 247, 39, 225, 77, 56, 4, 105, 62, 215, 72, 12, 187, 66 }, { 186, 62, 179, 61, 96, 127, 168, 56, 8, 185, 237, 241, 245, 39, 223, 41, 221, 64, 161, 59, 219, 251, 37, 182, 85, 166, 58, 97, 197, 150, 139, 53, 217, 146, 89, 205, 47, 102, 196, 44, 181, 134, 228, 242, 38, 101, 23, 110, 125, 193, 68, 115, 195, 45, 15, 184, 87, 207, 70, 26, 191, 86, 117, 120, 169, 130, 54, 10, 208, 145, 138, 143, 231, 33, 100, 173, 80, 206, 252, 36, 12, 107, 21, 7, 1, 186, 62, 179, 61 }, { 105, 248, 241, 247, 156, 182, 170, 162, 205, 94, 133, 110, 250, 35, 26, 99, 69, 143, 211, 132, 7, 2, 210, 237, 255, 243, 37, 113, 73, 89, 135, 188, 23, 220, 233, 70, 52, 198, 138, 3, 187, 21, 14, 4, 185, 199, 227, 251, 74, 226, 146, 178, 19, 101, 46, 165, 207, 140, 104, 145, 9, 6, 107, 42, 28, 8, 111, 147, 219, 235, 148, 217, 57, 121, 38, 202, 92, 87, 131, 5, 208, 63, 18, 12, 214, 84, 56, 16, 222 } };
+        static int[] logArrays = { 256, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 };
+        static int[] expArrays = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1, 2, 4 };
+
+        // Encode
+        internal static void Encode(byte[] codeWord, byte[] message, int mBitSize, int n1, int paramK, int paramG, int[] rsPoly)
+        {
+            int gateValue = 0;
+            byte[] encodedBytes = new byte[n1];
+            int[] tmp = new int[paramG];
+
+            byte[] msgByte = Arrays.Clone(message);
+
+            for (int i = 0; i < paramK; i++)
+            {
+                gateValue = Utils.ToUnsigned8bits(msgByte[paramK - 1 - i] ^ encodedBytes[n1 - paramK - 1]);
+
+                for (int j = 0; j < paramG; j++)
+                {
+                    tmp[j] = GFCalculator.mult(gateValue, rsPoly[j]);
+                }
+
+                for (int j = n1 - paramK - 1; j > 0; j--)
+                {
+                    encodedBytes[j] = (byte)(encodedBytes[j - 1] ^ tmp[j]);
+                }
+                encodedBytes[0] = (byte)(tmp[0]);
+            }
+
+            Array.Copy(msgByte, 0, encodedBytes, n1 - paramK, paramK);
+            Array.Copy(encodedBytes, 0, codeWord, 0, codeWord.Length);
+        }
+
+        // Decode
+        internal static void Decode(byte[] message, byte[] codeWord, int n1, int fft, int delta, int paramK, int paramG)
+        {
+            int fftSize = 1 << fft;
+            int mSize = 1 << (HqcParameters.PARAM_M - 1);
+
+            byte[] codeWordByte = Arrays.Clone(codeWord);
+
+            int[] syndromes = new int[2 * delta];
+            // Step 1: Compute syndromes
+            ComputeSyndromes(syndromes, codeWordByte, delta, n1);
+
+            // Step 2: Compute the error locator polynomial sigma
+            int[] sigma = new int[fftSize];
+            int degSigma = ComputeELP(sigma, syndromes, delta);
+
+            // Step 3: Compute roots using FFT
+            int[] fftRes = new int[1 << HqcParameters.PARAM_M];
+            byte[] errorSet = new byte[1 << HqcParameters.PARAM_M];
+            FastFourierTransform.FFT(fftRes, sigma, delta + 1, fft);
+            FastFourierTransform.FastFourierTransformGetError(errorSet, fftRes, mSize, logArrays);
+
+            // Step 4: Compute z(x)
+            int[] zx = new int[n1];
+            ComputeZx(zx, sigma, degSigma, syndromes, delta);
+
+            // Step 5: Compute errors
+            int[] errors = new int[n1];
+            ComputeErrors(errors, zx, errorSet, delta, n1);
+
+            // Step 6: Correct errors
+            for (int i = 0; i < n1; i++)
+            {
+                codeWordByte[i] ^= (byte) errors[i];
+            }
+            byte[] mTmp = new byte[paramK];
+            Array.Copy(codeWordByte, paramG - 1, mTmp, 0, paramK);
+            Array.Copy(mTmp, 0, message, 0, message.Length);
+        }
+
+        private static void ComputeSyndromes(int[] syndromes, byte[] codeWord, int delta, int n1)
+        {
+            if (n1 == 46)
+            {
+                for (int i = 0; i < 2 * delta; i++)
+                {
+                    for (int j = 1; j < n1; j++)
+                    {
+                        syndromes[i] ^= GFCalculator.mult(Utils.ToUnsigned8bits(codeWord[j]), alpha128[i, j - 1]);
+                    }
+                    syndromes[i] ^= Utils.ToUnsigned8bits(codeWord[0]);
+                }
+            }
+            else if (n1 == 56)
+            {
+                for (int i = 0; i < 2 * delta; i++)
+                {
+                    for (int j = 1; j < n1; j++)
+                    {
+                        syndromes[i] ^= GFCalculator.mult(Utils.ToUnsigned8bits(codeWord[j]), alpha192[i, j - 1]);
+                    }
+                    syndromes[i] ^= Utils.ToUnsigned8bits(codeWord[0]);
+                }
+            }
+            else if (n1 == 90)
+            {
+                for (int i = 0; i < 2 * delta; i++)
+                {
+                    for (int j = 1; j < n1; j++)
+                    {
+                        syndromes[i] ^= GFCalculator.mult(Utils.ToUnsigned8bits(codeWord[j]), alpha256[i, j - 1]);
+                    }
+                    syndromes[i] ^= Utils.ToUnsigned8bits(codeWord[0]);
+                }
+            }
+        }
+
+        private static int ComputeELP(int[] sigma, int[] syndromes, int delta)
+        {
+            sigma[0] = 1;
+            int degSigma = 0;
+            int degSigmaP = 0;
+            int[] sigmaDup = new int[delta + 1];
+            int[] sigmaP = new int[delta + 1];
+            int degSigmaDup = 0;
+            int pp = Utils.ToUnsigned16Bits(-1);
+            int dp = 1;
+            int d = syndromes[0];
+
+            sigmaP[1] = 1;
+
+            for (int i = 0; i < 2 * delta; i++)
+            {
+                Array.Copy(sigma, 0, sigmaDup, 0, delta + 1);
+                degSigmaDup = degSigma;
+                int dd = GFCalculator.mult(d, GFCalculator.inverse(dp));
+
+                for (int j = 1; j <= i + 1 && j <= delta; j++)
+                {
+                    sigma[j] ^= GFCalculator.mult(dd, sigmaP[j]);
+                }
+
+                int degX = Utils.ToUnsigned16Bits(i - pp);
+                int degXSigmaP = Utils.ToUnsigned16Bits(degX + degSigmaP);
+
+                int firstMask = d != 0 ? 0xffff : 0;
+                int secondMask = degXSigmaP > degSigma ? 0xffff : 0;
+
+                int mask = firstMask & secondMask;
+                degSigma ^= mask & (degXSigmaP ^ degSigma);
+
+                if (i == (2 * delta - 1))
+                {
+                    break;
+                }
+
+                pp ^= mask & (i ^ pp);
+                dp ^= mask & (d ^ dp);
+
+                for (int k = delta; k > 0; k--)
+                {
+                    sigmaP[k] = (mask & sigmaDup[k - 1]) ^ (~mask & sigmaP[k - 1]);
+                }
+
+                degSigmaP ^= mask & (degSigmaDup ^ degSigmaP);
+                d = syndromes[i + 1];
+
+                for (int k = 1; k <= i + 1 && k <= delta; k++)
+                {
+                    d ^= GFCalculator.mult(sigma[k], syndromes[i + 1 - k]);
+                }
+            }
+            return degSigma;
+        }
+
+        private static void ComputeZx(int[] output, int[] sigma, int deg, int[] syndromes, int delta)
+        {
+            output[0] = 1;
+
+            for (int i = 1; i < delta + 1; i++)
+            {
+                int mask = i - deg < 1 ? 0xffff : 0;
+                output[i] = mask & sigma[i];
+            }
+
+            output[1] ^= syndromes[0];
+
+            for (int i = 2; i <= delta; i++)
+            {
+                int mask = i - deg < 1 ? 0xffff : 0;
+                output[i] = mask & sigma[i - 1];
+
+                for (int j = 1; j < i; j++)
+                {
+                    output[i] ^= (mask) & GFCalculator.mult(sigma[j], syndromes[i - j - 1]);
+                }
+            }
+        }
+
+        private static void ComputeErrors(int[] res, int[] zx, byte[] errorCompactSet, int delta, int n1)
+        {
+            int[] betaSet = new int[delta];
+            int[] eSet = new int[delta];
+
+            int deltaCount = 0;
+            int deltaVal = 0;
+            int mask1 = 0;
+            for (int i = 0; i < n1; i++)
+            {
+                int mark = 0;
+                int mask = errorCompactSet[i] != 0 ? 0xffff : 0;
+                for (int j = 0; j < delta; j++)
+                {
+                    int iMask = j == deltaCount ? 0xffff : 0;
+                    betaSet[j] += iMask & mask & expArrays[i];
+                    mark += iMask & mask & 1;
+                }
+                deltaCount += mark;
+            }
+            deltaVal = deltaCount;
+
+            for (int i = 0; i < delta; i++)
+            {
+                int temp1 = 1;
+                int temp2 = 1;
+                int inv = GFCalculator.inverse(betaSet[i]);
+                int invPow = 1;
+
+                for (int j = 1; j <= delta; j++)
+                {
+                    invPow = GFCalculator.mult(invPow, inv);
+                    temp1 ^= GFCalculator.mult(invPow, zx[j]);
+                }
+
+                for (int j = 1; j < delta; j++)
+                {
+                    temp2 = GFCalculator.mult(temp2, (1 ^ GFCalculator.mult(inv, betaSet[(i + j) % delta])));
+                }
+
+                mask1 = i < deltaVal ? 0xffff : 0;
+                eSet[i] = mask1 & GFCalculator.mult(temp1, GFCalculator.inverse(temp2));
+            }
+
+            deltaCount = 0;
+            for (int i = 0; i < n1; i++)
+            {
+                int mark = 0;
+                int mask = errorCompactSet[i] != 0 ? 0xffff : 0;
+
+                for (int j = 0; j < delta; j++)
+                {
+                    int iMask = j == deltaCount ? 0xffff : 0;
+                    res[i] += iMask & mask & eSet[j];
+                    mark += iMask & mask & 1;
+                }
+                deltaCount += mark;
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/hqc/Utils.cs b/crypto/src/pqc/crypto/hqc/Utils.cs
new file mode 100644
index 000000000..4d44db07c
--- /dev/null
+++ b/crypto/src/pqc/crypto/hqc/Utils.cs
@@ -0,0 +1,155 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Hqc
+{
+    internal class Utils
+    {
+        internal static void ResizeArray(ulong[] output, int sizeOutBits, ulong[] input, int sizeInBits,
+            int n1n2ByteSize, int n1n2Byte64Size)
+        {
+            ulong mask = 0x7FFFFFFFFFFFFFFFUL;
+            int val = 0;
+            if (sizeOutBits < sizeInBits)
+            {
+                if (sizeOutBits % 64 != 0)
+                {
+                    val = 64 - (sizeOutBits % 64);
+                }
+
+                Array.Copy(input, 0, output, 0, n1n2ByteSize);
+
+                for (int i = 0; i < val; ++i)
+                {
+                    output[n1n2Byte64Size - 1] &= mask >> i;
+                }
+            }
+            else
+            {
+                Array.Copy(input, 0, output, 0, (sizeInBits + 7) / 8);
+            }
+        }
+
+        internal static void FromULongArrayToByteArray(byte[] output, ulong[] input)
+        {
+            int max = output.Length / 8;
+            for (int i = 0; i != max; i++)
+            {
+                Pack.UInt64_To_LE(input[i], output, i * 8);
+            }
+
+            if (output.Length % 8 != 0)
+            {
+                int off = max * 8;
+                int count = 0;
+                while (off < output.Length)
+                {
+                    output[off++] = (byte)(input[max] >> (count++ * 8));
+                }
+            }
+        }
+
+        internal static ulong BitMask(ulong a, ulong b)
+        {
+            uint tmp = (uint)(a % b);
+            return ((1UL << (int)tmp) - 1);
+        }
+
+        internal static void FromByteArrayToULongArray(ulong[] output, byte[] input)
+        {
+            byte[] tmp = input;
+            if (input.Length % 8 != 0)
+            {
+                tmp = new byte[((input.Length + 7) / 8) * 8];
+                Array.Copy(input, 0, tmp, 0, input.Length);
+            }
+
+            int off = 0;
+            for (int i = 0; i < output.Length; i++)
+            {
+                output[i] = Pack.LE_To_UInt64(tmp, off);
+                off += 8;
+            }
+        }
+
+        internal static void FromByteArrayToByte16Array(int[] output, byte[] input)
+        {
+            byte[] tmp = input;
+            if (input.Length % 2 != 0)
+            {
+                tmp = new byte[((input.Length + 1) / 2) * 2];
+                Array.Copy(input, 0, tmp, 0, input.Length);
+            }
+
+            int off = 0;
+            for (int i = 0; i < output.Length; i++)
+            {
+                output[i] = (int)Pack.LE_To_UInt16(tmp, off);
+                off += 2;
+            }
+        }
+
+        internal static void FromByte32ArrayToULongArray(ulong[] output, int[] input)
+        {
+            for (int i = 0; i != input.Length; i += 2)
+            {
+                output[i / 2] = (uint)input[i];
+                output[i / 2] |= (ulong)input[i + 1] << 32;
+            }
+        }
+
+        internal static void FromByte16ArrayToULongArray(ulong[] output, ushort[] input)
+        {
+            for (int i = 0; i != input.Length; i += 4)
+            {
+                output[i / 4] = input[i];
+                output[i / 4] |= (ulong)input[i + 1] << 16;
+                output[i / 4] |= (ulong)input[i + 2] << 32;
+                output[i / 4] |= (ulong)input[i + 3] << 48;
+            }
+        }
+
+        internal static void FromULongArrayToByte32Array(int[] output, ulong[] input)
+        {
+            for (int i = 0; i != input.Length; i++)
+            {
+                output[2 * i] = (int)input[i];
+                output[2 * i + 1] = (int)(input[i] >> 32);
+            }
+        }
+
+        internal static void CopyBytes(int[] src, int offsetSrc, int[] dst, int offsetDst, int lengthBytes)
+        {
+            Array.Copy(src, offsetSrc, dst, offsetDst, lengthBytes / 2);
+        }
+
+        internal static int GetByteSizeFromBitSize(int size)
+        {
+            return (size + 7) / 8;
+        }
+
+        internal  static int GetByte64SizeFromBitSize(int size)
+        {
+            return (size + 63) / 64;
+        }
+
+        internal static int ToUnsigned8bits(int a)
+        {
+            return a & 0xff;
+        }
+
+        internal static int ToUnsigned16Bits(int a)
+        {
+            return a & 0xffff;
+        }
+
+        internal static void XorULongToByte16Array(ushort[] output, int outOff, ulong input)
+        {
+            output[outOff + 0] ^= (ushort)input;
+            output[outOff + 1] ^= (ushort)(input >> 16);
+            output[outOff + 2] ^= (ushort)(input >> 32);
+            output[outOff + 3] ^= (ushort)(input >> 48);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/lms/Composer.cs b/crypto/src/pqc/crypto/lms/Composer.cs
index 437c86d65..6ad044e34 100644
--- a/crypto/src/pqc/crypto/lms/Composer.cs
+++ b/crypto/src/pqc/crypto/lms/Composer.cs
@@ -1,5 +1,5 @@
-using System;
 using System.IO;
+
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
@@ -7,14 +7,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
     /**
     * Type to assist in build LMS messages.
     */
-    public class Composer
+    public sealed class Composer
     {
         //Todo make sure MemoryStream works properly (not sure about byte arrays as inputs)
-        private MemoryStream bos = new MemoryStream();
+        private readonly MemoryStream bos = new MemoryStream();
 
         private Composer()
         {
-
         }
 
         public static Composer Compose()
@@ -24,9 +23,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
         public Composer U64Str(long n)
         {
-            U32Str((int) (n >> 32));
-            U32Str((int) n);
-
+            U32Str((int)(n >> 32));
+            U32Str((int)n);
             return this;
         }
 
@@ -35,47 +33,32 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             bos.WriteByte((byte)(n >> 24));
             bos.WriteByte((byte)(n >> 16));
             bos.WriteByte((byte)(n >> 8));
-            bos.WriteByte((byte)(n));
+            bos.WriteByte((byte)n);
             return this;
         }
 
-        public Composer U16Str(uint n)
+        public Composer U16Str(int n)
         {
             n &= 0xFFFF;
             bos.WriteByte((byte)(n >> 8));
-            bos.WriteByte((byte)(n));
+            bos.WriteByte((byte)n);
             return this;
         }
 
         public Composer Bytes(IEncodable[] encodable)
         {
-            try
-            {
-                foreach (var e in encodable)
-                {
-                    bos.Write(e.GetEncoded(), 0, e.GetEncoded().Length);// todo count?
-                }
-            }
-            catch (Exception ex)
+            foreach (var e in encodable)
             {
-                throw new Exception(ex.Message, ex);
+                byte[] encoding = e.GetEncoded();
+                bos.Write(encoding, 0, encoding.Length);// todo count?
             }
-
             return this;
         }
 
-
         public Composer Bytes(IEncodable encodable)
         {
-            try
-            {
-                bos.Write(encodable.GetEncoded(), 0, encodable.GetEncoded().Length);
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message, ex);
-            }
-
+            byte[] encoding = encodable.GetEncoded();
+            bos.Write(encoding, 0, encoding.Length);
             return this;
         }
 
@@ -83,82 +66,40 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             for (; len >= 0; len--)
             {
-                try
-                {
-                    bos.WriteByte((byte) v);
-                }
-                catch (Exception ex)
-                {
-                    throw new Exception(ex.Message, ex);
-                }
+                bos.WriteByte((byte)v);
             }
-
             return this;
         }
 
-        public Composer Bytes(byte[][] arrays)
+        public Composer Bytes2(byte[][] arrays)
         {
-            try
-            {
-                foreach (byte[] array in arrays)
-                {
-                    bos.Write(array, 0, array.Length); //todo count?
-                }
-            }
-            catch (Exception ex)
+            foreach (byte[] array in arrays)
             {
-                throw new Exception(ex.Message, ex);
+                bos.Write(array, 0, array.Length); //todo count?
             }
-
             return this;
         }
 
-        public Composer Bytes(byte[][] arrays, int start, int end)
+        public Composer Bytes2(byte[][] arrays, int start, int end)
         {
-            try
+            int j = start;
+            while (j != end)
             {
-                int j = start;
-                while (j != end)
-                {
-                    bos.Write(arrays[j], 0, arrays[j].Length);//todo count?
-                    j++;
-                }
+                bos.Write(arrays[j], 0, arrays[j].Length);//todo count?
+                j++;
             }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message, ex);
-            }
-
             return this;
         }
 
-
         public Composer Bytes(byte[] array)
         {
-            try
-            {
-                bos.Write(array, 0, array.Length);//todo count?
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message, ex);
-            }
-
+            bos.Write(array, 0, array.Length);//todo count?
             return this;
         }
 
-
         public Composer Bytes(byte[] array, int start, int len)
         {
-            try
-            {
-                bos.Write(array, start, len);
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message, ex);
-            }
-
+            bos.Write(array, start, len);
             return this;
         }
 
@@ -171,17 +112,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             while (bos.Length < requiredLen)
             {
-                bos.WriteByte((byte) v);
+                bos.WriteByte((byte)v);
             }
-
             return this;
         }
 
-        public Composer GetBool(bool v)
+        public Composer Boolean(bool v)
         {
-            bos.WriteByte((byte) (v ? 1 : 0));
+            bos.WriteByte((byte)(v ? 1 : 0));
             return this;
         }
-            
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/HSS.cs b/crypto/src/pqc/crypto/lms/HSS.cs
index 556ffac26..4634088c7 100644
--- a/crypto/src/pqc/crypto/lms/HSS.cs
+++ b/crypto/src/pqc/crypto/lms/HSS.cs
@@ -3,15 +3,15 @@ using System.Collections.Generic;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class HSS
+    public static class Hss
     {
-        public static HSSPrivateKeyParameters GenerateHssKeyPair(HSSKeyGenerationParameters parameters)
+        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];
+            LmsPrivateKeyParameters[] keys = new LmsPrivateKeyParameters[parameters.Depth];
+            LmsSignature[] sig = new LmsSignature[parameters.Depth - 1];
 
             byte[] rootSeed = new byte[32];
             parameters.Random.NextBytes(rootSeed);
@@ -30,27 +30,30 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             long hssKeyMaxIndex = 1;
             for (int t = 0; t < keys.Length; t++)
             {
+                var lms = parameters.GetLmsParameters(t);
                 if (t == 0)
                 {
-                    keys[t] = new LMSPrivateKeyParameters(
-                        parameters.GetLmsParameters()[t].GetLmSigParam(),
-                        parameters.GetLmsParameters()[t].GetLmotsParam(),
+                    keys[t] = new LmsPrivateKeyParameters(
+                        lms.LMSigParameters,
+                        lms.LMOtsParameters,
                         0,
                         I,
-                        1 << parameters.GetLmsParameters()[t].GetLmSigParam().GetH(),
-                        rootSeed);
+                        1 << lms.LMSigParameters.H,
+                        rootSeed,
+                        isPlaceholder: false);
                 }
                 else
                 {
-                    keys[t] = new PlaceholderLMSPrivateKey(
-                        parameters.GetLmsParameters()[t].GetLmSigParam(),
-                        parameters.GetLmsParameters()[t].GetLmotsParam(),
+                    keys[t] = new LmsPrivateKeyParameters(
+                        lms.LMSigParameters,
+                        lms.LMOtsParameters,
                         -1,
                         zero,
-                        1 << parameters.GetLmsParameters()[t].GetLmSigParam().GetH(),
-                        zero);
+                        1 << lms.LMSigParameters.H,
+                        zero,
+                        isPlaceholder: true);
                 }
-                hssKeyMaxIndex *= 1 << parameters.GetLmsParameters()[t].GetLmSigParam().GetH();
+                hssKeyMaxIndex <<= lms.LMSigParameters.H;
             }
 
             // if this has happened we're trying to generate a really large key
@@ -60,10 +63,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 hssKeyMaxIndex = long.MaxValue;
             }
 
-            return new HSSPrivateKeyParameters(
-                parameters.GetDepth(),
-                new List<LMSPrivateKeyParameters>(keys),
-                new List<LMSSignature>(sig),
+            return new HssPrivateKeyParameters(
+                parameters.Depth,
+                new List<LmsPrivateKeyParameters>(keys),
+                new List<LmsSignature>(sig),
                 0, hssKeyMaxIndex);
         }
 
@@ -76,18 +79,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
          *
          * @param keyPair
          */
-        public static void IncrementIndex(HSSPrivateKeyParameters keyPair)
+        public static void IncrementIndex(HssPrivateKeyParameters keyPair)
         {
             lock (keyPair)
             {
                 RangeTestKeys(keyPair);
                 keyPair.IncIndex();
-                (keyPair.GetKeys()[(keyPair.L - 1)] as LMSPrivateKeyParameters).IncIndex();
+                keyPair.GetKeys()[keyPair.L - 1].IncIndex();
             }
         }
 
-
-        public static void RangeTestKeys(HSSPrivateKeyParameters keyPair)
+        public static void RangeTestKeys(HssPrivateKeyParameters keyPair)
         {
             lock (keyPair)
             {
@@ -99,36 +101,28 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                             " is exhausted");
                 }
 
-
                 int L = keyPair.L;
                 int d = L;
                 var prv = keyPair.GetKeys();
-                while ((prv[d - 1] as LMSPrivateKeyParameters).GetIndex() == 1 << ((prv[(d - 1)] as LMSPrivateKeyParameters ).GetSigParameters().GetH()))
+                while (prv[d - 1].GetIndex() == 1 << prv[d - 1].GetSigParameters().H)
                 {
-                    d = d - 1;
-                    if (d == 0)
-                    {
-                        throw new Exception(
-                            "hss private key" +
-                                ((keyPair.IsShard()) ? " shard" : "") +
-                                " is exhausted the maximum limit for this HSS private key");
-                    }
+                    if (--d == 0)
+                        throw new Exception("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;
+                    keyPair.ReplaceConsumedKey(d++);
                 }
             }
         }
 
 
-        public static HSSSignature GenerateSignature(HSSPrivateKeyParameters keyPair, byte[] message)
+        public static HssSignature GenerateSignature(HssPrivateKeyParameters keyPair, byte[] message)
         {
-            LMSSignedPubKey[] signed_pub_key;
-            LMSPrivateKeyParameters nextKey;
+            LmsSignedPubKey[] signed_pub_key;
+            LmsPrivateKeyParameters nextKey;
             int L = keyPair.L;
 
             lock (keyPair)
@@ -138,17 +132,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 var keys = keyPair.GetKeys();
                 var sig = keyPair.GetSig();
 
-                nextKey = keyPair.GetKeys()[L - 1] as LMSPrivateKeyParameters;
+                nextKey = keyPair.GetKeys()[L - 1];
 
                 // Step 2. Stand in for sig[L-1]
                 int i = 0;
-                signed_pub_key = new LMSSignedPubKey[L - 1];
+                signed_pub_key = new LmsSignedPubKey[L - 1];
                 while (i < L - 1)
                 {
-                    signed_pub_key[i] = new LMSSignedPubKey(
-                        sig[i] as LMSSignature,
-                        (keys[i + 1] as LMSPrivateKeyParameters).GetPublicKey());
-                    i = i + 1;
+                    signed_pub_key[i] = new LmsSignedPubKey(sig[i], keys[i + 1].GetPublicKey());
+                    ++i;
                 }
 
                 //
@@ -157,43 +149,41 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 keyPair.IncIndex();
             }
 
-            LMSContext context = nextKey.GenerateLmsContext().WithSignedPublicKeys(signed_pub_key);
+            LmsContext context = nextKey.GenerateLmsContext().WithSignedPublicKeys(signed_pub_key);
 
             context.BlockUpdate(message, 0, message.Length);
 
             return GenerateSignature(L, context);
         }
 
-        public static HSSSignature GenerateSignature(int L, LMSContext context)
+        public static HssSignature GenerateSignature(int L, LmsContext context)
         {
-            return new HSSSignature(L - 1, context.GetSignedPubKeys(), LMS.GenerateSign(context));
+            return new HssSignature(L - 1, context.SignedPubKeys, Lms.GenerateSign(context));
         }
 
-        public static bool VerifySignature(HSSPublicKeyParameters publicKey, HSSSignature signature, byte[] message)
+        public static bool VerifySignature(HssPublicKeyParameters publicKey, HssSignature signature, byte[] message)
         {
-            int Nspk = signature.GetlMinus1();
-            if (Nspk + 1 != publicKey.GetL())
-            {
+            int Nspk = signature.GetLMinus1();
+            if (Nspk + 1 != publicKey.L)
                 return false;
-            }
 
-            LMSSignature[] sigList = new LMSSignature[Nspk + 1];
-            LMSPublicKeyParameters[] pubList = new LMSPublicKeyParameters[Nspk];
+            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[i] = signature.GetSignedPubKeys()[i].GetSignature();
+                pubList[i] = signature.GetSignedPubKeys()[i].GetPublicKey();
             }
-            sigList[Nspk] = signature.GetSignature();
+            sigList[Nspk] = signature.Signature;
 
-            LMSPublicKeyParameters key = publicKey.GetLmsPublicKey();
+            LmsPublicKeyParameters key = publicKey.LmsPublicKey;
 
             for (int i = 0; i < Nspk; i++)
             {
-                LMSSignature sig = sigList[i];
+                LmsSignature sig = sigList[i];
                 byte[] msg = pubList[i].ToByteArray();
-                if (!LMS.VerifySignature(key, sig, msg))
+                if (!Lms.VerifySignature(key, sig, msg))
                 {
                     return false;
                 }
@@ -206,27 +196,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     throw new Exception(ex.Message, ex);
                 }
             }
-            return LMS.VerifySignature(key, sigList[Nspk], message);
-        }
-
-
-        class PlaceholderLMSPrivateKey
-            : LMSPrivateKeyParameters
-        {
-
-            public PlaceholderLMSPrivateKey(LMSigParameters lmsParameter, LMOtsParameters otsParameters, int q, byte[] I, int maxQ, byte[] masterSecret)
-                : base(lmsParameter, otsParameters, q, I, maxQ, masterSecret)
-            {}
-
-            LMOtsPrivateKey GetNextOtsPrivateKey()
-            {
-                throw new Exception("placeholder only");
-            }
-
-            public LMSPublicKeyParameters GetPublicKey()
-            {
-                throw new Exception("placeholder only");
-            }
+            return Lms.VerifySignature(key, sigList[Nspk], message);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/HSSKeyGenerationParameters.cs b/crypto/src/pqc/crypto/lms/HSSKeyGenerationParameters.cs
index e7a2403aa..61ea51368 100644
--- a/crypto/src/pqc/crypto/lms/HSSKeyGenerationParameters.cs
+++ b/crypto/src/pqc/crypto/lms/HSSKeyGenerationParameters.cs
@@ -1,13 +1,23 @@
 using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class HSSKeyGenerationParameters
+    public sealed class HssKeyGenerationParameters
         : KeyGenerationParameters
     {
-        private LMSParameters[] lmsParameters;
+        private static LmsParameters[] ValidateLmsParameters(LmsParameters[] lmsParameters)
+        {
+            if (lmsParameters == null)
+                throw new ArgumentNullException(nameof(lmsParameters));
+            if (lmsParameters.Length < 1 || lmsParameters.Length > 8)  // RFC 8554, Section 6.
+                throw new ArgumentException("length should be between 1 and 8 inclusive", nameof(lmsParameters));
+            return lmsParameters;
+        }
+
+        private readonly LmsParameters[] m_lmsParameters;
 
         /**
          * Base constructor - parameters and a source of randomness.
@@ -15,26 +25,20 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
          * @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)
-            :base(random, LmsUtils.CalculateStrength(lmsParameters[0]))
+        public HssKeyGenerationParameters(LmsParameters[] lmsParameters, SecureRandom random)
+            :base(random, LmsUtilities.CalculateStrength(ValidateLmsParameters(lmsParameters)[0]))
         {
-            if (lmsParameters.Length == 0 || lmsParameters.Length > 8)  // RFC 8554, Section 6.
-            {
-                throw new ArgumentException("lmsParameters length should be between 1 and 8 inclusive");
-            }
-            this.lmsParameters = lmsParameters;
+            m_lmsParameters = lmsParameters;
         }
 
-        public int GetDepth()
-        {
-            return lmsParameters.Length;
-        }
+        public int Depth => m_lmsParameters.Length;
 
-        public LMSParameters[] GetLmsParameters()
+        public LmsParameters GetLmsParameters(int index)
         {
-            return lmsParameters;
+            if (index < 0 || index >= m_lmsParameters.Length)
+                throw new ArgumentOutOfRangeException(nameof(index));
+
+            return m_lmsParameters[index];
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/HSSKeyPairGenerator.cs b/crypto/src/pqc/crypto/lms/HSSKeyPairGenerator.cs
index 562679563..273e18904 100644
--- a/crypto/src/pqc/crypto/lms/HSSKeyPairGenerator.cs
+++ b/crypto/src/pqc/crypto/lms/HSSKeyPairGenerator.cs
@@ -2,21 +2,21 @@ using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class HSSKeyPairGenerator
-    : IAsymmetricCipherKeyPairGenerator
+    public sealed class HssKeyPairGenerator
+        : IAsymmetricCipherKeyPairGenerator
     {
-        HSSKeyGenerationParameters param;
+        private HssKeyGenerationParameters m_parameters;
 
-        public void Init(KeyGenerationParameters param)
+        public void Init(KeyGenerationParameters parameters)
         {
-            this.param = (HSSKeyGenerationParameters) param;
+            m_parameters = (HssKeyGenerationParameters)parameters;
         }
 
         public AsymmetricCipherKeyPair GenerateKeyPair()
         {
-            HSSPrivateKeyParameters privKey = HSS.GenerateHssKeyPair(param);
+            HssPrivateKeyParameters privKey = Hss.GenerateHssKeyPair(m_parameters);
 
             return new AsymmetricCipherKeyPair(privKey.GetPublicKey(), privKey);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/HSSPrivateKeyParameters.cs b/crypto/src/pqc/crypto/lms/HSSPrivateKeyParameters.cs
index fc85af1aa..447dbad27 100644
--- a/crypto/src/pqc/crypto/lms/HSSPrivateKeyParameters.cs
+++ b/crypto/src/pqc/crypto/lms/HSSPrivateKeyParameters.cs
@@ -6,29 +6,27 @@ using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
-// using static Org.BouncyCastle.Pqc.Crypto.Lms.HSS.rangeTestKeys;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class HSSPrivateKeyParameters
-        : LMSKeyParameters, ILMSContextBasedSigner
+    public class HssPrivateKeyParameters
+        : LmsKeyParameters, ILmsContextBasedSigner
     {
         private int l;
         private bool isShard;
-        private IList<LMSPrivateKeyParameters> keys;
-        private IList<LMSSignature> sig;
+        private IList<LmsPrivateKeyParameters> keys;
+        private IList<LmsSignature> sig;
         private long indexLimit;
         private long index = 0;
 
-        private HSSPublicKeyParameters publicKey;
+        private HssPublicKeyParameters publicKey;
 
-        public HSSPrivateKeyParameters(int l, IList<LMSPrivateKeyParameters> keys, IList<LMSSignature> sig, long index,
+        public HssPrivateKeyParameters(int l, IList<LmsPrivateKeyParameters> keys, IList<LmsSignature> sig, long index,
             long indexLimit)
     	    :base(true)
         {
             this.l = l;
-            this.keys = new List<LMSPrivateKeyParameters>(keys);
-            this.sig = new List<LMSSignature>(sig);
+            this.keys = new List<LmsPrivateKeyParameters>(keys);
+            this.sig = new List<LmsSignature>(sig);
             this.index = index;
             this.indexLimit = indexLimit;
             this.isShard = false;
@@ -39,7 +37,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             ResetKeyToIndex();
         }
 
-        private HSSPrivateKeyParameters(int l, IList<LMSPrivateKeyParameters> keys, IList<LMSSignature> sig, long index,
+        private HssPrivateKeyParameters(int l, IList<LmsPrivateKeyParameters> keys, IList<LmsSignature> sig, long index,
             long indexLimit, bool isShard)
     	    :base(true)
         {
@@ -47,75 +45,63 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             this.l = l;
             // this.keys =  new UnmodifiableListProxy(keys);
             // this.sig =  new UnmodifiableListProxy(sig);
-            this.keys = new List<LMSPrivateKeyParameters>(keys);
-            this.sig = new List<LMSSignature>(sig);
+            this.keys = new List<LmsPrivateKeyParameters>(keys);
+            this.sig = new List<LmsSignature>(sig);
             this.index = index;
             this.indexLimit = indexLimit;
             this.isShard = isShard;
         }
 
-        public static HSSPrivateKeyParameters GetInstance(byte[] privEnc, byte[] pubEnc)
+        public static HssPrivateKeyParameters GetInstance(byte[] privEnc, byte[] pubEnc)
         {
-            HSSPrivateKeyParameters pKey = GetInstance(privEnc);
+            HssPrivateKeyParameters pKey = GetInstance(privEnc);
 
-            pKey.publicKey = HSSPublicKeyParameters.GetInstance(pubEnc);
+            pKey.publicKey = HssPublicKeyParameters.GetInstance(pubEnc);
 
             return pKey;
         }
 
-        public static HSSPrivateKeyParameters GetInstance(Object src)
+        public static HssPrivateKeyParameters GetInstance(object src)
         {
-            if (src is HSSPrivateKeyParameters)
+            if (src is HssPrivateKeyParameters hssPrivateKeyParameters)
             {
-                return (HSSPrivateKeyParameters)src;
+                return hssPrivateKeyParameters;
             }
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                byte[] data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int version = BitConverter.ToInt32(data, 0);
+                int version = BinaryReaders.ReadInt32BigEndian(binaryReader);
                 if (version != 0)
-                {
-                    throw new Exception("unknown version for hss private key");
-                }
-                data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int d = BitConverter.ToInt32(data, 0);
-                
-                data = ((BinaryReader) src).ReadBytes(8);
-                Array.Reverse(data);
-                long index = BitConverter.ToInt64(data, 0);
-                
-                data = ((BinaryReader) src).ReadBytes(8);
-                Array.Reverse(data);
-                long maxIndex = BitConverter.ToInt64(data, 0);;
-                
-                data = ((BinaryReader) src).ReadBytes(1);
-                Array.Reverse(data);
-                bool limited =  BitConverter.ToBoolean(data, 0);
-                
-
-                var keys = new List<LMSPrivateKeyParameters>();
-                var signatures = new List<LMSSignature>();
+                    throw new Exception("unknown version for HSS private key");
+
+                int d = BinaryReaders.ReadInt32BigEndian(binaryReader);
+
+                long index = BinaryReaders.ReadInt64BigEndian(binaryReader);
+
+                long maxIndex = BinaryReaders.ReadInt64BigEndian(binaryReader);
+
+                bool limited = binaryReader.ReadBoolean();
+
+                var keys = new List<LmsPrivateKeyParameters>();
+                var signatures = new List<LmsSignature>();
 
                 for (int t = 0; t < d; t++)
                 {
-                    keys.Add(LMSPrivateKeyParameters.GetInstance(src));
+                    keys.Add(LmsPrivateKeyParameters.GetInstance(src));
                 }
 
                 for (int t = 0; t < d - 1; t++)
                 {
-                    signatures.Add(LMSSignature.GetInstance(src));
+                    signatures.Add(LmsSignature.GetInstance(src));
                 }
 
-                return new HSSPrivateKeyParameters(d, keys, signatures, index, maxIndex, limited);
+                return new HssPrivateKeyParameters(d, keys, signatures, index, maxIndex, limited);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.6 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream((byte[])src));
+                    input = new BinaryReader(new MemoryStream(bytes));
                     return GetInstance(input);
                 }
                 finally
@@ -126,9 +112,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     }
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
 
             throw new Exception($"cannot parse {src}");
@@ -136,25 +122,25 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
         public int L => l;
 
-        public  long GetIndex()
+        public long GetIndex()
         {
             lock (this)
                 return index;
         }
 
-        public LMSParameters[] GetLmsParameters()
+        public LmsParameters[] GetLmsParameters()
         {
             lock (this)
             {
                 int len = keys.Count;
 
-                LMSParameters[] parms = new LMSParameters[len];
+                LmsParameters[] parms = new LmsParameters[len];
 
                 for (int i = 0; i < len; i++)
                 {
-                    LMSPrivateKeyParameters lmsPrivateKey = (LMSPrivateKeyParameters) keys[i];
+                    LmsPrivateKeyParameters lmsPrivateKey = keys[i];
 
-                    parms[i] = new LMSParameters(lmsPrivateKey.GetSigParameters(), lmsPrivateKey.GetOtsParameters());
+                    parms[i] = new LmsParameters(lmsPrivateKey.GetSigParameters(), lmsPrivateKey.GetOtsParameters());
                 }
 
                 return parms;
@@ -169,24 +155,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             }
         }
 
-        private static HSSPrivateKeyParameters MakeCopy(HSSPrivateKeyParameters privateKeyParameters)
+        private static HssPrivateKeyParameters MakeCopy(HssPrivateKeyParameters privateKeyParameters)
         {
-            try
-            {
-                return HSSPrivateKeyParameters.GetInstance(privateKeyParameters.GetEncoded());
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message, ex);
-            }
+            return GetInstance(privateKeyParameters.GetEncoded());
         }
 
-        protected void UpdateHierarchy(IList<LMSPrivateKeyParameters> newKeys, IList<LMSSignature> newSig)
+        protected void UpdateHierarchy(IList<LmsPrivateKeyParameters> newKeys, IList<LmsSignature> newSig)
         {
             lock (this)
             {
-                keys = new List<LMSPrivateKeyParameters>(newKeys);
-                sig = new List<LMSSignature>(newSig);
+                keys = new List<LmsPrivateKeyParameters>(newKeys);
+                sig = new List<LmsSignature>(newSig);
             }
         }
 
@@ -202,9 +181,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return indexLimit - index;
         }
 
-        LMSPrivateKeyParameters GetRootKey()
+        LmsPrivateKeyParameters GetRootKey()
         {
-            return keys[0] as LMSPrivateKeyParameters;
+            return keys[0];
         }
 
         /**
@@ -216,7 +195,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
          * @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)
+        public HssPrivateKeyParameters ExtractKeyShard(int usageCount)
         {
             lock (this)
             {
@@ -227,15 +206,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 long shardStartIndex = index;
 
                 //
-                // Move this keys index along
+                // Move this key's index along
                 //
                 index += usageCount;
 
-                var keys = new List<LMSPrivateKeyParameters>(this.GetKeys());
-                var sig = new List<LMSSignature>(this.GetSig());
+                var keys = new List<LmsPrivateKeyParameters>(this.GetKeys());
+                var sig = new List<LmsSignature>(this.GetSig());
 
-                HSSPrivateKeyParameters shard = MakeCopy(new HSSPrivateKeyParameters(l, keys, sig, shardStartIndex,
-                    maxIndexForShard, true));
+                HssPrivateKeyParameters shard = MakeCopy(
+                    new HssPrivateKeyParameters(l, keys, sig, shardStartIndex, maxIndexForShard, true));
 
                 ResetKeyToIndex();
 
@@ -243,21 +222,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             }
         }
 
-
-        public IList<LMSPrivateKeyParameters> GetKeys()
+        public IList<LmsPrivateKeyParameters> GetKeys()
         {
-            lock (this)
-            {
-                return keys;
-            }
+            lock (this) return keys;
         }
 
-        internal IList<LMSSignature>GetSig()
+        internal IList<LmsSignature> GetSig()
         {
-            lock (this)
-            {
-                return sig;
-            }
+            lock (this) return sig;
         }
 
         /**
@@ -271,58 +243,55 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             // Extract the original keys
             var originalKeys = GetKeys();
 
-
             long[] qTreePath = new long[originalKeys.Count];
             long q = GetIndex();
 
             for (int t = originalKeys.Count - 1; t >= 0; t--)
             {
-                LMSigParameters sigParameters = (originalKeys[t] as LMSPrivateKeyParameters).GetSigParameters();
-                int mask = (1 << sigParameters.GetH()) - 1;
+                LMSigParameters sigParameters = originalKeys[t].GetSigParameters();
+                int mask = (1 << sigParameters.H) - 1;
                 qTreePath[t] = q & mask;
-                q >>= sigParameters.GetH();
+                q >>= sigParameters.H;
             }
 
             bool changed = false;
-            
-            
+
             // LMSPrivateKeyParameters[] keys =  originalKeys.ToArray(new LMSPrivateKeyParameters[originalKeys.Count]);//  new LMSPrivateKeyParameters[originalKeys.Size()];
             // LMSSignature[] sig = this.sig.toArray(new LMSSignature[this.sig.Count]);//   new LMSSignature[originalKeys.Size() - 1];
             //
-            
-            LMSPrivateKeyParameters originalRootKey = this.GetRootKey();
 
+            LmsPrivateKeyParameters originalRootKey = this.GetRootKey();
 
             //
             // We need to replace the root key to a new q value.
             //
-            if (((LMSPrivateKeyParameters)keys[0]).GetIndex() - 1 != qTreePath[0])
+            if (keys[0].GetIndex() - 1 != qTreePath[0])
             {
-                keys[0] = LMS.GenerateKeys(
+                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] as LMSPrivateKeyParameters;
+                LmsPrivateKeyParameters intermediateKey = keys[i - 1];
 
                 byte[] childI = new byte[16];
                 byte[] childSeed = new byte[32];
                 SeedDerive derive = new SeedDerive(
                     intermediateKey.GetI(),
                     intermediateKey.GetMasterSecret(),
-                    DigestUtilities.GetDigest(intermediateKey.GetOtsParameters().GetDigestOid()));
-                derive.SetQ((int)qTreePath[i - 1]);
-                derive.SetJ(~1);
+                    DigestUtilities.GetDigest(intermediateKey.GetOtsParameters().DigestOid))
+                {
+                    Q = (int)qTreePath[i - 1],
+                    J = ~1,
+                };
 
-                derive.deriveSeed(childSeed, true);
+                derive.DeriveSeed(true, childSeed, 0);
                 byte[] postImage = new byte[32];
-                derive.deriveSeed(postImage, false);
+                derive.DeriveSeed(false, postImage, 0);
                 Array.Copy(postImage, 0, childI, 0, childI.Length);
 
                 //
@@ -330,93 +299,86 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 // 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.
                 //
-                bool lmsQMatch =
-                    (i < qTreePath.Length - 1) ? qTreePath[i] == ((LMSPrivateKeyParameters)keys[i]).GetIndex() - 1 : qTreePath[i] == ((LMSPrivateKeyParameters)keys[i]).GetIndex();
+                bool 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.
                 //
-                bool seedEquals = Arrays.AreEqual(childI, ((LMSPrivateKeyParameters)keys[i]).GetI())
-                    && Arrays.AreEqual(childSeed, ((LMSPrivateKeyParameters)keys[i]).GetMasterSecret());
-
+                bool seedEquals = Arrays.AreEqual(childI, keys[i].GetI())
+                    && Arrays.AreEqual(childSeed, keys[i].GetMasterSecret());
 
                 if (!seedEquals)
                 {
                     //
                     // This means the parent has changed.
                     //
-                    keys[i] = LMS.GenerateKeys(
-                        ((LMSPrivateKeyParameters)originalKeys[i]).GetSigParameters(),
-                        ((LMSPrivateKeyParameters)originalKeys[i]).GetOtsParameters(),
+                    keys[i] = Lms.GenerateKeys(
+                        originalKeys[i].GetSigParameters(),
+                        originalKeys[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((LMSPrivateKeyParameters)keys[i - 1], ((LMSPrivateKeyParameters)keys[i]).GetPublicKey().ToByteArray());
+                    sig[i - 1] = Lms.GenerateSign((LmsPrivateKeyParameters)keys[i - 1], ((LmsPrivateKeyParameters)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(
-                        ((LMSPrivateKeyParameters)originalKeys[i]).GetSigParameters(),
-                        ((LMSPrivateKeyParameters)originalKeys[i]).GetOtsParameters(),
+                    keys[i] = Lms.GenerateKeys(
+                        originalKeys[i].GetSigParameters(),
+                        originalKeys[i].GetOtsParameters(),
                         (int)qTreePath[i], childI, childSeed);
                     changed = true;
                 }
-
             }
 
-
             if (changed)
             {
                 // We mutate the HSS key here!
                 UpdateHierarchy(keys, sig);
             }
-
         }
 
-        public HSSPublicKeyParameters GetPublicKey()
+        public HssPublicKeyParameters GetPublicKey()
         {
             lock (this)
-                return new HSSPublicKeyParameters(l, GetRootKey().GetPublicKey());
+                return new HssPublicKeyParameters(l, GetRootKey().GetPublicKey());
         }
 
         internal void ReplaceConsumedKey(int d)
         {
-
-            SeedDerive deriver = (keys[d - 1] as LMSPrivateKeyParameters).GetCurrentOtsKey().GetDerivationFunction();
-            deriver.SetJ(~1);
+            SeedDerive deriver = keys[d - 1].GetCurrentOtsKey().GetDerivationFunction();
+            deriver.J = ~1;
             byte[] childRootSeed = new byte[32];
-            deriver.deriveSeed(childRootSeed, true);
+            deriver.DeriveSeed(true, childRootSeed, 0);
             byte[] postImage = new byte[32];
-            deriver.deriveSeed(postImage, false);
+            deriver.DeriveSeed(false, postImage, 0);
             byte[] childI = new byte[16];
             Array.Copy(postImage, 0, childI, 0, childI.Length);
 
-            var newKeys = new List<LMSPrivateKeyParameters>(keys);
+            var newKeys = new List<LmsPrivateKeyParameters>(keys);
 
             //
             // We need the parameters from the LMS key we are replacing.
             //
-            LMSPrivateKeyParameters oldPk = keys[d] as LMSPrivateKeyParameters;
-
+            LmsPrivateKeyParameters oldPk = keys[d];
 
-            newKeys[d] = LMS.GenerateKeys(oldPk.GetSigParameters(), oldPk.GetOtsParameters(), 0, childI, childRootSeed);
+            newKeys[d] = Lms.GenerateKeys(oldPk.GetSigParameters(), oldPk.GetOtsParameters(), 0, childI, childRootSeed);
 
-            var newSig = new List<LMSSignature>(sig);
+            var newSig = new List<LmsSignature>(sig);
 
-            newSig[d - 1] = LMS.GenerateSign(newKeys[d - 1] as LMSPrivateKeyParameters,
-                (newKeys[d] as LMSPrivateKeyParameters).GetPublicKey().ToByteArray());
+            newSig[d - 1] = Lms.GenerateSign(newKeys[d - 1], newKeys[d].GetPublicKey().ToByteArray());
 
-            this.keys = new List<LMSPrivateKeyParameters>(newKeys);
-            this.sig = new List<LMSSignature>(newSig);
+            this.keys = new List<LmsPrivateKeyParameters>(newKeys);
+            this.sig = new List<LmsSignature>(newSig);
         }
 
         public override bool Equals(Object o)
@@ -430,7 +392,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 return false;
             }
 
-            HSSPrivateKeyParameters that = (HSSPrivateKeyParameters)o;
+            HssPrivateKeyParameters that = (HssPrivateKeyParameters)o;
 
             if (l != that.l)
             {
@@ -481,14 +443,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     .U32Str(l)
                     .U64Str(index)
                     .U64Str(indexLimit)
-                    .GetBool(isShard); // Depth
+                    .Boolean(isShard); // Depth
 
-                foreach (LMSPrivateKeyParameters key in keys)
+                foreach (LmsPrivateKeyParameters key in keys)
                 {
                     composer.Bytes(key);
                 }
 
-                foreach (LMSSignature s in sig)
+                foreach (LmsSignature s in sig)
                 {
                     composer.Bytes(s);
                 }
@@ -508,35 +470,33 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return result;
         }
 
-        protected Object Clone()
+        protected object Clone()
         {
             return MakeCopy(this);
         }
 
-        public LMSContext GenerateLmsContext()
+        public LmsContext GenerateLmsContext()
         {
-            LMSSignedPubKey[] signed_pub_key;
-            LMSPrivateKeyParameters nextKey;
+            LmsSignedPubKey[] signed_pub_key;
+            LmsPrivateKeyParameters nextKey;
             int L = this.L;
 
             lock (this)
             {
-                HSS.RangeTestKeys(this);
+                Hss.RangeTestKeys(this);
 
                 var keys = this.GetKeys();
                 var sig = this.GetSig();
 
-                nextKey = this.GetKeys()[(L - 1)] as LMSPrivateKeyParameters;
+                nextKey = this.GetKeys()[L - 1];
 
                 // Step 2. Stand in for sig[L-1]
                 int i = 0;
-                signed_pub_key = new LMSSignedPubKey[L - 1];
+                signed_pub_key = new LmsSignedPubKey[L - 1];
                 while (i < L - 1)
                 {
-                    signed_pub_key[i] = new LMSSignedPubKey(
-                        sig[i] as LMSSignature,
-                        (keys[i + 1] as LMSPrivateKeyParameters).GetPublicKey());
-                    i = i + 1;
+                    signed_pub_key[i] = new LmsSignedPubKey(sig[i], keys[i + 1].GetPublicKey());
+                    ++i;
                 }
 
                 //
@@ -548,11 +508,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return nextKey.GenerateLmsContext().WithSignedPublicKeys(signed_pub_key);
         }
 
-        public byte[] GenerateSignature(LMSContext context)
+        public byte[] GenerateSignature(LmsContext context)
         {
             try
             {
-                return HSS.GenerateSignature(L, context).GetEncoded();
+                return Hss.GenerateSignature(L, context).GetEncoded();
             }
             catch (IOException e)
             {
@@ -560,4 +520,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/HSSPublicKeyParameters.cs b/crypto/src/pqc/crypto/lms/HSSPublicKeyParameters.cs
index d833e674a..85b781228 100644
--- a/crypto/src/pqc/crypto/lms/HSSPublicKeyParameters.cs
+++ b/crypto/src/pqc/crypto/lms/HSSPublicKeyParameters.cs
@@ -1,43 +1,42 @@
 using System;
 using System.IO;
+
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class HSSPublicKeyParameters
-        : LMSKeyParameters, ILMSContextBasedVerifier
+    public sealed class HssPublicKeyParameters
+        : LmsKeyParameters, ILmsContextBasedVerifier
     {
-        private int l;
-        private LMSPublicKeyParameters lmsPublicKey;
+        private readonly int m_l;
+        private readonly LmsPublicKeyParameters m_lmsPublicKey;
 
-        public HSSPublicKeyParameters(int l, LMSPublicKeyParameters lmsPublicKey)
+        public HssPublicKeyParameters(int l, LmsPublicKeyParameters lmsPublicKey)
     	    :base(false)
         {
-
-            this.l = l;
-            this.lmsPublicKey = lmsPublicKey;
+            m_l = l;
+            m_lmsPublicKey = lmsPublicKey;
         }
 
-        public static HSSPublicKeyParameters GetInstance(Object src)
+        public static HssPublicKeyParameters GetInstance(object src)
         {
-            if (src is HSSPublicKeyParameters)
+            if (src is HssPublicKeyParameters hssPublicKeyParameters)
             {
-                return (HSSPublicKeyParameters)src;
+                return hssPublicKeyParameters;
             }
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                byte[] data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int L = BitConverter.ToInt32(data, 0);
-                LMSPublicKeyParameters lmsPublicKey = LMSPublicKeyParameters.GetInstance(src);// todo check endianness
-                return new HSSPublicKeyParameters(L, lmsPublicKey);
+                int L = BinaryReaders.ReadInt32BigEndian(binaryReader);
+
+                LmsPublicKeyParameters lmsPublicKey = LmsPublicKeyParameters.GetInstance(src);
+                return new HssPublicKeyParameters(L, lmsPublicKey);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.6 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream((byte[])src));
+                    input = new BinaryReader(new MemoryStream(bytes));
                     return GetInstance(input);
                 }
                 finally
@@ -45,94 +44,78 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     if (input != null) input.Close();
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
 
             throw new ArgumentException($"cannot parse {src}");
         }
 
-        public int GetL()
-        {
-            return l;
-        }
+        public int L => m_l;
+
+        public LmsPublicKeyParameters LmsPublicKey => m_lmsPublicKey;
 
-        public LMSPublicKeyParameters GetLmsPublicKey()
-        {
-            return lmsPublicKey;
-        }
-        
         public override bool Equals(Object o)
         {
             if (this == o)
-            {
                 return true;
-            }
             if (o == null || GetType() != o.GetType())
-            {
                 return false;
-            }
 
-            HSSPublicKeyParameters publicKey = (HSSPublicKeyParameters)o;
+            HssPublicKeyParameters publicKey = (HssPublicKeyParameters)o;
 
-            if (l != publicKey.l)
-            {
-                return false;
-            }
-            return lmsPublicKey.Equals(publicKey.lmsPublicKey);
+            return m_l == publicKey.m_l
+                && m_lmsPublicKey.Equals(publicKey.m_lmsPublicKey);
         }
 
         public override int GetHashCode()
         {
-            int result = l;
-            result = 31 * result + lmsPublicKey.GetHashCode();
+            int result = m_l;
+            result = 31 * result + m_lmsPublicKey.GetHashCode();
             return result;
         }
 
         public override byte[] GetEncoded()
         {
-            return Composer.Compose().U32Str(l)
-                .Bytes(lmsPublicKey.GetEncoded())
+            return Composer.Compose().U32Str(m_l)
+                .Bytes(m_lmsPublicKey.GetEncoded())
                 .Build();
         }
 
-        public LMSContext GenerateLmsContext(byte[] sigEnc)
+        public LmsContext GenerateLmsContext(byte[] sigEnc)
         {
-            HSSSignature signature;
+            HssSignature signature;
             try
             {
-                signature = HSSSignature.GetInstance(sigEnc, GetL());
+                signature = HssSignature.GetInstance(sigEnc, L);
             }
             catch (IOException e)
             {
                 throw new Exception($"cannot parse signature: {e.Message}");
             }
 
-            LMSSignedPubKey[] signedPubKeys = signature.GetSignedPubKey();
-            LMSPublicKeyParameters key = signedPubKeys[signedPubKeys.Length - 1].GetPublicKey();
+            LmsSignedPubKey[] signedPubKeys = signature.GetSignedPubKeys();
+            LmsPublicKeyParameters key = signedPubKeys[signedPubKeys.Length - 1].GetPublicKey();
 
-            return key.GenerateOtsContext(signature.GetSignature()).WithSignedPublicKeys(signedPubKeys);
+            return key.GenerateOtsContext(signature.Signature).WithSignedPublicKeys(signedPubKeys);
         }
 
-        public bool Verify(LMSContext context)
+        public bool Verify(LmsContext context)
         {
-            bool failed = false;
-
-            LMSSignedPubKey[] sigKeys = context.GetSignedPubKeys();
+            LmsSignedPubKey[] sigKeys = context.SignedPubKeys;
 
-            if (sigKeys.Length != GetL() - 1)
-            {
+            if (sigKeys.Length != L - 1)
                 return false;
-            }
 
-            LMSPublicKeyParameters key = GetLmsPublicKey();
+            LmsPublicKeyParameters key = LmsPublicKey;
+            bool failed = false;
 
             for (int i = 0; i < sigKeys.Length; i++)
             {
-                LMSSignature sig = sigKeys[i].GetSignature();
+                LmsSignature sig = sigKeys[i].GetSignature();
                 byte[] msg = sigKeys[i].GetPublicKey().ToByteArray();
-                if (!LMS.VerifySignature(key, sig, msg))
+                if (!Lms.VerifySignature(key, sig, msg))
                 {
                     failed = true;
                 }
diff --git a/crypto/src/pqc/crypto/lms/HSSSignature.cs b/crypto/src/pqc/crypto/lms/HSSSignature.cs
index 022ae7de3..21f0397c8 100644
--- a/crypto/src/pqc/crypto/lms/HSSSignature.cs
+++ b/crypto/src/pqc/crypto/lms/HSSSignature.cs
@@ -1,70 +1,63 @@
-
 using System;
 using System.IO;
-using Org.BouncyCastle.Pqc.Crypto.Lms;
+
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-
-    public class HSSSignature
+    public sealed class HssSignature
         : IEncodable
     {
-        private int lMinus1;
-        private LMSSignedPubKey[] signedPubKey;
-        private LMSSignature signature;
+        private readonly int m_lMinus1;
+        private readonly LmsSignedPubKey[] m_signedPubKey;
+        private readonly LmsSignature m_signature;
 
-        public HSSSignature(int lMinus1, LMSSignedPubKey[] signedPubKey, LMSSignature signature)
+        public HssSignature(int lMinus1, LmsSignedPubKey[] signedPubKey, LmsSignature signature)
         {
-            this.lMinus1 = lMinus1;
-            this.signedPubKey = signedPubKey;
-            this.signature = signature;
+            m_lMinus1 = lMinus1;
+            m_signedPubKey = signedPubKey;
+            m_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)
+         * @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)
         {
-            if (src is HSSSignature)
+            if (src is HssSignature hssSignature)
             {
-                return (HSSSignature) src;
+                return hssSignature;
             }
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                byte[] data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int lminus = BitConverter.ToInt32(data, 0);
+                int lminus = BinaryReaders.ReadInt32BigEndian(binaryReader);
                 if (lminus != L - 1)
-                {
                     throw new Exception("nspk exceeded maxNspk");
-                }
 
-                LMSSignedPubKey[] signedPubKeys = new LMSSignedPubKey[lminus];
+                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));
+                        signedPubKeys[t] = new LmsSignedPubKey(LmsSignature.GetInstance(src),
+                            LmsPublicKeyParameters.GetInstance(src));
                     }
                 }
 
-                LMSSignature sig = LMSSignature.GetInstance(src);
+                LmsSignature sig = LmsSignature.GetInstance(src);
 
-                return new HSSSignature(lminus, signedPubKeys, sig);
+                return new HssSignature(lminus, signedPubKeys, sig);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.6 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream((byte[]) src));
+                    input = new BinaryReader(new MemoryStream(bytes));
                     return GetInstance(input, L);
                 }
                 finally
@@ -72,90 +65,71 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     if (input != null) input.Close();
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream) src), L);
+                return GetInstance(Streams.ReadAll(memoryStream), L);
             }
 
             throw new ArgumentException($"cannot parse {src}");
         }
 
-
-        public int GetlMinus1()
+        public int GetLMinus1()
         {
-            return lMinus1;
+            return m_lMinus1;
         }
 
-        public LMSSignedPubKey[] GetSignedPubKey()
+        // FIXME
+        public LmsSignedPubKey[] GetSignedPubKeys()
         {
-            return signedPubKey;
+            return m_signedPubKey;
         }
 
-        public LMSSignature GetSignature()
-        {
-            return signature;
-        }
+        public LmsSignature Signature => m_signature;
 
-        public override bool Equals(Object o)
+        public override bool Equals(object other)
         {
-            if (this == o)
-            {
+            if (this == other)
                 return true;
-            }
-
-            if (o == null || GetType() != o.GetType())
-            {
+            if (!(other is HssSignature that))
                 return false;
-            }
-
-            HSSSignature signature1 = (HSSSignature) o;
 
-            if (lMinus1 != signature1.lMinus1)
-            {
+            if (this.m_lMinus1 != that.m_lMinus1)
                 return false;
-            }
-            // Probably incorrect - comparing Object[] arrays with Arrays.equals
 
-            if (signedPubKey.Length != signature1.signedPubKey.Length)
-            {
+            if (this.m_signedPubKey.Length != that.m_signedPubKey.Length)
                 return false;
-            }
 
-            for (int t = 0; t < signedPubKey.Length; t++)
+            for (int t = 0; t < m_signedPubKey.Length; t++)
             {
-                if (!signedPubKey[t].Equals(signature1.signedPubKey[t]))
-                {
+                if (!this.m_signedPubKey[t].Equals(that.m_signedPubKey[t]))
                     return false;
-                }
             }
 
-            return signature != null ? signature.Equals(signature1.signature) : signature1.signature == null;
+            return Equals(this.m_signature, that.m_signature);
         }
 
         public override int GetHashCode()
         {
-            int result = lMinus1;
-            result = 31 * result + signedPubKey.GetHashCode();
-            result = 31 * result + (signature != null ? signature.GetHashCode() : 0);
+            int result = m_lMinus1;
+            result = 31 * result + m_signedPubKey.GetHashCode();
+            result = 31 * result + (m_signature != null ? m_signature.GetHashCode() : 0);
             return result;
         }
 
         public byte[] GetEncoded()
         {
             Composer composer = Composer.Compose();
-            composer.U32Str(lMinus1);
-            if (signedPubKey != null)
+            composer.U32Str(m_lMinus1);
+            if (m_signedPubKey != null)
             {
-                foreach (LMSSignedPubKey sigPub in signedPubKey)
+                foreach (LmsSignedPubKey sigPub in m_signedPubKey)
                 {
                     composer.Bytes(sigPub);
                 }
             }
 
-            composer.Bytes(signature);
+            composer.Bytes(m_signature);
             return composer.Build();
-
         }
-
     }
 }
diff --git a/crypto/src/pqc/crypto/lms/HSSSigner.cs b/crypto/src/pqc/crypto/lms/HSSSigner.cs
index 9f361d20b..9ef7b57ae 100644
--- a/crypto/src/pqc/crypto/lms/HSSSigner.cs
+++ b/crypto/src/pqc/crypto/lms/HSSSigner.cs
@@ -5,21 +5,21 @@ using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class HSSSigner 
+    public sealed class HssSigner 
         : IMessageSigner
     {
-        private HSSPrivateKeyParameters privKey;
-        private HSSPublicKeyParameters pubKey;
+        private HssPrivateKeyParameters privKey;
+        private HssPublicKeyParameters pubKey;
 
         public void Init(bool forSigning, ICipherParameters param)
         {
             if (forSigning)
             {
-                this.privKey = (HSSPrivateKeyParameters) param;
+                this.privKey = (HssPrivateKeyParameters) param;
             }
             else
             {
-                this.pubKey = (HSSPublicKeyParameters) param;
+                this.pubKey = (HssPublicKeyParameters) param;
             }
         }
 
@@ -27,7 +27,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             try
             {
-                return HSS.GenerateSignature(privKey, message).GetEncoded();
+                return Hss.GenerateSignature(privKey, message).GetEncoded();
             }
             catch (IOException e)
             {
@@ -39,7 +39,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             try
             {
-                return HSS.VerifySignature(pubKey, HSSSignature.GetInstance(signature, pubKey.GetL()), message);
+                return Hss.VerifySignature(pubKey, HssSignature.GetInstance(signature, pubKey.L), message);
             }
             catch (IOException e)
             {
diff --git a/crypto/src/pqc/crypto/lms/ILMSContextBasedSigner.cs b/crypto/src/pqc/crypto/lms/ILMSContextBasedSigner.cs
index 86144b8e8..94864b471 100644
--- a/crypto/src/pqc/crypto/lms/ILMSContextBasedSigner.cs
+++ b/crypto/src/pqc/crypto/lms/ILMSContextBasedSigner.cs
@@ -1,10 +1,10 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public interface ILMSContextBasedSigner
+    public interface ILmsContextBasedSigner
     {
-        LMSContext GenerateLmsContext();
+        LmsContext GenerateLmsContext();
 
-        byte[] GenerateSignature(LMSContext context);
+        byte[] GenerateSignature(LmsContext context);
 
         long GetUsagesRemaining();
     }
diff --git a/crypto/src/pqc/crypto/lms/ILMSContextBasedVerifier.cs b/crypto/src/pqc/crypto/lms/ILMSContextBasedVerifier.cs
index 76538d052..ffba83ad2 100644
--- a/crypto/src/pqc/crypto/lms/ILMSContextBasedVerifier.cs
+++ b/crypto/src/pqc/crypto/lms/ILMSContextBasedVerifier.cs
@@ -1,9 +1,9 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public interface ILMSContextBasedVerifier
+    public interface ILmsContextBasedVerifier
     {
-        LMSContext GenerateLmsContext(byte[] signature);
+        LmsContext GenerateLmsContext(byte[] signature);
 
-        bool Verify(LMSContext context);
+        bool Verify(LmsContext context);
     }
 }
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/lms/LMOtsParameters.cs b/crypto/src/pqc/crypto/lms/LMOtsParameters.cs
index ca237fc5e..60bf28d50 100644
--- a/crypto/src/pqc/crypto/lms/LMOtsParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMOtsParameters.cs
@@ -1,86 +1,66 @@
 using System;
 using System.Collections.Generic;
+
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMOtsParameters
+    public sealed class LMOtsParameters
     {
-        //TODO add all parameter sets
+        // TODO add all parameter sets
 
-        public static int reserved = 0;
+        //public static int reserved = 0;
         public static LMOtsParameters sha256_n32_w1 = new LMOtsParameters(1, 32, 1, 265, 7, 8516, NistObjectIdentifiers.IdSha256);
         public static LMOtsParameters sha256_n32_w2 = new LMOtsParameters(2, 32, 2, 133, 6, 4292, NistObjectIdentifiers.IdSha256);
         public static LMOtsParameters sha256_n32_w4 = new LMOtsParameters(3, 32, 4, 67, 4, 2180, NistObjectIdentifiers.IdSha256);
         public static LMOtsParameters sha256_n32_w8 = new LMOtsParameters(4, 32, 8, 34, 0, 1124, NistObjectIdentifiers.IdSha256);
 
-        private static Dictionary<Object, LMOtsParameters> suppliers = new Dictionary<object, LMOtsParameters>
+        private static Dictionary<object, LMOtsParameters> Suppliers = new Dictionary<object, LMOtsParameters>
         {
-            { sha256_n32_w1.type, sha256_n32_w1 },
-            { sha256_n32_w2.type, sha256_n32_w2 },
-            { sha256_n32_w4.type, sha256_n32_w4 },
-            { sha256_n32_w8.type, sha256_n32_w8 }
+            { sha256_n32_w1.ID, sha256_n32_w1 },
+            { sha256_n32_w2.ID, sha256_n32_w2 },
+            { sha256_n32_w4.ID, sha256_n32_w4 },
+            { sha256_n32_w8.ID, sha256_n32_w8 }
         };
-        
-        
-        private int type;
-        private int n;
-        private int w;
-        private int p;
-        private int ls;
-        private uint sigLen;
-        private DerObjectIdentifier digestOID;
 
-        protected LMOtsParameters(int type, int n, int w, int p, int ls, uint sigLen, DerObjectIdentifier digestOID)
-        {
-            this.type = type;
-            this.n = n;
-            this.w = w;
-            this.p = p;
-            this.ls = ls;
-            this.sigLen = sigLen;
-            this.digestOID = digestOID;
-        }
-        public new int GetType()
-        {
-            return type;
-        }
+        private readonly int m_id;
+        private readonly int m_n;
+        private readonly int m_w;
+        private readonly int m_p;
+        private readonly int m_ls;
+        private readonly uint m_sigLen;
+        private readonly DerObjectIdentifier m_digestOid;
 
-        public int GetN()
+        internal LMOtsParameters(int id, int n, int w, int p, int ls, uint sigLen, DerObjectIdentifier digestOid)
         {
-            return n;
+            m_id = id;
+            m_n = n;
+            m_w = w;
+            m_p = p;
+            m_ls = ls;
+            m_sigLen = sigLen;
+            m_digestOid = digestOid;
         }
 
-        public int GetW()
-        {
-            return w;
-        }
+        public int ID => m_id;
 
-        public int GetP()
-        {
-            return p;
-        }
+        public int N => m_n;
 
-        public int GetLs()
-        {
-            return ls;
-        }
+        public int W => m_w;
 
-        public uint GetSigLen()
-        {
-            return sigLen;
-        }
+        public int P => m_p;
 
-        public DerObjectIdentifier GetDigestOid()
-        {
-            return digestOID;
-        }
+        public int Ls => m_ls;
+
+        public int SigLen => Convert.ToInt32(m_sigLen);
 
-        public static LMOtsParameters GetParametersForType(int type)
+        public DerObjectIdentifier DigestOid => m_digestOid;
+
+        public static LMOtsParameters GetParametersByID(int id)
         {
-            return suppliers[type];
+            return CollectionUtilities.GetValueOrNull(Suppliers, id);
         }
-
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMOtsPrivateKey.cs b/crypto/src/pqc/crypto/lms/LMOtsPrivateKey.cs
index e5ed9d7b0..20b717af6 100644
--- a/crypto/src/pqc/crypto/lms/LMOtsPrivateKey.cs
+++ b/crypto/src/pqc/crypto/lms/LMOtsPrivateKey.cs
@@ -1,69 +1,58 @@
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
-using static Org.BouncyCastle.Pqc.Crypto.Lms.LM_OTS;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMOtsPrivateKey
+    public sealed class LMOtsPrivateKey
     {
-        private LMOtsParameters parameter;
-        private byte[] I;
-        private int q;
-        private byte[] masterSecret;
+        private readonly LMOtsParameters m_parameters;
+        private readonly byte[] m_I;
+        private readonly int m_q;
+        private readonly byte[] m_masterSecret;
 
-        public LMOtsPrivateKey(LMOtsParameters parameter, byte[] i, int q, byte[] masterSecret)
+        public LMOtsPrivateKey(LMOtsParameters parameters, byte[] i, int q, byte[] masterSecret)
         {
-            this.parameter = parameter;
-            I = i;
-            this.q = q;
-            this.masterSecret = masterSecret;
+            m_parameters = parameters;
+            m_I = i;
+            m_q = q;
+            m_masterSecret = masterSecret;
         }
 
-        public LMSContext GetSignatureContext(LMSigParameters sigParams, byte[][] path)
+        public LmsContext GetSignatureContext(LMSigParameters sigParams, byte[][] path)
         {
-            byte[] C = new byte[SEED_LEN];
+            byte[] C = new byte[LMOts.SEED_LEN];
 
             SeedDerive derive = GetDerivationFunction();
-            derive.SetJ(SEED_RANDOMISER_INDEX); // This value from reference impl.
-            derive.deriveSeed(C, false);
+            derive.J = LMOts.SEED_RANDOMISER_INDEX; // This value from reference impl.
+            derive.DeriveSeed(false, C, 0);
 
-            IDigest ctx = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest ctx = DigestUtilities.GetDigest(m_parameters.DigestOid);
 
-            LmsUtils.ByteArray(this.GetI(), ctx);
-            LmsUtils.U32Str(this.GetQ(), ctx);
-            LmsUtils.U16Str(D_MESG, ctx);
-            LmsUtils.ByteArray(C, ctx);
+            LmsUtilities.ByteArray(m_I, ctx);
+            LmsUtilities.U32Str(m_q, ctx);
+            LmsUtilities.U16Str((short)LMOts.D_MESG, ctx);
+            LmsUtilities.ByteArray(C, ctx);
 
-            return new LMSContext(this, sigParams, ctx, C, path);
+            return new LmsContext(this, sigParams, ctx, C, path);
         }
 
         internal SeedDerive GetDerivationFunction()
         {
-            SeedDerive derive = new SeedDerive(I, masterSecret, DigestUtilities.GetDigest(parameter.GetDigestOid()));
-            derive.SetQ(q);
-            return derive;
+            return new SeedDerive(m_I, m_masterSecret, DigestUtilities.GetDigest(m_parameters.DigestOid))
+            {
+                Q = m_q,
+                J = 0,
+            };
         }
 
+        public LMOtsParameters Parameters => m_parameters;
 
-        public LMOtsParameters GetParameter()
-        {
-            return parameter;
-        }
-
-        public byte[] GetI()
-        {
-            return I;
-        }
+        // FIXME
+        public byte[] I => m_I;
 
-        public int GetQ()
-        {
-            return q;
-        }
+        public int Q => m_q;
 
-        public byte[] GetMasterSecret()
-        {
-            return masterSecret;
-        }
+        // FIXME
+        public byte[] MasterSecret => m_masterSecret;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMOtsPublicKey.cs b/crypto/src/pqc/crypto/lms/LMOtsPublicKey.cs
index c3d1794ff..ef3d4aced 100644
--- a/crypto/src/pqc/crypto/lms/LMOtsPublicKey.cs
+++ b/crypto/src/pqc/crypto/lms/LMOtsPublicKey.cs
@@ -1,63 +1,54 @@
 using System;
 using System.IO;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
-using static Org.BouncyCastle.Pqc.Crypto.Lms.LM_OTS;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMOtsPublicKey
+    public sealed class LMOtsPublicKey
     {
-        private LMOtsParameters parameter;
-        private byte[] I;
-        private int q;
-        private byte[] K;
+        private readonly LMOtsParameters m_parameters;
+        private readonly byte[] m_I;
+        private readonly int m_q;
+        private readonly byte[] m_K;
 
-
-        public LMOtsPublicKey(LMOtsParameters parameter, byte[] i, int q, byte[] k)
+        public LMOtsPublicKey(LMOtsParameters parameters, byte[] i, int q, byte[] k)
         {
-            this.parameter = parameter;
-            this.I = i;
-            this.q = q;
-            this.K = k;
+            m_parameters = parameters;
+            m_I = i;
+            m_q = q;
+            m_K = k;
         }
 
-        public static LMOtsPublicKey GetInstance(Object src)
+        public static LMOtsPublicKey GetInstance(object src)
         {
-            
             //todo
-            if (src is LMOtsPublicKey)
+            if (src is LMOtsPublicKey lmOtsPublicKey)
             {
-                return (LMOtsPublicKey)src;
+                return lmOtsPublicKey;
             }
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                byte[] data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int index = BitConverter.ToInt32(data, 0);
-                
-                LMOtsParameters parameter = LMOtsParameters.GetParametersForType(index);
-                byte[] I = new byte[16];
-                ((BinaryReader)src).Read(I, 0, I.Length);
-                
-                Array.Reverse(data);
-                int q = BitConverter.ToInt32(data, 0);
-
-                byte[] K = new byte[parameter.GetN()];
-                ((BinaryReader)src).Read(K, 0, K.Length);
+                int index = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMOtsParameters parameter = LMOtsParameters.GetParametersByID(index);
 
-                return new LMOtsPublicKey(parameter, I, q, K);
+                byte[] I = BinaryReaders.ReadBytesFully(binaryReader, 16);
 
+                int q = BinaryReaders.ReadInt32BigEndian(binaryReader);
+
+                byte[] K = BinaryReaders.ReadBytesFully(binaryReader, parameter.N);
+
+                return new LMOtsPublicKey(parameter, I, q, K);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.6 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream((byte[])src, false));
+                    input = new BinaryReader(new MemoryStream(bytes, false));
                     return GetInstance(input);
                 }
                 finally
@@ -65,102 +56,75 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     if (input != null) input.Close();//todo Platform Dispose
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
             throw new Exception ($"cannot parse {src}");
         }
 
-        public LMOtsParameters GetParameter()
-        {
-            return parameter;
-        }
+        public LMOtsParameters Parameters => m_parameters;
 
-        public byte[] GetI()
-        {
-            return I;
-        }
+        public byte[] I => m_I;
 
-        public int GetQ()
-        {
-            return q;
-        }
+        public int Q => m_q;
 
-        public byte[] GetK()
-        {
-            return K;
-        }
+        public byte[] K => m_K;
 
-        public override bool Equals(Object o)
+        public override bool Equals(object obj)
         {
-            if (this == o)
-            {
+            if (this == obj)
                 return true;
-            }
-            if (o == null || GetType() != o.GetType())
-            {
+            if (!(obj is LMOtsPublicKey that))
                 return false;
-            }
 
-            LMOtsPublicKey that = (LMOtsPublicKey)o;
-
-            if (q != that.q)
-            {
-                return false;
-            }
-            if (!parameter?.Equals(that.parameter) ?? that.parameter != null)
-            {
-                return false;
-            }
-            if (!Arrays.Equals(I, that.I))
-            {
-                return false;
-            }
-            return Arrays.Equals(K, that.K);
+            return m_q == that.m_q
+                && Objects.Equals(m_parameters, that.m_parameters)
+                && Arrays.AreEqual(m_I, that.m_I)
+                && Arrays.AreEqual(m_K, that.m_K);
         }
 
         public override int GetHashCode()
         {
-            int result = parameter != null ? parameter.GetHashCode() : 0;
-            result = 31 * result + Arrays.GetHashCode(I);
-            result = 31 * result + q;
-            result = 31 * result + Arrays.GetHashCode(K);
+            int result = Objects.GetHashCode(m_parameters);
+            result = 31 * result + Arrays.GetHashCode(m_I);
+            result = 31 * result + m_q;
+            result = 31 * result + Arrays.GetHashCode(m_K);
             return result;
         }
 
         public byte[] GetEncoded()
         {
             return Composer.Compose()
-                .U32Str(parameter.GetType())
-                .Bytes(I)
-                .U32Str(q)
-                .Bytes(K)
+                .U32Str(m_parameters.ID)
+                .Bytes(m_I)
+                .U32Str(m_q)
+                .Bytes(m_K)
                 .Build();
         }
 
-        internal LMSContext CreateOtsContext(LMOtsSignature signature)
+        internal LmsContext CreateOtsContext(LMOtsSignature signature)
         {
-            IDigest ctx = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest ctx = DigestUtilities.GetDigest(m_parameters.DigestOid);
 
-            LmsUtils.ByteArray(I, ctx);
-            LmsUtils.U32Str(q, ctx);
-            LmsUtils.U16Str(D_MESG, ctx);
-            LmsUtils.ByteArray(signature.GetC(), ctx);
+            LmsUtilities.ByteArray(m_I, ctx);
+            LmsUtilities.U32Str(m_q, ctx);
+            LmsUtilities.U16Str((short)LMOts.D_MESG, ctx);
+            LmsUtilities.ByteArray(signature.C, ctx);
 
-            return new LMSContext(this, signature, ctx);
+            return new LmsContext(this, signature, ctx);
         }
 
-        internal LMSContext CreateOtsContext(LMSSignature signature)
+        internal LmsContext CreateOtsContext(LmsSignature signature)
         {
-            IDigest ctx = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest ctx = DigestUtilities.GetDigest(m_parameters.DigestOid);
 
-            LmsUtils.ByteArray(I, ctx);
-            LmsUtils.U32Str(q, ctx);
-            LmsUtils.U16Str(D_MESG, ctx);
-            LmsUtils.ByteArray(signature.GetOtsSignature().GetC(), ctx);
+            LmsUtilities.ByteArray(m_I, ctx);
+            LmsUtilities.U32Str(m_q, ctx);
+            LmsUtilities.U16Str((short)LMOts.D_MESG, ctx);
+            LmsUtilities.ByteArray(signature.OtsSignature.C, ctx);
 
-            return new LMSContext(this, signature, ctx);
+            return new LmsContext(this, signature, ctx);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMOtsSignature.cs b/crypto/src/pqc/crypto/lms/LMOtsSignature.cs
index 599255fbd..c55866661 100644
--- a/crypto/src/pqc/crypto/lms/LMOtsSignature.cs
+++ b/crypto/src/pqc/crypto/lms/LMOtsSignature.cs
@@ -1,54 +1,48 @@
 using System;
 using System.IO;
+
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMOtsSignature
+    public sealed class LMOtsSignature
         : IEncodable
     {
-        private LMOtsParameters ParamType;
-        private byte[] C;
-        private byte[] y;
+        private readonly LMOtsParameters m_paramType;
+        private readonly byte[] m_C;
+        private readonly byte[] m_y;
 
-        public LMOtsSignature(LMOtsParameters ParamType, byte[] c, byte[] y)
+        public LMOtsSignature(LMOtsParameters paramType, byte[] c, byte[] y)
         {
-            this.ParamType = ParamType;
-            C = c;
-            this.y = y;
+            m_paramType = paramType;
+            m_C = c;
+            m_y = y;
         }
 
-        public static LMOtsSignature GetInstance(Object src)
+        public static LMOtsSignature GetInstance(object src)
         {
-            if (src is LMOtsSignature)
+            if (src is LMOtsSignature lmOtsSignature)
             {
-                return (LMOtsSignature)src;
+                return lmOtsSignature;
             }
-            //TODO replace inputstreams with something
-            
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                byte[] data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int index = BitConverter.ToInt32(data, 0);
-                LMOtsParameters type = LMOtsParameters.GetParametersForType(index);
-                byte[] C = new byte[type.GetN()];
-            
-                ((BinaryReader)src).Read(C, 0, C.Length);
-            
-                byte[] sig = new byte[type.GetP()*type.GetN()];
-                ((BinaryReader)src).Read(sig, 0, sig.Length);
-            
-            
-                return new LMOtsSignature(type, C, sig);
+                int index = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMOtsParameters parameter = LMOtsParameters.GetParametersByID(index);
+
+                byte[] C = BinaryReaders.ReadBytesFully(binaryReader, parameter.N);
+
+                byte[] sig = BinaryReaders.ReadBytesFully(binaryReader, parameter.P * parameter.N);
+
+                return new LMOtsSignature(parameter, C, sig);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.4 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream((byte[])src, false));
+                    input = new BinaryReader(new MemoryStream(bytes, false));
                     return GetInstance(input);
                 }
                 finally
@@ -56,65 +50,47 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     if (input != null) input.Close();
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
             throw new Exception ($"cannot parse {src}");
         }
-        public LMOtsParameters GetParamType()
-        {
-            return ParamType;
-        }
 
-        public byte[] GetC()
-        {
-            return C;
-        }
+        public LMOtsParameters ParamType => m_paramType;
 
-        public byte[] GetY()
-        {
-            return y;
-        }
+        // FIXME
+        public byte[] C => m_C;
+
+        // FIXME
+        public byte[] Y => m_y;
 
-        public override bool Equals(object o)
+        public override bool Equals(object obj)
         {
-            if (this == o)
-            {
+            if (this == obj)
                 return true;
-            }
-            if (o == null || GetType() != o.GetType())
-            {
+            if (!(obj is LMOtsSignature that))
                 return false;
-            }
-
-            LMOtsSignature that = (LMOtsSignature)o;
 
-            if (ParamType != null ? !ParamType.Equals(that.ParamType) : that.ParamType != null)
-            {
-                return false;
-            }
-            if (!Arrays.AreEqual(C, that.C))
-            {
-                return false;
-            }
-            return Arrays.AreEqual(y, that.y);
+            return Objects.Equals(m_paramType, that.m_paramType)
+                && Arrays.AreEqual(m_C, that.m_C)
+                && Arrays.AreEqual(m_y, that.m_y);
         }
 
         public override int GetHashCode()
         {
-            int result = ParamType != null ? ParamType.GetHashCode() : 0;
-            result = 31 * result + Arrays.GetHashCode(C);
-            result = 31 * result + Arrays.GetHashCode(y);
+            int result = Objects.GetHashCode(m_paramType);
+            result = 31 * result + Arrays.GetHashCode(m_C);
+            result = 31 * result + Arrays.GetHashCode(m_y);
             return result;
         }
 
         public byte[] GetEncoded()
         {
             return Composer.Compose()
-                .U32Str(ParamType.GetType())
-                .Bytes(C)
-                .Bytes(y)
+                .U32Str(m_paramType.ID)
+                .Bytes(m_C)
+                .Bytes(m_y)
                 .Build();
         }
     }
diff --git a/crypto/src/pqc/crypto/lms/LMS.cs b/crypto/src/pqc/crypto/lms/LMS.cs
index e6f7f4dda..6174d3889 100644
--- a/crypto/src/pqc/crypto/lms/LMS.cs
+++ b/crypto/src/pqc/crypto/lms/LMS.cs
@@ -1,16 +1,16 @@
-
 using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMS
+    public static class Lms
     {
         internal static ushort D_LEAF = 0x8282;
         internal static ushort D_INTR = 0x8383;
 
-        public static LMSPrivateKeyParameters GenerateKeys(LMSigParameters parameterSet,
+        public static LmsPrivateKeyParameters GenerateKeys(LMSigParameters parameterSet,
             LMOtsParameters lmOtsParameters, int q, byte[] I, byte[] rootSeed)
         {
             //
@@ -25,17 +25,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
 
             // Step 2
-            if (rootSeed == null || rootSeed.Length < parameterSet.GetM())
-            {
-                throw new ArgumentException($"root seed is less than {parameterSet.GetM()}");
-            }
+            if (rootSeed == null || rootSeed.Length < parameterSet.M)
+                throw new ArgumentException($"root seed is less than {parameterSet.M}");
 
-            int twoToH = 1 << parameterSet.GetH();
+            int twoToH = 1 << parameterSet.H;
 
-            return new LMSPrivateKeyParameters(parameterSet, lmOtsParameters, q, I, twoToH, rootSeed);
+            return new LmsPrivateKeyParameters(parameterSet, lmOtsParameters, q, I, twoToH, rootSeed);
         }
 
-        public static LMSSignature GenerateSign(LMSPrivateKeyParameters privateKey, byte[] message)
+        public static LmsSignature GenerateSign(LmsPrivateKeyParameters privateKey, byte[] message)
         {
             //
             // Get T from the public key.
@@ -44,14 +42,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             // byte[][] T = new byte[privateKey.getMaxQ()][];
 
             // Step 2
-            LMSContext context = privateKey.GenerateLmsContext();
+            LmsContext context = privateKey.GenerateLmsContext();
 
             context.BlockUpdate(message, 0, message.Length);
 
             return GenerateSign(context);
         }
 
-        public static LMSSignature GenerateSign(LMSContext context)
+        public static LmsSignature GenerateSign(LmsContext context)
         {
             //
             // Get T from the public key.
@@ -61,49 +59,48 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
             // Step 1.
             LMOtsSignature ots_signature =
-                LM_OTS.lm_ots_generate_signature(context.GetPrivateKey(), context.GetQ(), context.C);
+                LMOts.LMOtsGenerateSignature(context.PrivateKey, context.GetQ(), context.C);
 
-            return new LMSSignature(context.GetPrivateKey().GetQ(), ots_signature, context.GetSigParams(),
-                context.GetPath());
+            return new LmsSignature(context.PrivateKey.Q, ots_signature, context.SigParams, context.Path);
         }
 
-        public static bool VerifySignature(LMSPublicKeyParameters publicKey, LMSSignature S, byte[] message)
+        public static bool VerifySignature(LmsPublicKeyParameters publicKey, LmsSignature S, byte[] message)
         {
-            LMSContext context = publicKey.GenerateOtsContext(S);
+            LmsContext context = publicKey.GenerateOtsContext(S);
 
-            LmsUtils.ByteArray(message, context);
+            LmsUtilities.ByteArray(message, context);
 
             return VerifySignature(publicKey, context);
         }
 
-        public static bool VerifySignature(LMSPublicKeyParameters publicKey, byte[] S, byte[] message)
+        public static bool VerifySignature(LmsPublicKeyParameters publicKey, byte[] S, byte[] message)
         {
-            LMSContext context = publicKey.GenerateLmsContext(S);
+            LmsContext context = publicKey.GenerateLmsContext(S);
 
-            LmsUtils.ByteArray(message, context);
+            LmsUtilities.ByteArray(message, context);
 
             return VerifySignature(publicKey, context);
         }
 
-        public static bool VerifySignature(LMSPublicKeyParameters publicKey, LMSContext context)
+        public static bool 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);
+            LmsSignature S = (LmsSignature)context.Signature;
+            LMSigParameters lmsParameter = S.SigParameters;
+            int h = lmsParameter.H;
+            byte[][] path = S.Y;
+            byte[] Kc = LMOts.LMOtsValidateSignatureCalculate(context);
             // Step 4
             // node_num = 2^h + q
-            int node_num = (1 << h) + S.GetQ();
+            int node_num = (1 << h) + S.Q;
 
             // tmp = H(I || u32str(node_num) || u16str(D_LEAF) || Kc)
             byte[] I = publicKey.GetI();
-            IDigest H = DigestUtilities.GetDigest(lmsParameter.GetDigestOid());
+            IDigest H = DigestUtilities.GetDigest(lmsParameter.DigestOid);
             byte[] tmp = new byte[H.GetDigestSize()];
 
             H.BlockUpdate(I, 0, I.Length);
-            LmsUtils.U32Str(node_num, H);
-            LmsUtils.U16Str(D_LEAF, H);
+            LmsUtilities.U32Str(node_num, H);
+            LmsUtilities.U16Str((short)D_LEAF, H);
             H.BlockUpdate(Kc, 0, Kc.Length);
             H.DoFinal(tmp, 0);
 
@@ -115,8 +112,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 {
                     // is odd
                     H.BlockUpdate(I, 0, I.Length);
-                    LmsUtils.U32Str(node_num / 2, H);
-                    LmsUtils.U16Str(D_INTR, H);
+                    LmsUtilities.U32Str(node_num / 2, H);
+                    LmsUtilities.U16Str((short)D_INTR, H);
                     H.BlockUpdate(path[i], 0, path[i].Length);
                     H.BlockUpdate(tmp, 0, tmp.Length);
                     H.DoFinal(tmp, 0);
@@ -124,8 +121,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 else
                 {
                     H.BlockUpdate(I, 0, I.Length);
-                    LmsUtils.U32Str(node_num / 2, H);
-                    LmsUtils.U16Str(D_INTR, H);
+                    LmsUtilities.U32Str(node_num / 2, H);
+                    LmsUtilities.U16Str((short)D_INTR, H);
                     H.BlockUpdate(tmp, 0, tmp.Length);
                     H.BlockUpdate(path[i], 0, path[i].Length);
                     H.DoFinal(tmp, 0);
diff --git a/crypto/src/pqc/crypto/lms/LMSContext.cs b/crypto/src/pqc/crypto/lms/LMSContext.cs
index 35c33093b..6fcbd9413 100644
--- a/crypto/src/pqc/crypto/lms/LMSContext.cs
+++ b/crypto/src/pqc/crypto/lms/LMSContext.cs
@@ -1,128 +1,109 @@
 using System;
-using Org.BouncyCastle.Crypto;
 
-using static Org.BouncyCastle.Pqc.Crypto.Lms.LM_OTS;
+using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSContext
+    public sealed class LmsContext
         : IDigest
     {
-        private byte[] c;
-        private LMOtsPrivateKey key;
-        private LMSigParameters sigParams;
-        private byte[][] path;
-        private LMOtsPublicKey publicKey;
-        private Object signature;
+        private readonly byte[] m_c;
+        private readonly LMOtsPrivateKey m_privateKey;
+        private readonly LMSigParameters m_sigParams;
+        private readonly byte[][] m_path;
 
-        private LMSSignedPubKey[] signedPubKeys;
-        private volatile IDigest digest;
+        private readonly LMOtsPublicKey m_publicKey;
+        private readonly object m_signature;
+        private LmsSignedPubKey[] m_signedPubKeys;
+        private volatile IDigest m_digest;
 
-        public LMSContext(LMOtsPrivateKey key, LMSigParameters sigParams, IDigest digest, byte[] C, byte[][] path)
+        public LmsContext(LMOtsPrivateKey privateKey, LMSigParameters sigParams, IDigest 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;
+            m_privateKey = privateKey;
+            m_sigParams = sigParams;
+            m_digest = digest;
+            m_c = C;
+            m_path = path;
+            m_publicKey = null;
+            m_signature = null;
         }
 
-        public LMSContext(LMOtsPublicKey publicKey, Object signature, IDigest digest)
+        public LmsContext(LMOtsPublicKey publicKey, object signature, IDigest digest)
         {
-            this.publicKey = publicKey;
-            this.signature = signature;
-            this.digest = digest;
-            this.c = null;
-            this.key = null;
-            this.sigParams = null;
-            this.path = null;
+            m_publicKey = publicKey;
+            m_signature = signature;
+            m_digest = digest;
+            m_c = null;
+            m_privateKey = null;
+            m_sigParams = null;
+            m_path = null;
         }
 
-        public byte[] C => c;
+        public byte[] C => m_c;
 
         public byte[] GetQ()
         {
-            byte[] Q = new byte[MAX_HASH + 2];
-
-            digest.DoFinal(Q, 0);
-            
-            digest = null;
-
+            byte[] Q = new byte[LMOts.MAX_HASH + 2];
+            m_digest.DoFinal(Q, 0);
+            m_digest = null;
             return Q;
         }
 
-        internal byte[][] GetPath()
-        {
-            return path;
-        }
+        internal byte[][] Path => m_path;
 
-        internal LMOtsPrivateKey GetPrivateKey()
-        {
-            return key;
-        }
+        internal LMOtsPrivateKey PrivateKey => m_privateKey;
 
-        public LMOtsPublicKey GetPublicKey()
-        {
-            return publicKey;
-        }
+        public LMOtsPublicKey PublicKey => m_publicKey;
 
-        internal LMSigParameters GetSigParams()
-        {
-            return sigParams;
-        }
+        internal LMSigParameters SigParams => m_sigParams;
 
-        public Object GetSignature()
-        {
-            return signature;
-        }
+        public object Signature => m_signature;
 
-        internal LMSSignedPubKey[] GetSignedPubKeys()
-        {
-            return signedPubKeys;
-        }
+        internal LmsSignedPubKey[] SignedPubKeys => m_signedPubKeys;
 
-        internal LMSContext WithSignedPublicKeys(LMSSignedPubKey[] signedPubKeys)
+        internal LmsContext WithSignedPublicKeys(LmsSignedPubKey[] signedPubKeys)
         {
-            this.signedPubKeys = signedPubKeys;
-
+            m_signedPubKeys = signedPubKeys;
             return this;
         }
 
-        public string AlgorithmName
-        {
-            get => digest.AlgorithmName;
-        }
+        public string AlgorithmName => m_digest.AlgorithmName;
 
-        public int GetDigestSize()
-        {
-            return digest.GetDigestSize();
-        }
+        public int GetDigestSize() => m_digest.GetDigestSize();
 
-        public int GetByteLength()
-        {
-            return digest.GetByteLength();
-        }
+        public int GetByteLength() => m_digest.GetByteLength();
 
         public void Update(byte input)
         {
-            digest.Update(input);
+            m_digest.Update(input);
         }
 
         public void BlockUpdate(byte[] input, int inOff, int len)
         {
-            digest.BlockUpdate(input, inOff, len);
+            m_digest.BlockUpdate(input, inOff, len);
         }
 
         public int DoFinal(byte[] output, int outOff)
         {
-            return digest.DoFinal(output, outOff);
+            return m_digest.DoFinal(output, outOff);
         }
 
         public void Reset()
         {
-            digest.Reset();
+            m_digest.Reset();
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            m_digest.BlockUpdate(input);
+        }
+
+        public int DoFinal(Span<byte> output)
+        {
+            return m_digest.DoFinal(output);
         }
+#endif
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSException.cs b/crypto/src/pqc/crypto/lms/LMSException.cs
index c9c286b7f..694a370ed 100644
--- a/crypto/src/pqc/crypto/lms/LMSException.cs
+++ b/crypto/src/pqc/crypto/lms/LMSException.cs
@@ -4,25 +4,25 @@ using System.Runtime.Serialization;
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
     [Serializable]
-    public class LMSException
+    public class LmsException
         : Exception
     {
-		public LMSException()
+		public LmsException()
 			: base()
 		{
 		}
 
-		public LMSException(string message)
+		public LmsException(string message)
 			: base(message)
 		{
 		}
 
-		public LMSException(string message, Exception innerException)
+		public LmsException(string message, Exception innerException)
 			: base(message, innerException)
 		{
 		}
 
-		protected LMSException(SerializationInfo info, StreamingContext context)
+		protected LmsException(SerializationInfo info, StreamingContext context)
 			: base(info, context)
 		{
 		}
diff --git a/crypto/src/pqc/crypto/lms/LMSKeyGenerationParameters.cs b/crypto/src/pqc/crypto/lms/LMSKeyGenerationParameters.cs
index 837b8783a..ed2cb8957 100644
--- a/crypto/src/pqc/crypto/lms/LMSKeyGenerationParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMSKeyGenerationParameters.cs
@@ -3,11 +3,10 @@ using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-
-    public class LMSKeyGenerationParameters
+    public class LmsKeyGenerationParameters
         : KeyGenerationParameters
     {
-        private LMSParameters lmsParameters;
+        private readonly LmsParameters m_lmsParameters;
 
         /**
          * Base constructor - parameters and a source of randomness.
@@ -15,16 +14,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
          * @param lmsParameters LMS parameter set to use.
          * @param random   the random byte source.
          */
-        public LMSKeyGenerationParameters(LMSParameters lmsParameters, SecureRandom random)
-            : base(random, LmsUtils.CalculateStrength(lmsParameters)) // TODO: need something for "strength"
+        public LmsKeyGenerationParameters(LmsParameters lmsParameters, SecureRandom random)
+            : base(random, LmsUtilities.CalculateStrength(lmsParameters)) // TODO: need something for "strength"
         {
-            this.lmsParameters = lmsParameters;
+            m_lmsParameters = lmsParameters;
         }
 
-        public LMSParameters GetParameters()
-        {
-            return lmsParameters;
-        }
+        public LmsParameters LmsParameters => m_lmsParameters;
     }
 }
-
diff --git a/crypto/src/pqc/crypto/lms/LMSKeyPairGenerator.cs b/crypto/src/pqc/crypto/lms/LMSKeyPairGenerator.cs
index 3213c8ab1..e1afb00d9 100644
--- a/crypto/src/pqc/crypto/lms/LMSKeyPairGenerator.cs
+++ b/crypto/src/pqc/crypto/lms/LMSKeyPairGenerator.cs
@@ -3,19 +3,19 @@ using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSKeyPairGenerator
+    public sealed class LmsKeyPairGenerator
         : IAsymmetricCipherKeyPairGenerator
     {
-        private LMSKeyGenerationParameters param;
+        private LmsKeyGenerationParameters m_parameters;
 
-        public void Init(KeyGenerationParameters param)
+        public void Init(KeyGenerationParameters parameters)
         {
-            this.param = (LMSKeyGenerationParameters) param;
+            m_parameters = (LmsKeyGenerationParameters)parameters;
         }
 
         public AsymmetricCipherKeyPair GenerateKeyPair()
         {
-            SecureRandom source = param.Random;
+            SecureRandom source = m_parameters.Random;
 
             byte[] I = new byte[16];
             source.NextBytes(I);
@@ -23,11 +23,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             byte[] rootSecret = new byte[32];
             source.NextBytes(rootSecret);
 
-            LMSPrivateKeyParameters privKey = LMS.GenerateKeys(param.GetParameters().GetLmSigParam(),
-                param.GetParameters().GetLmotsParam(), 0, I, rootSecret);
+            LmsPrivateKeyParameters privKey = Lms.GenerateKeys(m_parameters.LmsParameters.LMSigParameters,
+                m_parameters.LmsParameters.LMOtsParameters, 0, I, rootSecret);
 
             return new AsymmetricCipherKeyPair(privKey.GetPublicKey(), privKey);
         }
     }
-    
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSKeyParameters.cs b/crypto/src/pqc/crypto/lms/LMSKeyParameters.cs
index 07123be7c..aaddfb823 100644
--- a/crypto/src/pqc/crypto/lms/LMSKeyParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMSKeyParameters.cs
@@ -1,18 +1,16 @@
-
-
-using System;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public abstract class LMSKeyParameters
+    public abstract class LmsKeyParameters
         : AsymmetricKeyParameter, IEncodable
     {
-        protected LMSKeyParameters(bool isPrivateKey)
-            : base(isPrivateKey) { }
+        internal LmsKeyParameters(bool isPrivateKey)
+            : base(isPrivateKey)
+        {
+        }
 
         public abstract byte[] GetEncoded();
     }
-    
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSParameters.cs b/crypto/src/pqc/crypto/lms/LMSParameters.cs
index 0f68b968f..49ce2f71a 100644
--- a/crypto/src/pqc/crypto/lms/LMSParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMSParameters.cs
@@ -1,24 +1,18 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSParameters
+    public sealed class LmsParameters
     {
-        private LMSigParameters lmSigParam;
-        private LMOtsParameters lmOTSParam;
+        private readonly LMSigParameters m_lmSigParameters;
+        private readonly LMOtsParameters m_lmOtsParameters;
 
-        public LMSParameters(LMSigParameters lmSigParam, LMOtsParameters lmOTSParam)
+        public LmsParameters(LMSigParameters lmSigParameters, LMOtsParameters lmOtsParameters)
         {
-            this.lmSigParam = lmSigParam;
-            this.lmOTSParam = lmOTSParam;
+            m_lmSigParameters = lmSigParameters;
+            m_lmOtsParameters = lmOtsParameters;
         }
 
-        public LMSigParameters GetLmSigParam()
-        {
-            return lmSigParam;
-        }
+        public LMSigParameters LMSigParameters => m_lmSigParameters;
 
-        public LMOtsParameters GetLmotsParam()
-        {
-            return lmOTSParam;
-        }
+        public LMOtsParameters LMOtsParameters => m_lmOtsParameters;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSPrivateKeyParameters.cs b/crypto/src/pqc/crypto/lms/LMSPrivateKeyParameters.cs
index fe3f9899d..278cbb04b 100644
--- a/crypto/src/pqc/crypto/lms/LMSPrivateKeyParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMSPrivateKeyParameters.cs
@@ -1,23 +1,21 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+
 using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Pqc.Crypto.Lms;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
-using static Org.BouncyCastle.Pqc.Crypto.Lms.LMS;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSPrivateKeyParameters
-        : LMSKeyParameters, ILMSContextBasedSigner
+    public sealed class LmsPrivateKeyParameters
+        : LmsKeyParameters, ILmsContextBasedSigner
     {
         private static CacheKey T1 = new CacheKey(1);
         private static CacheKey[] internedKeys = new CacheKey[129];
 
-        static LMSPrivateKeyParameters()
+        static LmsPrivateKeyParameters()
         {
             internedKeys[1] = T1;
             for (int i = 2; i < internedKeys.Length; i++)
@@ -36,15 +34,22 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         private IDigest tDigest;
 
         private int q;
+        private readonly bool m_isPlaceholder;
 
         //
         // These are not final because they can be generated.
         // They also do not need to be persisted.
         //
-        private LMSPublicKeyParameters publicKey;
+        private LmsPublicKeyParameters publicKey;
 
+        public LmsPrivateKeyParameters(LMSigParameters lmsParameter, LMOtsParameters otsParameters, int q, byte[] I,
+            int maxQ, byte[] masterSecret)
+            : this(lmsParameter, otsParameters, q, I, maxQ, masterSecret, false)
+        {
+        }
 
-        public LMSPrivateKeyParameters(LMSigParameters lmsParameter, LMOtsParameters otsParameters, int q, byte[] I, int maxQ, byte[] masterSecret)
+        internal LmsPrivateKeyParameters(LMSigParameters lmsParameter, LMOtsParameters otsParameters, int q, byte[] I,
+            int maxQ, byte[] masterSecret, bool isPlaceholder)
             : base(true)
         {
             this.parameters = lmsParameter;
@@ -53,12 +58,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             this.I = Arrays.Clone(I);
             this.maxQ = maxQ;
             this.masterSecret = Arrays.Clone(masterSecret);
-            this.maxCacheR = 1 << (parameters.GetH() + 1);
+            this.maxCacheR = 1 << (parameters.H + 1);
             this.tCache = new Dictionary<CacheKey, byte[]>();
-            this.tDigest = DigestUtilities.GetDigest(lmsParameter.GetDigestOid());
+            this.tDigest = DigestUtilities.GetDigest(lmsParameter.DigestOid);
+            this.m_isPlaceholder = isPlaceholder;
         }
 
-        private LMSPrivateKeyParameters(LMSPrivateKeyParameters parent, int q, int maxQ)
+        private LmsPrivateKeyParameters(LmsPrivateKeyParameters parent, int q, int maxQ)
             : base(true)
         {
             this.parameters = parent.parameters;
@@ -67,98 +73,59 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             this.I = parent.I;
             this.maxQ = maxQ;
             this.masterSecret = parent.masterSecret;
-            this.maxCacheR = 1 << parameters.GetH();
+            this.maxCacheR = 1 << parameters.H;
             this.tCache = parent.tCache;
-            this.tDigest = DigestUtilities.GetDigest(parameters.GetDigestOid());
+            this.tDigest = DigestUtilities.GetDigest(parameters.DigestOid);
             this.publicKey = parent.publicKey;
         }
 
-        public static LMSPrivateKeyParameters GetInstance(byte[] privEnc, byte[] pubEnc)
+        public static LmsPrivateKeyParameters GetInstance(byte[] privEnc, byte[] pubEnc)
         {
-            LMSPrivateKeyParameters pKey = GetInstance(privEnc);
+            LmsPrivateKeyParameters pKey = GetInstance(privEnc);
         
-            pKey.publicKey = LMSPublicKeyParameters.GetInstance(pubEnc);
+            pKey.publicKey = LmsPublicKeyParameters.GetInstance(pubEnc);
 
             return pKey;
         }
 
-        public static LMSPrivateKeyParameters GetInstance(Object src)
+        public static LmsPrivateKeyParameters GetInstance(object src)
         {
-            if (src is LMSPrivateKeyParameters)
+            if (src is LmsPrivateKeyParameters lmsPrivateKeyParameters)
             {
-                return (LMSPrivateKeyParameters)src;
+                return lmsPrivateKeyParameters;
             }
-            //TODO
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                BinaryReader dIn = (BinaryReader)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.ReadInt32() != 0) // todo check endienness
-                {
-                    throw new Exception("expected version 0 lms private key");
-                }
-                
-                // todo check endienness
-                byte[] data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int paramType = BitConverter.ToInt32(data, 0);
-                LMSigParameters parameter = LMSigParameters.GetParametersForType(paramType);
-                
-                data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                paramType = BitConverter.ToInt32(data, 0);
-                
-                LMOtsParameters otsParameter = LMOtsParameters.GetParametersForType(paramType);
-                byte[] I = new byte[16];
-                dIn.Read(I, 0, I.Length);
-            
-                
-                data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int q =  BitConverter.ToInt32(data, 0);
-                
-                data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int maxQ = BitConverter.ToInt32(data, 0);
-                
-                data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int l = BitConverter.ToInt32(data, 0);
-                
-                
+                int version = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                if (version != 0)
+                    throw new Exception("unknown version for LMS private key");
+
+                int sigParamType = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMSigParameters sigParameter = LMSigParameters.GetParametersByID(sigParamType);
+
+                int otsParamType = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMOtsParameters otsParameter = LMOtsParameters.GetParametersByID(otsParamType);
+
+                byte[] I = BinaryReaders.ReadBytesFully(binaryReader, 16);
+
+                int q = BinaryReaders.ReadInt32BigEndian(binaryReader);
+
+                int maxQ = BinaryReaders.ReadInt32BigEndian(binaryReader);
+
+                int l = BinaryReaders.ReadInt32BigEndian(binaryReader);
                 if (l < 0)
-                {
                     throw new Exception("secret length less than zero");
-                }
-                if (l > dIn.BaseStream.Length)
-                {
-                    throw new IOException("secret length exceeded " + dIn.BaseStream.Length);
-                }
-                byte[] masterSecret = new byte[l];
-                dIn.Read(masterSecret, 0, masterSecret.Length);
-            
-                return new LMSPrivateKeyParameters(parameter, otsParameter, q, I, maxQ, masterSecret);
-            
+
+                byte[] masterSecret = BinaryReaders.ReadBytesFully(binaryReader, l);
+
+                return new LmsPrivateKeyParameters(sigParameter, otsParameter, q, I, maxQ, masterSecret);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.6 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream((byte[])src, false));
+                    input = new BinaryReader(new MemoryStream(bytes, false));
                     return GetInstance(input);
                 }
                 finally
@@ -169,9 +136,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     }
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
 
             throw new ArgumentException($"cannot parse {src}");
@@ -183,9 +150,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             lock (this)
             {
                 if (q >= maxQ)
-                {
                     throw new Exception("ots private keys expired");
-                }
+
                 return new LMOtsPrivateKey(otsParameters, I, q, masterSecret);
             }
         }
@@ -207,13 +173,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 q++;
         }
 
-        public LMSContext GenerateLmsContext()
+        public LmsContext GenerateLmsContext()
         {
             // Step 1.
             LMSigParameters lmsParameter = this.GetSigParameters();
 
             // Step 2
-            int h = lmsParameter.GetH();
+            int h = lmsParameter.H;
             int q = GetIndex();
             LMOtsPrivateKey otsPk = GetNextOtsPrivateKey();
 
@@ -232,11 +198,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return otsPk.GetSignatureContext(this.GetSigParameters(), path);
         }
 
-        public byte[] GenerateSignature(LMSContext context)
+        public byte[] GenerateSignature(LmsContext context)
         {
             try
             {
-                return GenerateSign(context).GetEncoded();
+                return Lms.GenerateSign(context).GetEncoded();
             }
             catch (IOException e)
             {
@@ -244,21 +210,22 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             }
         }
 
-        LMOtsPrivateKey GetNextOtsPrivateKey()
+        internal LMOtsPrivateKey GetNextOtsPrivateKey()
         {
+            if (m_isPlaceholder)
+                throw new Exception("placeholder only");
+
             lock (this)
             {
                 if (q >= maxQ)
-                {
                     throw new Exception("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>
@@ -268,15 +235,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
          * @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)
+        public LmsPrivateKeyParameters ExtractKeyShard(int usageCount)
         {
             lock (this)
             {
                 if (q + usageCount >= maxQ)
-                {
                     throw new ArgumentException("usageCount exceeds usages remaining");
-                }
-                LMSPrivateKeyParameters keyParameters = new LMSPrivateKeyParameters(this, q, q + usageCount);
+
+                LmsPrivateKeyParameters keyParameters = new LmsPrivateKeyParameters(this, q, q + usageCount);
                 q += usageCount;
 
                 return keyParameters;
@@ -308,19 +274,22 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return maxQ - q;
         }
 
-        public LMSPublicKeyParameters GetPublicKey()
+        public LmsPublicKeyParameters GetPublicKey()
         {
+            if (m_isPlaceholder)
+                throw new Exception("placeholder only");
+
             lock (this)
             {
                 if (publicKey == null)
                 {
-                    publicKey = new LMSPublicKeyParameters(parameters, otsParameters, this.FindT(T1), I);
+                    publicKey = new LmsPublicKeyParameters(parameters, otsParameters, this.FindT(T1), I);
                 }
                 return publicKey;
             }
         }
 
-        byte[] FindT(int r)
+        internal byte[] FindT(int r)
         {
             if (r < maxCacheR)
             {
@@ -334,20 +303,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             lock (tCache)
             {
-                byte[] t;
-                if (!tCache.TryGetValue(key, out t))
-                {
-                    t = CalcT(key.index);
-                    tCache[key] = t;
-                }
+                if (tCache.TryGetValue(key, out byte[] t))
+                    return t;
 
-                return t;
+                return tCache[key] = CalcT(key.index);
             }
         }
 
         private byte[] CalcT(int r)
         {
-            int h = this.GetSigParameters().GetH();
+            int h = this.GetSigParameters().H;
 
             int twoToh = 1 << h;
 
@@ -357,16 +322,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
             if (r >= twoToh)
             {
-                LmsUtils.ByteArray(this.GetI(), tDigest);
-                LmsUtils.U32Str(r, tDigest);
-                LmsUtils.U16Str(D_LEAF, tDigest);
+                LmsUtilities.ByteArray(this.GetI(), tDigest);
+                LmsUtilities.U32Str(r, tDigest);
+                LmsUtilities.U16Str((short)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());
+                byte[] K = LMOts.LmsOtsGeneratePublicKey(this.GetOtsParameters(), this.GetI(), (r - twoToh),
+                    this.GetMasterSecret());
 
-                LmsUtils.ByteArray(K, tDigest);
+                LmsUtilities.ByteArray(K, tDigest);
                 T = new byte[tDigest.GetDigestSize()];
                 tDigest.DoFinal(T, 0);
                 return T;
@@ -375,11 +341,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             byte[] t2r = FindT(2 * r);
             byte[] t2rPlus1 = FindT((2 * r + 1));
 
-            LmsUtils.ByteArray(this.GetI(), tDigest);
-            LmsUtils.U32Str(r, tDigest);
-            LmsUtils.U16Str(D_INTR, tDigest);
-            LmsUtils.ByteArray(t2r, tDigest);
-            LmsUtils.ByteArray(t2rPlus1, tDigest);
+            LmsUtilities.ByteArray(this.GetI(), tDigest);
+            LmsUtilities.U32Str(r, tDigest);
+            LmsUtilities.U16Str((short)Lms.D_INTR, tDigest);
+            LmsUtilities.ByteArray(t2r, tDigest);
+            LmsUtilities.ByteArray(t2rPlus1, tDigest);
             T = new byte[tDigest.GetDigestSize()];
             tDigest.DoFinal(T, 0);
 
@@ -397,7 +363,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 return false;
             }
 
-            LMSPrivateKeyParameters that = (LMSPrivateKeyParameters)o;
+            LmsPrivateKeyParameters that = (LmsPrivateKeyParameters)o;
 
             if (q != that.q)
             {
@@ -467,8 +433,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
             return Composer.Compose()
                 .U32Str(0) // version
-                .U32Str(parameters.GetType()) // type
-                .U32Str(otsParameters.GetType()) // ots type
+                .U32Str(parameters.ID) // type
+                .U32Str(otsParameters.ID) // ots type
                 .Bytes(I) // I at 16 bytes
                 .U32Str(q) // q
                 .U32Str(maxQ) // maximum q
diff --git a/crypto/src/pqc/crypto/lms/LMSPublicKeyParameters.cs b/crypto/src/pqc/crypto/lms/LMSPublicKeyParameters.cs
index d58d3d924..f8d0970af 100644
--- a/crypto/src/pqc/crypto/lms/LMSPublicKeyParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMSPublicKeyParameters.cs
@@ -1,22 +1,20 @@
 using System;
 using System.IO;
-using Org.BouncyCastle.Pqc.Crypto.Lms;
+
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
-using static Org.BouncyCastle.Pqc.Crypto.Lms.LMS;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSPublicKeyParameters
-        : LMSKeyParameters, ILMSContextBasedVerifier
+    public sealed class LmsPublicKeyParameters
+        : LmsKeyParameters, ILmsContextBasedVerifier
     {
         private LMSigParameters parameterSet;
         private LMOtsParameters lmOtsType;
         private byte[] I;
         private byte[] T1;
 
-        public LMSPublicKeyParameters(LMSigParameters parameterSet, LMOtsParameters lmOtsType, byte[] T1, byte[] I)
+        public LmsPublicKeyParameters(LMSigParameters parameterSet, LMOtsParameters lmOtsType, byte[] T1, byte[] I)
             : base(false)
         {
             this.parameterSet = parameterSet;
@@ -25,39 +23,32 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             this.T1 = Arrays.Clone(T1);
         }
 
-        public static LMSPublicKeyParameters GetInstance(Object src)
+        public static LmsPublicKeyParameters GetInstance(object src)
         {
-            if (src is LMSPublicKeyParameters)
+            if (src is LmsPublicKeyParameters lmsPublicKeyParameters)
             {
-                return (LMSPublicKeyParameters)src;
+                return lmsPublicKeyParameters;
             }
-            // todo
-             else if (src is BinaryReader)
-             {
-                 byte[] data = ((BinaryReader) src).ReadBytes(4);
-                 Array.Reverse(data);
-                 int pubType = BitConverter.ToInt32(data, 0);
-                 LMSigParameters lmsParameter = LMSigParameters.GetParametersForType(pubType);
-                 
-                 data = ((BinaryReader) src).ReadBytes(4);
-                 Array.Reverse(data);
-                 int index = BitConverter.ToInt32(data, 0);
-                 LMOtsParameters ostTypeCode = LMOtsParameters.GetParametersForType(index);
-            
-                 byte[] I = new byte[16];
-                 ((BinaryReader)src).Read(I, 0, I.Length);//change to readbytes?
-            
-                 byte[] T1 = new byte[lmsParameter.GetM()];
-                 ((BinaryReader)src).Read(T1, 0, T1.Length);
-                 return new LMSPublicKeyParameters(lmsParameter, ostTypeCode, T1, I);
-             }
-             else if (src is byte[])
+            else if (src is BinaryReader binaryReader)
+            {
+                int pubType = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMSigParameters lmsParameter = LMSigParameters.GetParametersByID(pubType);
+
+                int index = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMOtsParameters ostTypeCode = LMOtsParameters.GetParametersByID(index);
+
+                byte[] I = BinaryReaders.ReadBytesFully(binaryReader, 16);
+
+                byte[] T1 = BinaryReaders.ReadBytesFully(binaryReader, lmsParameter.M);
+
+                return new LmsPublicKeyParameters(lmsParameter, ostTypeCode, T1, I);
+            }
+            else if (src is byte[] bytes)
              {
-            
                  BinaryReader input = null;
                  try // 1.5 / 1.6 compatibility
                  {
-                     input = new BinaryReader(new MemoryStream((byte[])src, false));
+                     input = new BinaryReader(new MemoryStream(bytes, false));
                      return GetInstance(input);
                  }
                  finally
@@ -68,9 +59,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                      }
                  }
              }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
             throw new Exception ($"cannot parse {src}");
         }
@@ -90,9 +81,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return lmOtsType;
         }
 
-        public LMSParameters GetLmsParameters()
+        public LmsParameters GetLmsParameters()
         {
-            return new LMSParameters(this.GetSigParameters(), this.GetOtsParameters());
+            return new LmsParameters(this.GetSigParameters(), this.GetOtsParameters());
         }
 
         public byte[] GetT1()
@@ -126,7 +117,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 return false;
             }
 
-            LMSPublicKeyParameters publicKey = (LMSPublicKeyParameters)o;
+            LmsPublicKeyParameters publicKey = (LmsPublicKeyParameters)o;
 
             if (!parameterSet.Equals(publicKey.parameterSet))
             {
@@ -155,18 +146,18 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         internal byte[] ToByteArray()
         {
             return Composer.Compose()
-                .U32Str(parameterSet.GetType())
-                .U32Str(lmOtsType.GetType())
+                .U32Str(parameterSet.ID)
+                .U32Str(lmOtsType.ID)
                 .Bytes(I)
                 .Bytes(T1)
                 .Build();
         }
 
-        public LMSContext GenerateLmsContext(byte[] signature)
+        public LmsContext GenerateLmsContext(byte[] signature)
         {
             try
             {
-                return GenerateOtsContext(LMSSignature.GetInstance(signature));
+                return GenerateOtsContext(LmsSignature.GetInstance(signature));
             }
             catch (IOException e)
             {
@@ -174,21 +165,22 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             }
         }
 
-        internal LMSContext GenerateOtsContext(LMSSignature S)
+        internal LmsContext GenerateOtsContext(LmsSignature S)
         {
-            int ots_typecode = GetOtsParameters().GetType();
-            if (S.GetOtsSignature().GetParamType().GetType() != ots_typecode)
+            int ots_typecode = GetOtsParameters().ID;
+            if (S.OtsSignature.ParamType.ID != ots_typecode)
             {
                 throw new ArgumentException("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);
+            return new LMOtsPublicKey(LMOtsParameters.GetParametersByID(ots_typecode), I,  S.Q, null)
+                .CreateOtsContext(S);
         }
 
-        public bool Verify(LMSContext context)
+        public bool Verify(LmsContext context)
         {
-            return VerifySignature(this, context);
+            return Lms.VerifySignature(this, context);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSSignature.cs b/crypto/src/pqc/crypto/lms/LMSSignature.cs
index 8769160b4..a1ae475c1 100644
--- a/crypto/src/pqc/crypto/lms/LMSSignature.cs
+++ b/crypto/src/pqc/crypto/lms/LMSSignature.cs
@@ -6,7 +6,7 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSSignature
+    public class LmsSignature
         : IEncodable
     {
         private int q;
@@ -14,7 +14,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         private LMSigParameters parameter;
         private byte[][] y;
 
-        public LMSSignature(int q, LMOtsSignature otsSignature, LMSigParameters parameter, byte[][] y)
+        public LmsSignature(int q, LMOtsSignature otsSignature, LMSigParameters parameter, byte[][] y)
         {
             this.q = q;
             this.otsSignature = otsSignature;
@@ -22,40 +22,36 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             this.y = y;
         }
 
-        public static LMSSignature GetInstance(Object src)
+        public static LmsSignature GetInstance(object src)
         {
-            if (src is LMSSignature)
+            if (src is LmsSignature lmsSignature)
             {
-                return (LMSSignature)src;
+                return lmsSignature;
             }
-            else if (src is BinaryReader)
+            else if (src is BinaryReader binaryReader)
             {
-                byte[] data = ((BinaryReader)src).ReadBytes(4);
-                Array.Reverse(data);
-                int q = BitConverter.ToInt32(data, 0);
-                
+                int q = BinaryReaders.ReadInt32BigEndian(binaryReader);
+
                 LMOtsSignature otsSignature = LMOtsSignature.GetInstance(src);
 
-                data = ((BinaryReader) src).ReadBytes(4);
-                Array.Reverse(data);
-                int index = BitConverter.ToInt32(data, 0);
-                LMSigParameters type = LMSigParameters.GetParametersForType(index);
-            
-                byte[][] path = new byte[type.GetH()][];
+                int index = BinaryReaders.ReadInt32BigEndian(binaryReader);
+                LMSigParameters type = LMSigParameters.GetParametersByID(index);
+
+                byte[][] path = new byte[type.H][];
                 for (int h = 0; h < path.Length; h++)
                 {
-                    path[h] = new byte[type.GetM()];
-                    ((BinaryReader)src).Read(path[h], 0, path[h].Length);
+                    path[h] = new byte[type.M];
+                    binaryReader.Read(path[h], 0, path[h].Length);
                 }
-            
-                return new LMSSignature(q, otsSignature, type, path);
+
+                return new LmsSignature(q, otsSignature, type, path);
             }
-            else if (src is byte[])
+            else if (src is byte[] bytes)
             {
                 BinaryReader input = null;
                 try // 1.5 / 1.6 compatibility
                 {
-                    input = new BinaryReader(new MemoryStream( (byte[]) src, false));
+                    input = new BinaryReader(new MemoryStream(bytes, false));
                     return GetInstance(input);
                 }
                 finally
@@ -63,9 +59,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                     if (input != null) input.Close();// todo platform dispose
                 }
             }
-            else if (src is MemoryStream)
+            else if (src is MemoryStream memoryStream)
             {
-                return GetInstance(Streams.ReadAll((Stream)src));
+                return GetInstance(Streams.ReadAll(memoryStream));
             }
             throw new Exception ($"cannot parse {src}");
         }
@@ -81,7 +77,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 return false;
             }
 
-            LMSSignature that = (LMSSignature)o;
+            LmsSignature that = (LmsSignature)o;
 
             if (q != that.q)
             {
@@ -126,29 +122,18 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return Composer.Compose()
                 .U32Str(q)
                 .Bytes(otsSignature.GetEncoded())
-                .U32Str(parameter.GetType())
-                .Bytes(y)
+                .U32Str(parameter.ID)
+                .Bytes2(y)
                 .Build();
         }
 
-        public int GetQ()
-        {
-            return q;
-        }
+        public int Q => q;
 
-        public LMOtsSignature GetOtsSignature()
-        {
-            return otsSignature;
-        }
+        public LMOtsSignature OtsSignature => otsSignature;
 
-        public LMSigParameters GetParameter()
-        {
-            return parameter;
-        }
+        public LMSigParameters SigParameters => parameter;
 
-        public byte[][] GetY()
-        {
-            return y;
-        }
+        // FIXME
+        public byte[][] Y => y;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSSignedPubKey.cs b/crypto/src/pqc/crypto/lms/LMSSignedPubKey.cs
index 20d1bade5..5e07c9c93 100644
--- a/crypto/src/pqc/crypto/lms/LMSSignedPubKey.cs
+++ b/crypto/src/pqc/crypto/lms/LMSSignedPubKey.cs
@@ -1,27 +1,28 @@
 using System;
+
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSSignedPubKey
+    public class LmsSignedPubKey
         : IEncodable
     {
-        private LMSSignature signature;
-        private LMSPublicKeyParameters publicKey;
+        private LmsSignature signature;
+        private LmsPublicKeyParameters publicKey;
 
-        public LMSSignedPubKey(LMSSignature signature, LMSPublicKeyParameters publicKey)
+        public LmsSignedPubKey(LmsSignature signature, LmsPublicKeyParameters publicKey)
         {
             this.signature = signature;
             this.publicKey = publicKey;
         }
 
 
-        public LMSSignature GetSignature()
+        public LmsSignature GetSignature()
         {
             return signature;
         }
 
-        public LMSPublicKeyParameters GetPublicKey()
+        public LmsPublicKeyParameters GetPublicKey()
         {
             return publicKey;
         }
@@ -37,7 +38,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 return false;
             }
 
-            LMSSignedPubKey that = (LMSSignedPubKey)o;
+            LmsSignedPubKey that = (LmsSignedPubKey)o;
 
             if (signature != null ? !signature.Equals(that.signature) : that.signature != null)
             {
diff --git a/crypto/src/pqc/crypto/lms/LMSSigner.cs b/crypto/src/pqc/crypto/lms/LMSSigner.cs
index a5aebd312..edbfd7330 100644
--- a/crypto/src/pqc/crypto/lms/LMSSigner.cs
+++ b/crypto/src/pqc/crypto/lms/LMSSigner.cs
@@ -5,21 +5,21 @@ using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSSigner
+    public sealed class LmsSigner
         : IMessageSigner
     {
-        private LMSPrivateKeyParameters privKey;
-        private LMSPublicKeyParameters pubKey;
+        private LmsPrivateKeyParameters m_privateKey;
+        private LmsPublicKeyParameters m_publicKey;
 
         public void Init(bool forSigning, ICipherParameters param)
         {
             if (forSigning)
             {
-                privKey = (LMSPrivateKeyParameters)param;
+                m_privateKey = (LmsPrivateKeyParameters)param;
             }
             else
             {
-                pubKey = (LMSPublicKeyParameters)param;
+                m_publicKey = (LmsPublicKeyParameters)param;
             }
         }
 
@@ -27,7 +27,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             try
             {
-                return LMS.GenerateSign(privKey, message).GetEncoded();
+                return Lms.GenerateSign(m_privateKey, message).GetEncoded();
             }
             catch (IOException e)
             {
@@ -39,7 +39,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             try
             {
-                return LMS.VerifySignature(pubKey, LMSSignature.GetInstance(signature), message);
+                return Lms.VerifySignature(m_publicKey, LmsSignature.GetInstance(signature), message);
             }
             catch (IOException e)
             {
@@ -47,4 +47,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LMSigParameters.cs b/crypto/src/pqc/crypto/lms/LMSigParameters.cs
index 7b4053e69..1f2a90b2b 100644
--- a/crypto/src/pqc/crypto/lms/LMSigParameters.cs
+++ b/crypto/src/pqc/crypto/lms/LMSigParameters.cs
@@ -1,11 +1,13 @@
-using System;
 using System.Collections.Generic;
+
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LMSigParameters
+    public sealed class LMSigParameters
     {
         public static LMSigParameters lms_sha256_n32_h5 = new LMSigParameters(5, 32, 5, NistObjectIdentifiers.IdSha256);
         public static LMSigParameters lms_sha256_n32_h10 = new LMSigParameters(6, 32, 10, NistObjectIdentifiers.IdSha256);
@@ -13,52 +15,39 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         public static LMSigParameters lms_sha256_n32_h20 = new LMSigParameters(8, 32, 20, NistObjectIdentifiers.IdSha256);
         public static LMSigParameters lms_sha256_n32_h25 = new LMSigParameters(9, 32, 25, NistObjectIdentifiers.IdSha256);
 
-
-        private static Dictionary<int, LMSigParameters> paramBuilders = new Dictionary<int, LMSigParameters>
+        private static Dictionary<int, LMSigParameters> ParametersByID = new Dictionary<int, LMSigParameters>
         {
-            { lms_sha256_n32_h5.type,  lms_sha256_n32_h5  },
-            { lms_sha256_n32_h10.type, lms_sha256_n32_h10 },
-            { lms_sha256_n32_h15.type, lms_sha256_n32_h15 },
-            { lms_sha256_n32_h20.type, lms_sha256_n32_h20 },
-            { lms_sha256_n32_h25.type, lms_sha256_n32_h25 }
+            { lms_sha256_n32_h5.ID, lms_sha256_n32_h5 },
+            { lms_sha256_n32_h10.ID, lms_sha256_n32_h10 },
+            { lms_sha256_n32_h15.ID, lms_sha256_n32_h15 },
+            { lms_sha256_n32_h20.ID, lms_sha256_n32_h20 },
+            { lms_sha256_n32_h25.ID, lms_sha256_n32_h25 }
         };
 
-        private int type;
-        private int m;
-        private int h;
-        private DerObjectIdentifier digestOid;
-
-        protected LMSigParameters(int type, int m, int h, DerObjectIdentifier digestOid)
-        {
-            this.type = type;
-            this.m = m;
-            this.h = h;
-            this.digestOid = digestOid;
+        private readonly int m_id;
+        private readonly int m_m;
+        private readonly int m_h;
+        private readonly DerObjectIdentifier m_digestOid;
 
-        }
-        public int GetType()
+        internal LMSigParameters(int id, int m, int h, DerObjectIdentifier digestOid)
         {
-            return type;
+            m_id = id;
+            m_m = m;
+            m_h = h;
+            m_digestOid = digestOid;
         }
 
-        public int GetH()
-        {
-            return h;
-        }
+        public int ID => m_id;
 
-        public int GetM()
-        {
-            return m;
-        }
+        public int H => m_h;
 
-        public DerObjectIdentifier GetDigestOid()
-        {
-            return digestOid;
-        }
+        public int M => m_m;
+
+        public DerObjectIdentifier DigestOid => m_digestOid;
 
-        public static LMSigParameters GetParametersForType(int type)
+        public static LMSigParameters GetParametersByID(int id)
         {
-            return paramBuilders[type];
+            return CollectionUtilities.GetValueOrNull(ParametersByID, id);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LM_OTS.cs b/crypto/src/pqc/crypto/lms/LM_OTS.cs
index d5662bd9a..0aa5c580e 100644
--- a/crypto/src/pqc/crypto/lms/LM_OTS.cs
+++ b/crypto/src/pqc/crypto/lms/LM_OTS.cs
@@ -1,4 +1,5 @@
 using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Security;
@@ -6,7 +7,7 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LM_OTS
+    public static class LMOts
     {
         private static ushort D_PBLC = 0x8080;
         private static int ITER_K = 20;
@@ -18,7 +19,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         internal static int MAX_HASH = 32;
         internal static ushort D_MESG = 0x8181;
 
-
         public static int Coef(byte[] S, int i, int w)
         {
             int index = (i * w) / 8;
@@ -29,38 +29,35 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return (S[index] >> shift) & mask;
         }
 
-
         public static int Cksm(byte[] S, int sLen, LMOtsParameters parameters)
         {
             int sum = 0;
 
-            int w = parameters.GetW();
+            int w = parameters.W;
 
             // NB assumption about size of "w" not overflowing integer.
             int twoWpow = (1 << w) - 1;
 
-            for (int i = 0; i < (sLen * 8 / parameters.GetW()); i++)
+            for (int i = 0; i < (sLen * 8 / parameters.W); i++)
             {
-                sum = sum + twoWpow - Coef(S, i, parameters.GetW());
+                sum = sum + twoWpow - Coef(S, i, parameters.W);
             }
-            return sum << parameters.GetLs();
+            return sum << parameters.Ls;
         }
 
-
-        public static LMOtsPublicKey lms_ots_generatePublicKey(LMOtsPrivateKey privateKey)
+        public static LMOtsPublicKey LmsOtsGeneratePublicKey(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);
+            byte[] K = LmsOtsGeneratePublicKey(privateKey.Parameters, privateKey.I, privateKey.Q,
+                privateKey.MasterSecret);
+            return new LMOtsPublicKey(privateKey.Parameters, privateKey.I, privateKey.Q, K);
         }
 
-        internal static byte[] lms_ots_generatePublicKey(LMOtsParameters parameter, byte[] I, int q, byte[] masterSecret)
+        internal static byte[] LmsOtsGeneratePublicKey(LMOtsParameters parameter, byte[] I, int q, byte[] masterSecret)
         {
-
-
             //
             // Start hash that computes the final value.
             //
-            IDigest publicContext = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest publicContext = DigestUtilities.GetDigest(parameter.DigestOid);
             byte[] prehashPrefix = Composer.Compose()
                 .Bytes(I)
                 .U32Str(q)
@@ -69,7 +66,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 .Build();
             publicContext.BlockUpdate(prehashPrefix, 0, prehashPrefix.Length);
 
-            IDigest ctx = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest ctx = DigestUtilities.GetDigest(parameter.DigestOid);
 
             byte[] buf = Composer.Compose()
                 .Bytes(I)
@@ -77,19 +74,19 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
                 .PadUntil(0, 23 + ctx.GetDigestSize())
                 .Build();
 
+            SeedDerive derive = new SeedDerive(I, masterSecret, DigestUtilities.GetDigest(parameter.DigestOid))
+            {
+                Q = q,
+                J = 0,
+            };
 
-            SeedDerive derive = new SeedDerive(I, masterSecret, DigestUtilities.GetDigest(parameter.GetDigestOid()));
-            derive.SetQ(q);
-            derive.SetJ(0);
-
-            int p = parameter.GetP();
-            int n = parameter.GetN();
-            int twoToWminus1 = (1 << parameter.GetW()) - 1;
-
+            int p = parameter.P;
+            int n = parameter.N;
+            int twoToWminus1 = (1 << parameter.W) - 1;
 
             for (ushort i = 0; i < p; i++)
             {
-                derive.deriveSeed(buf, i < p - 1, ITER_PREV); // Private Key!
+                derive.DeriveSeed(i < p - 1, buf, ITER_PREV); // Private Key!
                 Pack.UInt16_To_BE(i, buf, ITER_K);
                 for (int j = 0; j < twoToWminus1; j++)
                 {
@@ -102,25 +99,23 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
             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, bool preHashed)
+        public static LMOtsSignature lm_ots_generate_signature(LMSigParameters sigParams, LMOtsPrivateKey privateKey,
+            byte[][] path, byte[] message, bool preHashed)
         {
             //
             // Add the randomizer.
             //
-
             byte[] C;
             byte[] Q = new byte[MAX_HASH + 2];
 
             if (!preHashed)
             {
-                LMSContext qCtx = privateKey.GetSignatureContext(sigParams, path);
+                LmsContext qCtx = privateKey.GetSignatureContext(sigParams, path);
 
-                LmsUtils.ByteArray(message, 0, message.Length, qCtx);
+                LmsUtilities.ByteArray(message, 0, message.Length, qCtx);
 
                 C = qCtx.C;
                 Q = qCtx.GetQ();
@@ -128,23 +123,23 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             else
             {
                 C = new byte[SEED_LEN];
-                Array.Copy(message, 0, Q, 0, privateKey.GetParameter().GetN());
+                Array.Copy(message, 0, Q, 0, privateKey.Parameters.N);
             }
 
-            return lm_ots_generate_signature(privateKey, Q, C);
+            return LMOtsGenerateSignature(privateKey, Q, C);
         }
 
-        public static LMOtsSignature lm_ots_generate_signature(LMOtsPrivateKey privateKey, byte[] Q, byte[] C)
+        public static LMOtsSignature LMOtsGenerateSignature(LMOtsPrivateKey privateKey, byte[] Q, byte[] C)
         {
-            LMOtsParameters parameter = privateKey.GetParameter();
+            LMOtsParameters parameter = privateKey.Parameters;
 
-            int n = parameter.GetN();
-            int p = parameter.GetP();
-            int w = parameter.GetW();
+            int n = parameter.N;
+            int p = parameter.P;
+            int w = parameter.W;
 
             byte[] sigComposer = new byte[p * n];
 
-            IDigest ctx = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest ctx = DigestUtilities.GetDigest(parameter.DigestOid);
 
             SeedDerive derive = privateKey.GetDerivationFunction();
 
@@ -153,16 +148,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             Q[n + 1] = (byte)cs;
 
             byte[] tmp = Composer.Compose()
-                .Bytes(privateKey.GetI())
-                .U32Str(privateKey.GetQ())
+                .Bytes(privateKey.I)
+                .U32Str(privateKey.Q)
                 .PadUntil(0, ITER_PREV + n)
                 .Build();
 
-            derive.SetJ(0);
+            derive.J = 0;
             for (ushort i = 0; i < p; i++)
             {
                 Pack.UInt16_To_BE(i, tmp, ITER_K);
-                derive.deriveSeed(tmp, i < p - 1, ITER_PREV);
+                derive.DeriveSeed(i < p - 1, tmp, ITER_PREV);
                 int a = Coef(Q, i, w);
                 for (int j = 0; j < a; j++)
                 {
@@ -176,55 +171,56 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return new LMOtsSignature(parameter, C, sigComposer);
         }
 
-        public static bool lm_ots_validate_signature(LMOtsPublicKey publicKey, LMOtsSignature signature, byte[] message, bool prehashed)
+        public static bool LMOtsValidateSignature(LMOtsPublicKey publicKey, LMOtsSignature signature, byte[] message,
+            bool prehashed)
         {
-            if (!signature.GetParamType().Equals(publicKey.GetParameter())) // todo check
-            {
-                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());
+            if (!signature.ParamType.Equals(publicKey.Parameters)) // todo check
+                throw new LmsException("public key and signature ots types do not match");
+
+            return Arrays.AreEqual(LMOtsValidateSignatureCalculate(publicKey, signature, message), publicKey.K);
         }
 
-        public static byte[] lm_ots_validate_signature_calculate(LMOtsPublicKey publicKey, LMOtsSignature signature, byte[] message)
+        public static byte[] LMOtsValidateSignatureCalculate(LMOtsPublicKey publicKey, LMOtsSignature signature, 
+            byte[] message)
         {
-            LMSContext ctx = publicKey.CreateOtsContext(signature);
+            LmsContext ctx = publicKey.CreateOtsContext(signature);
 
-            LmsUtils.ByteArray(message, ctx);
+            LmsUtilities.ByteArray(message, ctx);
 
-            return lm_ots_validate_signature_calculate(ctx);
+            return LMOtsValidateSignatureCalculate(ctx);
         }
 
-        public static byte[] lm_ots_validate_signature_calculate(LMSContext context)
+        public static byte[] LMOtsValidateSignatureCalculate(LmsContext context)
         {
-            LMOtsPublicKey publicKey = context.GetPublicKey();
-            LMOtsParameters parameter = publicKey.GetParameter();
-            Object sig = context.GetSignature();
+            LMOtsPublicKey publicKey = context.PublicKey;
+            LMOtsParameters parameter = publicKey.Parameters;
+            object sig = context.Signature;
             LMOtsSignature signature;
-            if (sig is LMSSignature)
+            if (sig is LmsSignature)
             {
-                signature = ((LMSSignature)sig).GetOtsSignature();
+                signature = ((LmsSignature)sig).OtsSignature;
             }
             else
             {
                 signature = (LMOtsSignature)sig;
             }
 
-            int n = parameter.GetN();
-            int w = parameter.GetW();
-            int p = parameter.GetP();
+            int n = parameter.N;
+            int w = parameter.W;
+            int p = parameter.P;
             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();
+            byte[] I = publicKey.I;
+            int q = publicKey.Q;
 
-            IDigest finalContext = DigestUtilities.GetDigest(parameter.GetDigestOid());
-            LmsUtils.ByteArray(I, finalContext);
-            LmsUtils.U32Str(q, finalContext);
-            LmsUtils.U16Str(D_PBLC, finalContext);
+            IDigest finalContext = DigestUtilities.GetDigest(parameter.DigestOid);
+            LmsUtilities.ByteArray(I, finalContext);
+            LmsUtilities.U32Str(q, finalContext);
+            LmsUtilities.U16Str((short)D_PBLC, finalContext);
 
             byte[] tmp = Composer.Compose()
                 .Bytes(I)
@@ -234,9 +230,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
 
             int max_digit = (1 << w) - 1;
 
-            byte[] y = signature.GetY();
+            byte[] y = signature.Y;
 
-            IDigest ctx = DigestUtilities.GetDigest(parameter.GetDigestOid());
+            IDigest ctx = DigestUtilities.GetDigest(parameter.DigestOid);
             for (ushort i = 0; i < p; i++)
             {
                 Pack.UInt16_To_BE(i, tmp, ITER_K);
@@ -259,4 +255,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             return K;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/LmsUtils.cs b/crypto/src/pqc/crypto/lms/LmsUtils.cs
index 04696f830..e99dfe585 100644
--- a/crypto/src/pqc/crypto/lms/LmsUtils.cs
+++ b/crypto/src/pqc/crypto/lms/LmsUtils.cs
@@ -1,9 +1,10 @@
 using System;
+
 using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class LmsUtils
+    public static class LmsUtilities
     {
         public static void U32Str(int n, IDigest d)
         {
@@ -13,7 +14,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             d.Update((byte)(n));
         }
 
-        public static void U16Str(ushort n, IDigest d)
+        public static void U16Str(short n, IDigest d)
         {
             d.Update((byte)(n >> 8));
             d.Update((byte)(n));
@@ -29,15 +30,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
             digest.BlockUpdate(array, start, len);
         }
 
-        public static int CalculateStrength(LMSParameters lmsParameters)
+        public static int CalculateStrength(LmsParameters lmsParameters)
         {
             if (lmsParameters == null)
-            {
-                throw new NullReferenceException("lmsParameters cannot be null");
-            }
+                throw new ArgumentNullException(nameof(lmsParameters));
 
-            LMSigParameters sigParameters = lmsParameters.GetLmSigParam();
-            return (1 << sigParameters.GetH()) * sigParameters.GetM();
+            LMSigParameters sigParameters = lmsParameters.LMSigParameters;
+            return sigParameters.M << sigParameters.H;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/lms/SeedDerive.cs b/crypto/src/pqc/crypto/lms/SeedDerive.cs
index 58083a329..38b72fd3f 100644
--- a/crypto/src/pqc/crypto/lms/SeedDerive.cs
+++ b/crypto/src/pqc/crypto/lms/SeedDerive.cs
@@ -1,94 +1,69 @@
 using System;
+
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Lms
 {
-    public class SeedDerive
+    public sealed class SeedDerive
     {
-        private byte[] I;
-        private byte[] masterSeed;
-        private IDigest digest;
-        private int q;
-        private int j;
-
+        private readonly byte[] m_I;
+        private readonly byte[] m_masterSeed;
+        private readonly IDigest m_digest;
 
         public SeedDerive(byte[] I, byte[] masterSeed, IDigest digest)
         {
-            this.I = I;
-            this.masterSeed = masterSeed;
-            this.digest = digest;
+            m_I = I;
+            m_masterSeed = masterSeed;
+            m_digest = digest;
         }
 
-        public int GetQ()
-        {
-            return q;
-        }
+        public int Q { get; set; }
 
-        public void SetQ(int q)
-        {
-            this.q = q;
-        }
+        public int J { get; set; }
 
-        public int GetJ()
-        {
-            return j;
-        }
+        // FIXME
+        public byte[] I => m_I;
 
-        public void SetJ(int j)
-        {
-            this.j = j;
-        }
+        // FIXME
+        public byte[] MasterSeed => m_masterSeed;
 
-        public byte[] GetI()
+        public byte[] DeriveSeed(bool incJ, byte[] target, int offset)
         {
-            return I;
-        }
+            if (target.Length - offset < m_digest.GetDigestSize())
+                throw new ArgumentException("target length is less than digest size.", nameof(target));
 
-        public byte[] GetMasterSeed()
-        {
-            return masterSeed;
-        }
+            int q = Q, j = J;
 
+            m_digest.BlockUpdate(I, 0, I.Length);
 
-        public byte[] DeriveSeed(byte[] target, int offset)
-        {
-            if (target.Length < digest.GetDigestSize())
-            {
-                throw new ArgumentException("target length is less than digest size.");
-            }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> qj = stackalloc byte[7];
+            Pack.UInt32_To_BE((uint)q, qj);
+            Pack.UInt16_To_BE((ushort)j, qj[4..]);
+            qj[6] = 0xFF;
+            m_digest.BlockUpdate(qj);
+#else
+            m_digest.Update((byte)(q >> 24));
+            m_digest.Update((byte)(q >> 16));
+            m_digest.Update((byte)(q >> 8));
+            m_digest.Update((byte)(q));
 
-            digest.BlockUpdate(I, 0, I.Length);
-            digest.Update((byte)(q >> 24));
-            digest.Update((byte)(q >> 16));
-            digest.Update((byte)(q >> 8));
-            digest.Update((byte)(q));
+            m_digest.Update((byte)(j >> 8));
+            m_digest.Update((byte)(j));
+            m_digest.Update(0xFF);
+#endif
 
-            digest.Update((byte)(j >> 8));
-            digest.Update((byte)(j));
-            digest.Update((byte)0xFF);
-            digest.BlockUpdate(masterSeed, 0, masterSeed.Length);
+            m_digest.BlockUpdate(m_masterSeed, 0, m_masterSeed.Length);
 
-            digest.DoFinal(target, offset); // Digest resets here.
-
-            return target;
-        }
-
-        public void deriveSeed(byte[] target, bool incJ)
-        {
-            deriveSeed(target, incJ, 0);
-        }
-
-
-        public void deriveSeed(byte[] target, bool incJ, int offset)
-        {
-
-            DeriveSeed(target, offset);
+            m_digest.DoFinal(target, offset); // Digest resets here.
 
             if (incJ)
             {
-                j++;
+                ++J;
             }
 
+            return target;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/ntru/NtruEncapsulation.cs b/crypto/src/pqc/crypto/ntru/NtruEncapsulation.cs
new file mode 100644
index 000000000..b00fbef31
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruEncapsulation.cs
@@ -0,0 +1,57 @@
+using System;
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    /// <summary>
+    /// Encapsulated secret encapsulated by NTRU.
+    /// </summary>
+    internal class NtruEncapsulation : ISecretWithEncapsulation
+    {
+        private readonly byte[] _sharedKey;
+        private readonly byte[] _ciphertext;
+
+        private bool _hasBeenDestroyed;
+
+        internal NtruEncapsulation(byte[] sharedKey, byte[] ciphertext)
+        {
+            _sharedKey = sharedKey;
+            _ciphertext = ciphertext;
+        }
+
+        public void Dispose()
+        {
+            if (!_hasBeenDestroyed)
+            {
+                Array.Clear(_sharedKey, 0, _sharedKey.Length);
+                Array.Clear(_ciphertext, 0, _ciphertext.Length);
+                _hasBeenDestroyed = true;
+            }
+        }
+
+        public byte[] GetSecret()
+        {
+            CheckDestroyed();
+            return _sharedKey;
+        }
+
+        public byte[] GetEncapsulation()
+        {
+            CheckDestroyed();
+            return _ciphertext;
+        }
+
+        void CheckDestroyed()
+        {
+            if (IsDestroyed())
+            {
+                throw new InvalidOperationException("Object has been destroyed");
+            }
+        }
+
+        public bool IsDestroyed()
+        {
+            return _hasBeenDestroyed;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruKemExtractor.cs b/crypto/src/pqc/crypto/ntru/NtruKemExtractor.cs
new file mode 100644
index 000000000..aba22a0f4
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruKemExtractor.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Diagnostics;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Owcpa;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    /// <summary>
+    /// NTRU secret encapsulation extractor.
+    /// </summary>
+    public class NtruKemExtractor : IEncapsulatedSecretExtractor
+    {
+        private readonly NtruParameters _parameters;
+        private readonly NtruPrivateKeyParameters _ntruPrivateKey;
+
+        public NtruKemExtractor(NtruPrivateKeyParameters ntruPrivateKey)
+        {
+            _parameters = ntruPrivateKey.Parameters;
+            _ntruPrivateKey = ntruPrivateKey;
+        }
+
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            Debug.Assert(_ntruPrivateKey != null);
+
+            NtruParameterSet parameterSet = _parameters.ParameterSet;
+
+            byte[] sk = _ntruPrivateKey.PrivateKey;
+            int i, fail;
+            byte[] rm;
+            byte[] buf = new byte[parameterSet.PrfKeyBytes + parameterSet.NtruCiphertextBytes()];
+
+            NtruOwcpa owcpa = new NtruOwcpa(parameterSet);
+            OwcpaDecryptResult owcpaResult = owcpa.Decrypt(encapsulation, _ntruPrivateKey.PrivateKey);
+            rm = owcpaResult.Rm;
+            fail = owcpaResult.Fail;
+
+            Sha3Digest sha3256 = new Sha3Digest(256);
+
+            byte[] k = new byte[sha3256.GetDigestSize()];
+
+            sha3256.BlockUpdate(rm, 0, rm.Length);
+            sha3256.DoFinal(k, 0);
+
+            /* shake(secret PRF key || input ciphertext) */
+            for (i = 0; i < parameterSet.PrfKeyBytes; i++)
+            {
+                buf[i] = sk[i + parameterSet.OwcpaSecretKeyBytes()];
+            }
+
+            for (i = 0; i < parameterSet.NtruCiphertextBytes(); i++)
+            {
+                buf[parameterSet.PrfKeyBytes + i] = encapsulation[i];
+            }
+
+            sha3256.Reset();
+            sha3256.BlockUpdate(buf, 0, buf.Length);
+            sha3256.DoFinal(rm, 0);
+
+            Cmov(k, rm, (byte)fail);
+
+            byte[] sharedKey = new byte[parameterSet.SharedKeyBytes];
+            Array.Copy(k, 0, sharedKey, 0, parameterSet.SharedKeyBytes);
+
+            Array.Clear(k, 0, k.Length);
+
+            return sharedKey;
+        }
+
+        private static void Cmov(byte[] r, byte[] x, byte b)
+        {
+            b = (byte)(~b + 1);
+            for (int i = 0; i < r.Length; i++)
+            {
+                r[i] ^= (byte)(b & (x[i] ^ r[i]));
+            }
+        }
+
+        public int EncapsulationLength => _parameters.ParameterSet.NtruCiphertextBytes();
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruKemGenerator.cs b/crypto/src/pqc/crypto/ntru/NtruKemGenerator.cs
new file mode 100644
index 000000000..e579c898d
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruKemGenerator.cs
@@ -0,0 +1,70 @@
+using System;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Owcpa;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    /// <summary>
+    /// Encapsulate a secret using NTRU. Returns an <see cref="NtruEncapsulation"/> as encapsulation.
+    /// </summary>
+    ///
+    /// <seealso cref="NtruKemExtractor"/>
+    /// <seealso href="https://ntru.org/">NTRU website</seealso>
+    public class NtruKemGenerator : IEncapsulatedSecretGenerator
+    {
+        private readonly SecureRandom _random;
+
+        public NtruKemGenerator(SecureRandom random)
+        {
+            _random = random;
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            var parameterSet = ((NtruPublicKeyParameters)recipientKey).GetParameters().ParameterSet;
+            var sampling = new NtruSampling(parameterSet);
+            var owcpa = new NtruOwcpa(parameterSet);
+            Polynomial r;
+            Polynomial m;
+            var rm = new byte[parameterSet.OwcpaMsgBytes()];
+            var rmSeed = new byte[parameterSet.SampleRmBytes()];
+
+            _random.NextBytes(rmSeed);
+
+            var pair = sampling.SampleRm(rmSeed);
+            r = pair.R();
+            m = pair.M();
+
+            var rm1 = r.S3ToBytes(parameterSet.OwcpaMsgBytes());
+            Array.Copy(rm1, 0, rm, 0, rm1.Length);
+
+            var rm2 = m.S3ToBytes(rm.Length - parameterSet.PackTrinaryBytes());
+
+            Array.Copy(rm2, 0, rm, parameterSet.PackTrinaryBytes(), rm2.Length);
+
+            var sha3256 = new Sha3Digest(256);
+            sha3256.BlockUpdate(rm, 0, rm.Length);
+
+
+            var k = new byte[sha3256.GetDigestSize()];
+
+            sha3256.DoFinal(k, 0);
+
+
+            r.Z3ToZq();
+
+            var c = owcpa.Encrypt(r, m, ((NtruPublicKeyParameters)recipientKey).PublicKey);
+
+            var sharedKey = new byte[parameterSet.SharedKeyBytes];
+            Array.Copy(k, 0, sharedKey, 0, sharedKey.Length);
+
+            Array.Clear(k, 0, k.Length);
+
+            return new NtruEncapsulation(sharedKey, c);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruKeyGenerationParameters.cs b/crypto/src/pqc/crypto/ntru/NtruKeyGenerationParameters.cs
new file mode 100644
index 000000000..df2a28bb0
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruKeyGenerationParameters.cs
@@ -0,0 +1,21 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    public class NtruKeyGenerationParameters : KeyGenerationParameters
+    {
+        internal NtruParameters NtruParameters { get; }
+
+        // We won't be using strength as the key length differs between public & private key
+        public NtruKeyGenerationParameters(SecureRandom random, NtruParameters ntruParameters) : base(random, 1)
+        {
+            NtruParameters = ntruParameters;
+        }
+
+        public NtruParameters GetParameters()
+        {
+            return NtruParameters;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruKeyPairGenerator.cs b/crypto/src/pqc/crypto/ntru/NtruKeyPairGenerator.cs
new file mode 100644
index 000000000..60bddc4c3
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruKeyPairGenerator.cs
@@ -0,0 +1,44 @@
+using System;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Owcpa;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    public class NtruKeyPairGenerator : IAsymmetricCipherKeyPairGenerator
+    {
+        private NtruKeyGenerationParameters _keygenParameters;
+        private SecureRandom _random;
+
+        public void Init(KeyGenerationParameters parameters)
+        {
+            _keygenParameters = (NtruKeyGenerationParameters)parameters;
+            _random = parameters.Random;
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            // Debug.Assert(this._random != null);
+            NtruParameterSet parameterSet = _keygenParameters.NtruParameters.ParameterSet;
+
+            var seed = new byte[parameterSet.SampleFgBytes()];
+            _random.NextBytes(seed);
+
+            NtruOwcpa owcpa = new NtruOwcpa(parameterSet);
+            OwcpaKeyPair owcpaKeys = owcpa.KeyPair(seed);
+
+            byte[] publicKey = owcpaKeys.PublicKey;
+            byte[] privateKey = new byte[parameterSet.NtruSecretKeyBytes()];
+            byte[] owcpaPrivateKey = owcpaKeys.PrivateKey;
+            Array.Copy(owcpaPrivateKey, 0, privateKey, 0, owcpaPrivateKey.Length);
+            //
+            byte[] prfBytes = new byte[parameterSet.PrfKeyBytes];
+            _random.NextBytes(prfBytes);
+            Array.Copy(prfBytes, 0, privateKey, parameterSet.OwcpaSecretKeyBytes(), prfBytes.Length);
+
+            return new AsymmetricCipherKeyPair(new NtruPublicKeyParameters(_keygenParameters.NtruParameters, publicKey),
+                new NtruPrivateKeyParameters(_keygenParameters.NtruParameters, privateKey));
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruKeyParameters.cs b/crypto/src/pqc/crypto/ntru/NtruKeyParameters.cs
new file mode 100644
index 000000000..906f7d5d4
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruKeyParameters.cs
@@ -0,0 +1,21 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    public abstract class NtruKeyParameters : AsymmetricKeyParameter
+    {
+        public NtruParameters Parameters { get; }
+
+        public NtruKeyParameters(bool privateKey, NtruParameters parameters) : base(privateKey)
+        {
+            Parameters = parameters;
+        }
+
+        public abstract byte[] GetEncoded();
+
+        public NtruParameters GetParameters()
+        {
+            return Parameters;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruParameters.cs b/crypto/src/pqc/crypto/ntru/NtruParameters.cs
new file mode 100644
index 000000000..3bf2233ff
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruParameters.cs
@@ -0,0 +1,33 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    public class NtruParameters : ICipherParameters
+    {
+        public static readonly NtruParameters NtruHps2048509 =
+            new NtruParameters("ntruhps2048509", new NtruHps2048509());
+
+        public static readonly NtruParameters NtruHps2048677 =
+            new NtruParameters("ntruhps2048677", new NtruHps2048677());
+
+        public static readonly NtruParameters NtruHps4096821 =
+            new NtruParameters("ntruhps4096821", new NtruHps4096821());
+
+        public static readonly NtruParameters NtruHrss701 = new NtruParameters("ntruhrss701", new NtruHrss701());
+
+        internal readonly NtruParameterSet ParameterSet;
+
+        private readonly string _name;
+
+        private NtruParameters(string name, NtruParameterSet parameterSet)
+        {
+            _name = name;
+            ParameterSet = parameterSet;
+        }
+
+        public string Name => _name;
+
+        public int DefaultKeySize => ParameterSet.SharedKeyBytes * 8;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruPrivateKeyParamaters.cs b/crypto/src/pqc/crypto/ntru/NtruPrivateKeyParamaters.cs
new file mode 100644
index 000000000..0c90a8afa
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruPrivateKeyParamaters.cs
@@ -0,0 +1,24 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    public class NtruPrivateKeyParameters : NtruKeyParameters
+    {
+        private byte[] _privateKey;
+
+        public byte[] PrivateKey
+        {
+            get => (byte[])_privateKey.Clone();
+            private set => _privateKey = (byte[])value.Clone();
+        }
+
+        public NtruPrivateKeyParameters(NtruParameters parameters, byte[] key) : base(true, parameters)
+        {
+            PrivateKey = key;
+        }
+
+
+        public override byte[] GetEncoded()
+        {
+            return PrivateKey;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruPublicKeyParameters.cs b/crypto/src/pqc/crypto/ntru/NtruPublicKeyParameters.cs
new file mode 100644
index 000000000..6a7cc0752
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruPublicKeyParameters.cs
@@ -0,0 +1,23 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    public class NtruPublicKeyParameters : NtruKeyParameters
+    {
+        private byte[] _publicKey;
+
+        public byte[] PublicKey
+        {
+            get => (byte[])_publicKey.Clone();
+            set => _publicKey = (byte[])value.Clone();
+        }
+
+        public NtruPublicKeyParameters(NtruParameters parameters, byte[] key) : base(false, parameters)
+        {
+            PublicKey = key;
+        }
+
+        public override byte[] GetEncoded()
+        {
+            return PublicKey;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/NtruSampling.cs b/crypto/src/pqc/crypto/ntru/NtruSampling.cs
new file mode 100644
index 000000000..fca99b130
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/NtruSampling.cs
@@ -0,0 +1,207 @@
+using System;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    /// <summary>
+    /// NTRU sampling.
+    ///
+    /// <seealso href="https://ntru.org/f/ntru-20190330.pdf">NTRU specification section 1.10</seealso>
+    /// </summary>
+    internal class NtruSampling
+    {
+        private readonly NtruParameterSet _parameterSet;
+
+        internal NtruSampling(NtruParameterSet parameterSet)
+        {
+            _parameterSet = parameterSet;
+        }
+
+        /// <summary>
+        /// Sample_fg
+        /// </summary>
+        /// <param name="uniformBytes">random byte array</param>
+        /// <returns>a pair of polynomial <c>f</c> and <c>g</c></returns>
+        /// <exception cref="ArgumentException"></exception>
+        // TODO: using tuple as return value might be better but I'm not sure if it's available with the target
+        // language version
+        internal PolynomialPair SampleFg(byte[] uniformBytes)
+        {
+            switch (_parameterSet)
+            {
+                case NtruHrssParameterSet _:
+                {
+                    var f = SampleIidPlus(Arrays.CopyOfRange(uniformBytes, 0, _parameterSet.SampleIidBytes()));
+                    var g = SampleIidPlus(Arrays.CopyOfRange(uniformBytes, _parameterSet.SampleIidBytes(),
+                        uniformBytes.Length));
+                    return new PolynomialPair(f, g);
+                }
+                case NtruHpsParameterSet _:
+                {
+                    var f = (HpsPolynomial)SampleIid(
+                        Arrays.CopyOfRange(uniformBytes, 0, _parameterSet.SampleIidBytes()));
+                    var g = SampleFixedType(Arrays.CopyOfRange(uniformBytes, _parameterSet.SampleIidBytes(),
+                        uniformBytes.Length));
+                    return new PolynomialPair(f, g);
+                }
+                default:
+                    throw new ArgumentException("Invalid polynomial type");
+            }
+        }
+
+        /// <summary>
+        /// Sample_rm
+        /// </summary>
+        /// <param name="uniformBytes">random byte array</param>
+        /// <returns>a pair of polynomial <c>r</c> and <c>m</c></returns>
+        /// <exception cref="ArgumentException"></exception>
+        internal PolynomialPair SampleRm(byte[] uniformBytes)
+        {
+            switch (_parameterSet)
+            {
+                case NtruHrssParameterSet _:
+                {
+                    var r = (HrssPolynomial)SampleIid(Arrays.CopyOfRange(uniformBytes, 0,
+                        _parameterSet.SampleIidBytes()));
+                    var m = (HrssPolynomial)SampleIid(Arrays.CopyOfRange(uniformBytes, _parameterSet.SampleIidBytes(),
+                        uniformBytes.Length));
+                    return new PolynomialPair(r, m);
+                }
+                case NtruHpsParameterSet _:
+                {
+                    var r = (HpsPolynomial)SampleIid(
+                        Arrays.CopyOfRange(uniformBytes, 0, _parameterSet.SampleIidBytes()));
+                    var m = SampleFixedType(Arrays.CopyOfRange(uniformBytes, _parameterSet.SampleIidBytes(),
+                        uniformBytes.Length));
+                    return new PolynomialPair(r, m);
+                }
+                default:
+                    throw new ArgumentException("Invalid polynomial type");
+            }
+        }
+
+        /// <summary>
+        /// Ternary
+        /// </summary>
+        /// <param name="uniformBytes">random byte array</param>
+        /// <returns>A ternary polynomial</returns>
+        internal Polynomial SampleIid(byte[] uniformBytes)
+        {
+            var r = _parameterSet.CreatePolynomial();
+            for (var i = 0; i < _parameterSet.N - 1; i++)
+            {
+                r.coeffs[i] = (ushort)Mod3(uniformBytes[i]);
+            }
+
+            r.coeffs[_parameterSet.N - 1] = 0;
+            return r;
+        }
+
+        /// <summary>
+        /// Fixed_Type
+        /// </summary>
+        /// <param name="uniformBytes">random byte array</param>
+        /// <returns>a ternary polynomial with exactly q/16 − 1 coefficients equal to 1 and q/16 − 1 coefficient equal to −1</returns>
+        internal HpsPolynomial SampleFixedType(byte[] uniformBytes)
+        {
+            var n = _parameterSet.N;
+            var weight = ((NtruHpsParameterSet)_parameterSet).Weight();
+            var r = (HpsPolynomial)_parameterSet.CreatePolynomial();
+            var s = new int[n - 1];
+            int i;
+
+            for (i = 0; i < (n - 1) / 4; i++)
+            {
+                s[4 * i + 0] = (uniformBytes[15 * i + 0] << 2) + (uniformBytes[15 * i + 1] << 10) +
+                               (uniformBytes[15 * i + 2] << 18) + (uniformBytes[15 * i + 3] << 26);
+                s[4 * i + 1] = ((uniformBytes[15 * i + 3] & 0xc0) >> 4) + (uniformBytes[15 * i + 4] << 4) +
+                               (uniformBytes[15 * i + 5] << 12) + (uniformBytes[15 * i + 6] << 20) +
+                               (uniformBytes[15 * i + 7] << 28);
+                s[4 * i + 2] = ((uniformBytes[15 * i + 7] & 0xf0) >> 2) + (uniformBytes[15 * i + 8] << 6) +
+                               (uniformBytes[15 * i + 9] << 14) + (uniformBytes[15 * i + 10] << 22) +
+                               (uniformBytes[15 * i + 11] << 30);
+                s[4 * i + 3] = (uniformBytes[15 * i + 11] & 0xfc) + (uniformBytes[15 * i + 12] << 8) +
+                               (uniformBytes[15 * i + 13] << 16) + (uniformBytes[15 * i + 14] << 24);
+            }
+
+            // (N-1) = 2 mod 4
+            if (n - 1 > (n - 1) / 4 * 4)
+            {
+                i = (n - 1) / 4;
+                s[4 * i + 0] = (uniformBytes[15 * i + 0] << 2) + (uniformBytes[15 * i + 1] << 10) +
+                               (uniformBytes[15 * i + 2] << 18) + (uniformBytes[15 * i + 3] << 26);
+                s[4 * i + 1] = ((uniformBytes[15 * i + 3] & 0xc0) >> 4) + (uniformBytes[15 * i + 4] << 4) +
+                               (uniformBytes[15 * i + 5] << 12) + (uniformBytes[15 * i + 6] << 20) +
+                               (uniformBytes[15 * i + 7] << 28);
+            }
+
+            for (i = 0; i < weight / 2; i++)
+            {
+                s[i] |= 1;
+            }
+
+            for (i = weight / 2; i < weight; i++)
+            {
+                s[i] |= 2;
+            }
+
+            Array.Sort(s);
+
+            for (i = 0; i < n - 1; i++)
+            {
+                r.coeffs[i] = (ushort)(s[i] & 3);
+            }
+
+            r.coeffs[n - 1] = 0;
+            return r;
+        }
+
+        /// <summary>
+        /// Ternary_Plus
+        /// </summary>
+        /// <param name="uniformBytes">random byte array</param>
+        /// <returns>a ternary polynomial that satisfies the non-negative correlation property</returns>
+        internal HrssPolynomial SampleIidPlus(byte[] uniformBytes)
+        {
+            var n = _parameterSet.N;
+            int i;
+            ushort s = 0;
+            var r = (HrssPolynomial)SampleIid(uniformBytes);
+
+            /* Map {0,1,2} -> {0, 1, 2^16 - 1} */
+            for (i = 0; i < n - 1; i++)
+            {
+                r.coeffs[i] = (ushort)(r.coeffs[i] | -(r.coeffs[i] >> 1));
+            }
+
+            /* s = <x*r, r>.  (r[n-1] = 0) */
+            for (i = 0; i < n - 1; i++)
+            {
+                s += (ushort)((uint)r.coeffs[i + 1] * r.coeffs[i]);
+            }
+
+            /* Extract sign of s (sign(0) = 1) */
+            s = (ushort)(1 | -(s >> 15));
+
+            for (i = 0; i < n - 1; i += 2)
+            {
+                r.coeffs[i] = (ushort)((uint)s * r.coeffs[i]);
+            }
+
+            /* Map {0,1,2^16-1} -> {0, 1, 2} */
+            for (i = 0; i < n - 1; i++)
+            {
+                r.coeffs[i] = (ushort)(3 & (r.coeffs[i] ^ (r.coeffs[i] >> 15)));
+            }
+
+            return r;
+        }
+
+        private static int Mod3(int a)
+        {
+            return a % 3;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/PolynomialPair.cs b/crypto/src/pqc/crypto/ntru/PolynomialPair.cs
new file mode 100644
index 000000000..8c7a2c3cb
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/PolynomialPair.cs
@@ -0,0 +1,36 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru
+{
+    internal class PolynomialPair
+    {
+        private readonly Polynomial _a;
+        private readonly Polynomial _b;
+
+        public PolynomialPair(Polynomial a, Polynomial b)
+        {
+            _a = a;
+            _b = b;
+        }
+
+        internal Polynomial F()
+        {
+            return _a;
+        }
+
+        internal Polynomial G()
+        {
+            return _b;
+        }
+
+        internal Polynomial R()
+        {
+            return _a;
+        }
+
+        internal Polynomial M()
+        {
+            return _b;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/owcpa/NtruOwcpa.cs b/crypto/src/pqc/crypto/ntru/owcpa/NtruOwcpa.cs
new file mode 100644
index 000000000..b6cbdfd5d
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/owcpa/NtruOwcpa.cs
@@ -0,0 +1,246 @@
+using System;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Owcpa
+{
+    /// <summary>
+    /// An OW-CPA secure deterministic public key encryption scheme (DPKE).
+    /// </summary>
+    internal class NtruOwcpa
+    {
+        private readonly NtruParameterSet _parameterSet;
+        private readonly NtruSampling _sampling;
+
+        internal NtruOwcpa(NtruParameterSet parameterSet)
+        {
+            _parameterSet = parameterSet;
+            _sampling = new NtruSampling(parameterSet);
+        }
+
+        /// <summary>
+        /// Generate a DPKE key pair.
+        /// </summary>
+        /// <param name="seed">a random byte array</param>
+        /// <returns>DPKE key pair</returns>
+        internal OwcpaKeyPair KeyPair(byte[] seed)
+        {
+            byte[] publicKey;
+            var privateKey = new byte[_parameterSet.OwcpaSecretKeyBytes()];
+            var n = _parameterSet.N;
+            var q = _parameterSet.Q();
+            int i;
+            PolynomialPair pair;
+            Polynomial x1, x2, x3, x4, x5;
+            x1 = _parameterSet.CreatePolynomial();
+            x2 = _parameterSet.CreatePolynomial();
+            x3 = _parameterSet.CreatePolynomial();
+            x4 = _parameterSet.CreatePolynomial();
+            x5 = _parameterSet.CreatePolynomial();
+
+            Polynomial f, g, invfMod3 = x3;
+            Polynomial gf = x3, invgf = x4, tmp = x5;
+            Polynomial invh = x3, h = x3;
+
+            pair = _sampling.SampleFg(seed);
+            f = pair.F();
+            g = pair.G();
+
+            invfMod3.S3Inv(f);
+            var fs3ToBytes = f.S3ToBytes(_parameterSet.OwcpaMsgBytes());
+            Array.Copy(fs3ToBytes, 0, privateKey, 0, fs3ToBytes.Length);
+            var s3Res = invfMod3.S3ToBytes(privateKey.Length - _parameterSet.PackTrinaryBytes());
+            Array.Copy(s3Res, 0, privateKey, _parameterSet.PackTrinaryBytes(), s3Res.Length);
+
+            f.Z3ToZq();
+            g.Z3ToZq();
+
+            if (_parameterSet is NtruHrssParameterSet)
+            {
+                /* g = 3*(x-1)*g */
+                for (i = n - 1; i > 0; i--)
+                {
+                    g.coeffs[i] = (ushort)(3 * (g.coeffs[i - 1] - g.coeffs[i]));
+                }
+
+                g.coeffs[0] = (ushort)-(3 * g.coeffs[0]);
+            }
+            else
+            {
+                for (i = 0; i < n; i++)
+                {
+                    g.coeffs[i] = (ushort)(3 * g.coeffs[i]);
+                }
+            }
+
+            gf.RqMul(g, f);
+            invgf.RqInv(gf);
+
+            tmp.RqMul(invgf, f);
+            invh.SqMul(tmp, f);
+            var sqRes = invh.SqToBytes(privateKey.Length - 2 * _parameterSet.PackTrinaryBytes());
+            Array.Copy(sqRes, 0, privateKey, 2 * _parameterSet.PackTrinaryBytes(), sqRes.Length);
+
+            tmp.RqMul(invgf, g);
+            h.RqMul(tmp, g);
+            publicKey = h.RqSumZeroToBytes(_parameterSet.OwcpaPublicKeyBytes());
+
+            return new OwcpaKeyPair(publicKey, privateKey);
+        }
+
+        /// <summary>
+        /// DPKE encryption.
+        /// </summary>
+        /// <param name="r"></param>
+        /// <param name="m"></param>
+        /// <param name="publicKey"></param>
+        /// <returns>DPKE ciphertext</returns>
+        internal byte[] Encrypt(Polynomial r, Polynomial m, byte[] publicKey)
+        {
+            int i;
+            Polynomial x1 = _parameterSet.CreatePolynomial(), x2 = _parameterSet.CreatePolynomial();
+            Polynomial h = x1, liftm = x1;
+            Polynomial ct = x2;
+
+            h.RqSumZeroFromBytes(publicKey);
+
+            ct.RqMul(r, h);
+
+            liftm.Lift(m);
+
+            for (i = 0; i < _parameterSet.N; i++)
+            {
+                ct.coeffs[i] += liftm.coeffs[i];
+            }
+
+            return ct.RqSumZeroToBytes(_parameterSet.NtruCiphertextBytes());
+        }
+
+        /// <summary>
+        /// DPKE decryption.
+        /// </summary>
+        /// <param name="ciphertext"></param>
+        /// <param name="privateKey"></param>
+        /// <returns>an instance of <see cref="OwcpaDecryptResult"/> containing <c>packed_rm</c> an  fail flag</returns>
+        internal OwcpaDecryptResult Decrypt(byte[] ciphertext, byte[] privateKey)
+        {
+            byte[] sk = privateKey;
+            byte[] rm = new byte[_parameterSet.OwcpaMsgBytes()];
+            int i, fail;
+            Polynomial x1 = _parameterSet.CreatePolynomial();
+            Polynomial x2 = _parameterSet.CreatePolynomial();
+            Polynomial x3 = _parameterSet.CreatePolynomial();
+            Polynomial x4 = _parameterSet.CreatePolynomial();
+
+            Polynomial c = x1, f = x2, cf = x3;
+            Polynomial mf = x2, finv3 = x3, m = x4;
+            Polynomial liftm = x2, invh = x3, r = x4;
+            Polynomial b = x1;
+
+            c.RqSumZeroFromBytes(ciphertext);
+            f.S3FromBytes(sk);
+
+            f.Z3ToZq();
+
+            cf.RqMul(c, f);
+
+            mf.RqToS3(cf);
+
+            finv3.S3FromBytes(Arrays.CopyOfRange(sk, _parameterSet.PackTrinaryBytes(), sk.Length));
+
+            m.S3Mul(mf, finv3);
+
+            byte[] arr1 = m.S3ToBytes(rm.Length - _parameterSet.PackTrinaryBytes());
+
+            fail = 0;
+
+            /* Check that the unused bits of the last byte of the ciphertext are zero */
+            fail |= CheckCiphertext(ciphertext);
+
+            /* For the IND-CCA2 KEM we must ensure that c = Enc(h, (r,m)).             */
+            /* We can avoid re-computing r*h + Lift(m) as long as we check that        */
+            /* r (defined as b/h mod (q, Phi_n)) and m are in the message space.       */
+            /* (m can take any value in S3 in NTRU_HRSS) */
+
+
+            if (_parameterSet is NtruHpsParameterSet)
+            {
+                fail |= CheckM((HpsPolynomial)m);
+            }
+
+            /* b = c - Lift(m) mod (q, x^n - 1) */
+            liftm.Lift(m);
+
+            for (i = 0; i < _parameterSet.N; i++)
+            {
+                b.coeffs[i] = (ushort)(c.coeffs[i] - liftm.coeffs[i]);
+            }
+
+            /* r = b / h mod (q, Phi_n) */
+            invh.SqFromBytes(Arrays.CopyOfRange(sk, 2 * _parameterSet.PackTrinaryBytes(), sk.Length));
+            r.SqMul(b, invh);
+
+            fail |= CheckR(r);
+
+            r.TrinaryZqToZ3();
+            byte[] arr2 = r.S3ToBytes(_parameterSet.OwcpaMsgBytes());
+            Array.Copy(arr2, 0, rm, 0, arr2.Length);
+            Array.Copy(arr1, 0, rm, _parameterSet.PackTrinaryBytes(), arr1.Length);
+
+            return new OwcpaDecryptResult(rm, fail);
+        }
+
+        private int CheckCiphertext(byte[] ciphertext)
+        {
+            ushort t;
+            t = ciphertext[_parameterSet.NtruCiphertextBytes() - 1];
+            t &= (ushort)(0xff << (8 - (7 & (_parameterSet.LogQ * _parameterSet.PackDegree()))));
+
+            /* We have 0 <= t < 256 */
+            /* Return 0 on success (t=0), 1 on failure */
+            return 1 & ((~t + 1) >> 15);
+        }
+
+        private int CheckR(Polynomial r)
+        {
+            /* A valid r has coefficients in {0,1,q-1} and has r[N-1] = 0 */
+            /* Note: We may assume that 0 <= r[i] <= q-1 for all i        */
+            int i;
+            int t = 0; // unsigned
+            ushort c; // unsigned
+            for (i = 0; i < _parameterSet.N - 1; i++)
+            {
+                c = r.coeffs[i];
+                t |= (c + 1) & (_parameterSet.Q() - 4); /* 0 iff c is in {-1,0,1,2} */
+                t |= (c + 2) & 4; /* 1 if c = 2, 0 if c is in {-1,0,1} */
+            }
+
+            t |= r.coeffs[_parameterSet.N - 1]; /* Coefficient n-1 must be zero */
+
+            /* We have 0 <= t < 2^16. */
+            /* Return 0 on success (t=0), 1 on failure */
+            return (1 & ((~t + 1) >> 31));
+        }
+
+        private int CheckM(HpsPolynomial m)
+        {
+            int i;
+            int t = 0; // unsigned
+            ushort ps = 0; // unsigned
+            ushort ms = 0; // unsigned
+            for (i = 0; i < _parameterSet.N - 1; i++)
+            {
+                ps += (ushort)(m.coeffs[i] & 1);
+                ms += (ushort)(m.coeffs[i] & 2);
+            }
+
+            t |= ps ^ (ms >> 1);
+            t |= ms ^ ((NtruHpsParameterSet)_parameterSet).Weight();
+
+            /* We have 0 <= t < 2^16. */
+            /* Return 0 on success (t=0), 1 on failure */
+            return (1 & ((~t + 1) >> 31));
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/owcpa/OwcpaDecryptResult.cs b/crypto/src/pqc/crypto/ntru/owcpa/OwcpaDecryptResult.cs
new file mode 100644
index 000000000..53f8b4219
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/owcpa/OwcpaDecryptResult.cs
@@ -0,0 +1,14 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Owcpa
+{
+    internal class OwcpaDecryptResult
+    {
+        internal byte[] Rm;
+        internal int Fail;
+
+        internal OwcpaDecryptResult(byte[] rm, int fail)
+        {
+            Rm = rm;
+            Fail = fail;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/owcpa/OwcpaKeyPair.cs b/crypto/src/pqc/crypto/ntru/owcpa/OwcpaKeyPair.cs
new file mode 100644
index 000000000..305e653d8
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/owcpa/OwcpaKeyPair.cs
@@ -0,0 +1,14 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Owcpa
+{
+    internal class OwcpaKeyPair
+    {
+        internal readonly byte[] PublicKey;
+        internal readonly byte[] PrivateKey;
+
+        internal OwcpaKeyPair(byte[] publicKey, byte[] privateKey)
+        {
+            PublicKey = publicKey;
+            PrivateKey = privateKey;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048509.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048509.cs
new file mode 100644
index 000000000..dcbf47636
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048509.cs
@@ -0,0 +1,9 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal class NtruHps2048509 : NtruHpsParameterSet
+    {
+        internal NtruHps2048509() : base(509, 11, 32, 32, 16)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048677.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048677.cs
new file mode 100644
index 000000000..2076f160d
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruHps2048677.cs
@@ -0,0 +1,9 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal class NtruHps2048677 : NtruHpsParameterSet
+    {
+        internal NtruHps2048677() : base(677, 11, 32, 32, 24)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruHps4096821.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruHps4096821.cs
new file mode 100644
index 000000000..df01f76be
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruHps4096821.cs
@@ -0,0 +1,16 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal class NtruHps4096821 : NtruHpsParameterSet
+    {
+        internal NtruHps4096821() : base(821, 12, 32, 32, 32)
+        {
+        }
+
+        internal override Polynomial CreatePolynomial()
+        {
+            return new Hps4096Polynomial(this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruHpsParameterSet.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruHpsParameterSet.cs
new file mode 100644
index 000000000..9b0d04305
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruHpsParameterSet.cs
@@ -0,0 +1,32 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal class NtruHpsParameterSet : NtruParameterSet
+    {
+        private protected NtruHpsParameterSet(int n, int logQ, int seedBytes, int prfKeyBytes, int sharedKeyBytes) :
+            base(n, logQ, seedBytes, prfKeyBytes, sharedKeyBytes)
+        {
+        }
+
+        internal override Polynomial CreatePolynomial()
+        {
+            return new HpsPolynomial(this);
+        }
+
+        internal override int SampleFgBytes()
+        {
+            return SampleIidBytes() + SampleFixedTypeBytes();
+        }
+
+        internal override int SampleRmBytes()
+        {
+            return SampleIidBytes() + SampleFixedTypeBytes();
+        }
+
+        internal int Weight()
+        {
+            return Q() / 8 - 2;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruHrss701.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruHrss701.cs
new file mode 100644
index 000000000..9e795265e
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruHrss701.cs
@@ -0,0 +1,9 @@
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal class NtruHrss701 : NtruHrssParameterSet
+    {
+        internal NtruHrss701() : base(701, 13, 32, 32, 24)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruHrssParameterSet.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruHrssParameterSet.cs
new file mode 100644
index 000000000..8a5e1ab66
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruHrssParameterSet.cs
@@ -0,0 +1,27 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal class NtruHrssParameterSet : NtruParameterSet
+    {
+        private protected NtruHrssParameterSet(int n, int logQ, int seedBytes, int prfKeyBytes, int sharedKeyBytes) :
+            base(n, logQ, seedBytes, prfKeyBytes, sharedKeyBytes)
+        {
+        }
+
+        internal override Polynomial CreatePolynomial()
+        {
+            return new HrssPolynomial(this);
+        }
+
+        internal override int SampleFgBytes()
+        {
+            return 2 * SampleIidBytes();
+        }
+
+        internal override int SampleRmBytes()
+        {
+            return 2 * SampleIidBytes();
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/parametersets/NtruParameterSet.cs b/crypto/src/pqc/crypto/ntru/parametersets/NtruParameterSet.cs
new file mode 100644
index 000000000..c105e0cd3
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/parametersets/NtruParameterSet.cs
@@ -0,0 +1,88 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets
+{
+    internal abstract class NtruParameterSet
+    {
+        internal int N { get; }
+        internal int LogQ { get; }
+        internal int SeedBytes { get; }
+        internal int PrfKeyBytes { get; }
+        internal int SharedKeyBytes { get; }
+
+        internal NtruParameterSet(int n, int logQ, int seedBytes, int prfKeyBytes, int sharedKeyBytes)
+        {
+            N = n;
+            LogQ = logQ;
+            SeedBytes = seedBytes;
+            PrfKeyBytes = prfKeyBytes;
+            SharedKeyBytes = sharedKeyBytes;
+        }
+
+        internal abstract Polynomial CreatePolynomial();
+
+        internal int Q()
+        {
+            return 1 << LogQ;
+        }
+
+        internal int SampleIidBytes()
+        {
+            return N - 1;
+        }
+
+        internal int SampleFixedTypeBytes()
+        {
+            return (30 * (N - 1) + 7) / 8;
+        }
+
+        internal abstract int SampleFgBytes();
+
+        internal abstract int SampleRmBytes();
+
+        internal int PackDegree()
+        {
+            return N - 1;
+        }
+
+        internal int PackTrinaryBytes()
+        {
+            return (PackDegree() + 4) / 5;
+        }
+
+        internal int OwcpaMsgBytes()
+        {
+            return 2 * PackTrinaryBytes();
+        }
+
+        internal int OwcpaPublicKeyBytes()
+        {
+            return (LogQ * PackDegree() + 7) / 8;
+        }
+
+        internal int OwcpaSecretKeyBytes()
+        {
+            return 2 * PackTrinaryBytes() + OwcpaPublicKeyBytes();
+        }
+
+        internal int OwcpaBytes()
+        {
+            return (LogQ * PackDegree() + 7) / 8;
+        }
+
+        internal int NtruPublicKeyBytes()
+        {
+            return OwcpaPublicKeyBytes();
+        }
+
+        internal int NtruSecretKeyBytes()
+        {
+            return OwcpaSecretKeyBytes() + PrfKeyBytes;
+        }
+
+        internal int NtruCiphertextBytes()
+        {
+            return OwcpaBytes();
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/polynomials/Hps4096Polynomial.cs b/crypto/src/pqc/crypto/ntru/polynomials/Hps4096Polynomial.cs
new file mode 100644
index 000000000..9a0d97759
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/polynomials/Hps4096Polynomial.cs
@@ -0,0 +1,42 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials
+{
+    internal class Hps4096Polynomial : HpsPolynomial
+    {
+        public Hps4096Polynomial(NtruParameterSet parameterSet) : base(parameterSet)
+        {
+        }
+
+        public override byte[] SqToBytes(int len)
+        {
+            byte[] r = new byte[len];
+            uint q = (uint)ParameterSet.Q();
+            int i;
+
+            for (i = 0; i < ParameterSet.PackDegree() / 2; i++)
+            {
+                r[3 * i + 0] = (byte)(ModQ(((uint)coeffs[2 * i + 0] & 0xffff), q) & 0xff);
+                r[3 * i + 1] = (byte)((ModQ(((uint)coeffs[2 * i + 0] & 0xffff), q) >> 8) |
+                                      ((ModQ((uint)(coeffs[2 * i + 1] & 0xffff), q) & 0x0f) << 4));
+                r[3 * i + 2] = (byte)((ModQ(((uint)coeffs[2 * i + 1] & 0xffff), q) >> 4));
+            }
+
+            return r;
+        }
+
+        public override void SqFromBytes(byte[] a)
+        {
+            int i;
+            for (i = 0; i < ParameterSet.PackDegree() / 2; i++)
+            {
+                coeffs[2 * i + 0] =
+                    (ushort)(((a[3 * i + 0] & 0xff) >> 0) | (((ushort)(a[3 * i + 1] & 0xff) & 0x0f) << 8));
+                coeffs[2 * i + 1] =
+                    (ushort)(((a[3 * i + 1] & 0xff) >> 4) | (((ushort)(a[3 * i + 2] & 0xff) & 0xff) << 4));
+            }
+
+            coeffs[ParameterSet.N - 1] = 0;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/polynomials/HpsPolynomial.cs b/crypto/src/pqc/crypto/ntru/polynomials/HpsPolynomial.cs
new file mode 100644
index 000000000..6097912a1
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/polynomials/HpsPolynomial.cs
@@ -0,0 +1,162 @@
+using System;
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials
+{
+    internal class HpsPolynomial : Polynomial
+    {
+        public HpsPolynomial(NtruParameterSet parameterSet) : base(parameterSet)
+        {
+        }
+
+        public override byte[] SqToBytes(int len)
+        {
+            byte[] r = new byte[len];
+
+            int i, j;
+            short[] t = new short[8];
+            for (i = 0;
+                 i < ParameterSet.PackDegree() / 8;
+                 i++)
+            {
+                for (j = 0; j < 8; j++)
+                {
+                    t[j] = (short)ModQ((uint)coeffs[8 * i + j] & 0xffff, (uint)ParameterSet.Q());
+                }
+
+                r[11 * i + 0] = (byte)(t[0] & 0xff);
+                r[11 * i + 1] = (byte)((t[0] >> 8) | ((t[1] & 0x1f) << 3));
+                r[11 * i + 2] = (byte)((t[1] >> 5) | ((t[2] & 0x03) << 6));
+                r[11 * i + 3] = (byte)((t[2] >> 2) & 0xff);
+                r[11 * i + 4] = (byte)((t[2] >> 10) | ((t[3] & 0x7f) << 1));
+                r[11 * i + 5] = (byte)((t[3] >> 7) | ((t[4] & 0x0f) << 4));
+                r[11 * i + 6] = (byte)((t[4] >> 4) | ((t[5] & 0x01) << 7));
+                r[11 * i + 7] = (byte)((t[5] >> 1) & 0xff);
+                r[11 * i + 8] = (byte)((t[5] >> 9) | ((t[6] & 0x3f) << 2));
+                r[11 * i + 9] = (byte)((t[6] >> 6) | ((t[7] & 0x07) << 5));
+                r[11 * i + 10] = (byte)(t[7] >> 3);
+            }
+
+            for (j = 0; j < ParameterSet.PackDegree() - 8 * i; j++)
+            {
+                t[j] = (short)ModQ((uint)coeffs[8 * i + j] & 0xffff, (uint)ParameterSet.Q());
+            }
+
+            for (; j < 8; j++)
+            {
+                t[j] = 0;
+            }
+
+            switch (ParameterSet.PackDegree() & 0x07)
+            {
+                case 4:
+                {
+                    r[11 * i + 0] = (byte)(t[0] & 0xff);
+                    r[11 * i + 1] = (byte)((t[0] >> 8) | ((t[1] & 0x1f) << 3));
+                    r[11 * i + 2] = (byte)((t[1] >> 5) | ((t[2] & 0x03) << 6));
+                    r[11 * i + 3] = (byte)((t[2] >> 2) & 0xff);
+                    r[11 * i + 4] = (byte)((t[2] >> 10) | ((t[3] & 0x7f) << 1));
+                    r[11 * i + 5] = (byte)((t[3] >> 7) | ((t[4] & 0x0f) << 4));
+                    break;
+                }
+                case 2:
+                {
+                    r[11 * i + 0] = (byte)(t[0] & 0xff);
+                    r[11 * i + 1] = (byte)((t[0] >> 8) | ((t[1] & 0x1f) << 3));
+                    r[11 * i + 2] = (byte)((t[1] >> 5) | ((t[2] & 0x03) << 6));
+                    break;
+                }
+            }
+
+            return r;
+        }
+
+        public override void SqFromBytes(byte[] a)
+        {
+            int n = coeffs.Length;
+            int i;
+            for (i = 0; i < ParameterSet.PackDegree() / 8; i++)
+            {
+                coeffs[8 * i + 0] =
+                    (ushort)(((a[11 * i + 0] & 0xff) >> 0) | (((ushort)(a[11 * i + 1] & 0xff) & 0x07) << 8));
+                coeffs[8 * i + 1] =
+                    (ushort)(((a[11 * i + 1] & 0xff) >> 3) | (((ushort)(a[11 * i + 2] & 0xff) & 0x3f) << 5));
+                coeffs[8 * i + 2] = (ushort)(((a[11 * i + 2] & 0xff) >> 6) |
+                                             (((ushort)(a[11 * i + 3] & 0xff) & 0xff) << 2) |
+                                             (((ushort)(a[11 * i + 4] & 0xff) & 0x01) << 10));
+                coeffs[8 * i + 3] =
+                    (ushort)(((a[11 * i + 4] & 0xff) >> 1) | (((ushort)(a[11 * i + 5] & 0xff) & 0x0f) << 7));
+                coeffs[8 * i + 4] =
+                    (ushort)(((a[11 * i + 5] & 0xff) >> 4) | (((ushort)(a[11 * i + 6] & 0xff) & 0x7f) << 4));
+                coeffs[8 * i + 5] = (ushort)(((a[11 * i + 6] & 0xff) >> 7) |
+                                             (((ushort)(a[11 * i + 7] & 0xff) & 0xff) << 1) |
+                                             (((ushort)(a[11 * i + 8] & 0xff) & 0x03) << 9));
+                coeffs[8 * i + 6] =
+                    (ushort)(((a[11 * i + 8] & 0xff) >> 2) | (((ushort)(a[11 * i + 9] & 0xff) & 0x1f) << 6));
+                coeffs[8 * i + 7] =
+                    (ushort)(((a[11 * i + 9] & 0xff) >> 5) | (((ushort)(a[11 * i + 10] & 0xff) & 0xff) << 3));
+            }
+
+            switch (ParameterSet.PackDegree() & 0x07)
+            {
+                case 4:
+                {
+                    coeffs[8 * i + 0] =
+                        (ushort)(((a[11 * i + 0] & 0xff) >> 0) | (((ushort)(a[11 * i + 1] & 0xff) & 0x07) << 8));
+                    coeffs[8 * i + 1] =
+                        (ushort)(((a[11 * i + 1] & 0xff) >> 3) | (((ushort)(a[11 * i + 2] & 0xff) & 0x3f) << 5));
+                    coeffs[8 * i + 2] = (ushort)(((a[11 * i + 2] & 0xff) >> 6) |
+                                                 (((ushort)(a[11 * i + 3] & 0xff) & 0xff) << 2) |
+                                                 (((ushort)(a[11 * i + 4] & 0xff) & 0x01) << 10));
+                    coeffs[8 * i + 3] =
+                        (ushort)(((a[11 * i + 4] & 0xff) >> 1) | (((ushort)(a[11 * i + 5] & 0xff) & 0x0f) << 7));
+                    break;
+                }
+                case 2:
+                {
+                    coeffs[8 * i + 0] =
+                        (ushort)(((a[11 * i + 0] & 0xff) >> 0) | (((ushort)(a[11 * i + 1] & 0xff) & 0x07) << 8));
+                    coeffs[8 * i + 1] =
+                        (ushort)(((a[11 * i + 1] & 0xff) >> 3) | (((ushort)(a[11 * i + 2] & 0xff) & 0x3f) << 5));
+                    break;
+                }
+            }
+
+            coeffs[n - 1] = 0;
+        }
+
+        public override void Lift(Polynomial a)
+        {
+            int n = coeffs.Length;
+            Array.Copy(a.coeffs, 0, coeffs, 0, n);
+            Z3ToZq();
+        }
+
+        public override void R2Inv(Polynomial a)
+        {
+            HpsPolynomial f = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial g = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial v = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial w = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            R2Inv(a, f, g, v, w);
+        }
+
+        public override void RqInv(Polynomial a)
+        {
+            HpsPolynomial ai2 = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial b = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial c = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial s = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            RqInv(a, ai2, b, c, s);
+        }
+
+        public override void S3Inv(Polynomial a)
+        {
+            HpsPolynomial f = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial g = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial v = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            HpsPolynomial w = new HpsPolynomial((NtruHpsParameterSet)ParameterSet);
+            S3Inv(a, f, g, v, w);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/polynomials/HrssPolynomial.cs b/crypto/src/pqc/crypto/ntru/polynomials/HrssPolynomial.cs
new file mode 100644
index 000000000..c359bf1ad
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/polynomials/HrssPolynomial.cs
@@ -0,0 +1,217 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials
+{
+    internal class HrssPolynomial : Polynomial
+    {
+        internal HrssPolynomial(NtruParameterSet parameterSet) : base(parameterSet)
+        {
+        }
+
+        public override byte[] SqToBytes(int len)
+        {
+            // throw new NotImplementedException();
+
+            byte[] r = new byte[len];
+            short[] t = new short[8];
+
+            int i, j;
+
+            for (i = 0; i < ParameterSet.PackDegree() / 8; i++)
+            {
+                for (j = 0; j < 8; j++)
+                {
+                    t[j] = (short)ModQ((uint)coeffs[8 * i + j] & 0xffff, (uint)ParameterSet.Q());
+                }
+
+                r[13 * i + 0] = (byte)(t[0] & 0xff);
+                r[13 * i + 1] = (byte)((t[0] >> 8) | ((t[1] & 0x07) << 5));
+                r[13 * i + 2] = (byte)((t[1] >> 3) & 0xff);
+                r[13 * i + 3] = (byte)((t[1] >> 11) | ((t[2] & 0x3f) << 2));
+                r[13 * i + 4] = (byte)((t[2] >> 6) | ((t[3] & 0x01) << 7));
+                r[13 * i + 5] = (byte)((t[3] >> 1) & 0xff);
+                r[13 * i + 6] = (byte)((t[3] >> 9) | ((t[4] & 0x0f) << 4));
+                r[13 * i + 7] = (byte)((t[4] >> 4) & 0xff);
+                r[13 * i + 8] = (byte)((t[4] >> 12) | ((t[5] & 0x7f) << 1));
+                r[13 * i + 9] = (byte)((t[5] >> 7) | ((t[6] & 0x03) << 6));
+                r[13 * i + 10] = (byte)((t[6] >> 2) & 0xff);
+                r[13 * i + 11] = (byte)((t[6] >> 10) | ((t[7] & 0x1f) << 3));
+                r[13 * i + 12] = (byte)((t[7] >> 5));
+            }
+
+            for (j = 0; j < ParameterSet.PackDegree() - 8 * i; j++)
+            {
+                t[j] = (short)ModQ((uint)coeffs[8 * i + j] & 0xffff, (uint)ParameterSet.Q());
+            }
+
+            for (; j < 8; j++)
+            {
+                t[j] = 0;
+            }
+
+            switch (ParameterSet.PackDegree() - 8 * (ParameterSet.PackDegree() / 8))
+            {
+                case 4:
+                {
+                    r[13 * i + 0] = (byte)(t[0] & 0xff);
+                    r[13 * i + 1] = (byte)((t[0] >> 8) | ((t[1] & 0x07) << 5));
+                    r[13 * i + 2] = (byte)((t[1] >> 3) & 0xff);
+                    r[13 * i + 3] = (byte)((t[1] >> 11) | ((t[2] & 0x3f) << 2));
+                    r[13 * i + 4] = (byte)((t[2] >> 6) | ((t[3] & 0x01) << 7));
+                    r[13 * i + 5] = (byte)((t[3] >> 1) & 0xff);
+                    r[13 * i + 6] = (byte)((t[3] >> 9) | ((t[4] & 0x0f) << 4));
+                    break;
+                }
+                case 2:
+                {
+                    r[13 * i + 0] = (byte)(t[0] & 0xff);
+                    r[13 * i + 1] = (byte)((t[0] >> 8) | ((t[1] & 0x07) << 5));
+                    r[13 * i + 2] = (byte)((t[1] >> 3) & 0xff);
+                    r[13 * i + 3] = (byte)((t[1] >> 11) | ((t[2] & 0x3f) << 2));
+                    break;
+                }
+            }
+
+            return r;
+        }
+
+
+        public override void SqFromBytes(byte[] a)
+        {
+            // throw new NotImplementedException();
+
+            int i;
+
+            for (i = 0; i < ParameterSet.PackDegree() / 8; i++)
+            {
+                coeffs[8 * i + 0] = (ushort)((a[13 * i + 0] & 0xff) | (((ushort)(a[13 * i + 1] & 0xff) & 0x1f) << 8));
+                coeffs[8 * i + 1] = (ushort)(((a[13 * i + 1] & 0xff) >> 5) | (((ushort)(a[13 * i + 2] & 0xff)) << 3) |
+                                             (((short)(a[13 * i + 3] & 0xff) & 0x03) << 11));
+                coeffs[8 * i + 2] =
+                    (ushort)(((a[13 * i + 3] & 0xff) >> 2) | (((ushort)(a[13 * i + 4] & 0xff) & 0x7f) << 6));
+                coeffs[8 * i + 3] = (ushort)(((a[13 * i + 4] & 0xff) >> 7) | (((ushort)(a[13 * i + 5] & 0xff)) << 1) |
+                                             (((short)(a[13 * i + 6] & 0xff) & 0x0f) << 9));
+                coeffs[8 * i + 4] = (ushort)(((a[13 * i + 6] & 0xff) >> 4) | (((ushort)(a[13 * i + 7] & 0xff)) << 4) |
+                                             (((short)(a[13 * i + 8] & 0xff) & 0x01) << 12));
+                coeffs[8 * i + 5] =
+                    (ushort)(((a[13 * i + 8] & 0xff) >> 1) | (((ushort)(a[13 * i + 9] & 0xff) & 0x3f) << 7));
+                coeffs[8 * i + 6] = (ushort)(((a[13 * i + 9] & 0xff) >> 6) | (((ushort)(a[13 * i + 10] & 0xff)) << 2) |
+                                             (((short)(a[13 * i + 11] & 0xff) & 0x07) << 10));
+                coeffs[8 * i + 7] = (ushort)(((a[13 * i + 11] & 0xff) >> 3) | (((ushort)(a[13 * i + 12] & 0xff)) << 5));
+            }
+
+            switch (ParameterSet.PackDegree() & 0x07)
+            {
+                case 4:
+                {
+                    coeffs[8 * i + 0] =
+                        (ushort)((a[13 * i + 0] & 0xff) | (((short)(a[13 * i + 1] & 0xff) & 0x1f) << 8));
+                    coeffs[8 * i + 1] = (ushort)(((a[13 * i + 1] & 0xff) >> 5) |
+                                                 (((short)(a[13 * i + 2] & 0xff)) << 3) |
+                                                 (((short)(a[13 * i + 3] & 0xff) & 0x03) << 11));
+                    coeffs[8 * i + 2] =
+                        (ushort)(((a[13 * i + 3] & 0xff) >> 2) | (((short)(a[13 * i + 4] & 0xff) & 0x7f) << 6));
+                    coeffs[8 * i + 3] = (ushort)(((a[13 * i + 4] & 0xff) >> 7) |
+                                                 (((short)(a[13 * i + 5] & 0xff)) << 1) |
+                                                 (((short)(a[13 * i + 6] & 0xff) & 0x0f) << 9));
+                    break;
+                }
+                case 2:
+                {
+                    coeffs[8 * i + 0] =
+                        (ushort)((a[13 * i + 0] & 0xff) | (((short)(a[13 * i + 1] & 0xff) & 0x1f) << 8));
+                    coeffs[8 * i + 1] = (ushort)(((a[13 * i + 1] & 0xff) >> 5) |
+                                                 (((short)(a[13 * i + 2] & 0xff)) << 3) |
+                                                 (((short)(a[13 * i + 3] & 0xff) & 0x03) << 11));
+                    break;
+                }
+            }
+
+            coeffs[ParameterSet.N - 1] = 0;
+        }
+
+        public override void Lift(Polynomial a)
+        {
+            int n = coeffs.Length;
+
+            /* NOTE: Assumes input is in {0,1,2}^N */
+            /*       Produces output in [0,Q-1]^N */
+            int i;
+            HrssPolynomial b = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+
+            ushort t, zj;
+
+            /* Define z by <z*x^i, x-1> = delta_{i,0} mod 3:      */
+            /*   t      = -1/N mod p = -N mod 3                   */
+            /*   z[0]   = 2 - t mod 3                             */
+            /*   z[1]   = 0 mod 3                                 */
+            /*   z[j]   = z[j-1] + t mod 3                        */
+            /* We'll compute b = a/(x-1) mod (3, Phi) using       */
+            /*   b[0] = <z, a>, b[1] = <z*x,a>, b[2] = <z*x^2,a>  */
+            /*   b[i] = b[i-3] - (a[i] + a[i-1] + a[i-2])         */
+            t = (ushort)(3 - (n % 3));
+            b.coeffs[0] = (ushort)(a.coeffs[0] * (2 - t) + a.coeffs[1] * 0 + a.coeffs[2] * t);
+            b.coeffs[1] = (ushort)(a.coeffs[1] * (2 - t) + a.coeffs[2] * 0);
+            b.coeffs[2] = (ushort)(a.coeffs[2] * (2 - t));
+
+            zj = 0; /* z[1] */
+            for (i = 3; i < n; i++)
+            {
+                b.coeffs[0] += (ushort)(a.coeffs[i] * (zj + 2 * t));
+                b.coeffs[1] += (ushort)(a.coeffs[i] * (zj + t));
+                b.coeffs[2] += (ushort)(a.coeffs[i] * zj);
+                zj = (ushort)((zj + t) % 3);
+            }
+
+            b.coeffs[1] += (ushort)(a.coeffs[0] * (zj + t));
+            b.coeffs[2] += (ushort)(a.coeffs[0] * zj);
+            b.coeffs[2] += (ushort)(a.coeffs[1] * (zj + t));
+            for (i = 3; i < n; i++)
+            {
+                b.coeffs[i] = (ushort)(b.coeffs[i - 3] + 2 * (a.coeffs[i] + a.coeffs[i - 1] + a.coeffs[i - 2]));
+            }
+
+
+            /* Finish reduction mod Phi by subtracting Phi * b[N-1] */
+            b.Mod3PhiN();
+
+            /* Switch from {0,1,2} to {0,1,q-1} coefficient representation */
+            b.Z3ToZq();
+
+
+            /* Multiply by (x-1) */
+            coeffs[0] = (ushort)-b.coeffs[0];
+            for (i = 0; i < n - 1; i++)
+            {
+                coeffs[i + 1] = (ushort)(b.coeffs[i] - b.coeffs[i + 1]);
+            }
+        }
+
+        public override void R2Inv(Polynomial a)
+        {
+            HrssPolynomial f = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial g = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial v = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial w = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            R2Inv(a, f, g, v, w);
+        }
+
+        public override void RqInv(Polynomial a)
+        {
+            HrssPolynomial ai2 = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial b = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial c = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial s = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            RqInv(a, ai2, b, c, s);
+        }
+
+        public override void S3Inv(Polynomial a)
+        {
+            HrssPolynomial f = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial g = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial v = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            HrssPolynomial w = new HrssPolynomial((NtruHrssParameterSet)ParameterSet);
+            S3Inv(a, f, g, v, w);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntru/polynomials/Polynomial.cs b/crypto/src/pqc/crypto/ntru/polynomials/Polynomial.cs
new file mode 100644
index 000000000..c4db04d7f
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntru/polynomials/Polynomial.cs
@@ -0,0 +1,417 @@
+using Org.BouncyCastle.Pqc.Crypto.Ntru.ParameterSets;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Ntru.Polynomials
+{
+    internal abstract class Polynomial
+    {
+        internal ushort[] coeffs;
+        private protected readonly NtruParameterSet ParameterSet;
+
+        internal Polynomial(NtruParameterSet parameterSet)
+        {
+            coeffs = new ushort[parameterSet.N];
+            ParameterSet = parameterSet;
+        }
+
+        internal static short BothNegativeMask(short x, short y)
+        {
+            return (short)((x & y) >> 15);
+        }
+
+        internal static ushort Mod3(ushort a)
+        {
+            // return (ushort)(a % 3);
+            return Mod(a, 3);
+        }
+
+        internal static byte Mod3(byte a)
+        {
+            // return (byte)(a % 3);
+            return (byte)Mod(a, 3);
+        }
+
+        // Returns a uint since the reference implementation is a define instead of a normal function
+        internal static uint ModQ(uint x, uint q)
+        {
+            // return x % q;
+            return Mod(x, q);
+        }
+
+        // Defined in: poly_mod.c
+        internal void Mod3PhiN()
+        {
+            int n = ParameterSet.N;
+            for (int i = 0; i < n; i++)
+            {
+                coeffs[i] = Mod3((ushort)(coeffs[i] + 2 * coeffs[n - 1]));
+            }
+        }
+
+        internal void ModQPhiN()
+        {
+            int n = ParameterSet.N;
+            for (int i = 0; i < n; i++)
+            {
+                coeffs[i] = (ushort)(coeffs[i] - coeffs[n - 1]);
+            }
+        }
+
+        internal static ushort Mod(double a, double b)
+        {
+            return (ushort)(a - b * System.Math.Floor(a / b));
+        }
+
+        // Pack Sq polynomial as a byte array
+        public abstract byte[] SqToBytes(int len);
+
+        // Unpack a Sq polynomial
+        public abstract void SqFromBytes(byte[] a);
+
+        // Pack a Rq0 polynomial as a byte array
+        public byte[] RqSumZeroToBytes(int len)
+        {
+            return SqToBytes(len);
+        }
+
+        // Unpack a Rq0 polynomial 
+        public void RqSumZeroFromBytes(byte[] a)
+        {
+            int n = coeffs.Length;
+
+            SqFromBytes(a);
+            coeffs[n - 1] = 0;
+            for (int i = 0; i < ParameterSet.PackDegree(); i++)
+            {
+                coeffs[n - 1] -= coeffs[i];
+            }
+        }
+
+        // Pack an S3 polynomial as a byte array
+        public byte[] S3ToBytes(int messageSize)
+        {
+            byte[] msg = new byte[messageSize];
+            byte c;
+
+            for (int i = 0; i < ParameterSet.PackDegree() / 5; i++)
+            {
+                c = (byte)(coeffs[5 * i + 4] & 255);
+                c = (byte)(3 * c + coeffs[5 * i + 3] & 255);
+                c = (byte)(3 * c + coeffs[5 * i + 2] & 255);
+                c = (byte)(3 * c + coeffs[5 * i + 1] & 255);
+                c = (byte)(3 * c + coeffs[5 * i + 0] & 255);
+                msg[i] = c;
+            }
+
+            if (ParameterSet.PackDegree() > (ParameterSet.PackDegree() / 5) * 5)
+            {
+                int i = ParameterSet.PackDegree() / 5;
+                c = 0;
+
+                for (int j = ParameterSet.PackDegree() - (5 * i) - 1; j >= 0; j--)
+                {
+                    c = (byte)(3 * c + coeffs[5 * i + j] & 255);
+                }
+
+                msg[i] = c;
+            }
+
+            return msg;
+        }
+
+        // Unpack an S3 polynomial
+        public void S3FromBytes(byte[] msg)
+        {
+            int n = coeffs.Length;
+            byte c;
+
+            for (int i = 0; i < ParameterSet.PackDegree() / 5; i++)
+            {
+                c = msg[i];
+                coeffs[5 * i + 0] = c;
+                coeffs[5 * i + 1] = (ushort)(c * 171 >> 9);
+                coeffs[5 * i + 2] = (ushort)(c * 57 >> 9);
+                coeffs[5 * i + 3] = (ushort)(c * 19 >> 9);
+                coeffs[5 * i + 4] = (ushort)(c * 203 >> 14);
+            }
+
+            if (ParameterSet.PackDegree() > (ParameterSet.PackDegree() / 5) * 5)
+            {
+                int i = ParameterSet.PackDegree() / 5;
+                c = msg[i];
+                for (int j = 0; (5 * i + j) < ParameterSet.PackDegree(); j++)
+                {
+                    coeffs[5 * i + j] = c;
+                    c = (byte)(c * 171 >> 9);
+                }
+            }
+
+            coeffs[n - 1] = 0;
+            Mod3PhiN();
+        }
+
+        // Defined in: poly_rq_mul.c
+        public void RqMul(Polynomial a, Polynomial b)
+        {
+            int n = coeffs.Length;
+            int k, i;
+
+            for (k = 0; k < n; k++)
+            {
+                coeffs[k] = 0;
+                for (i = 1; i < n - k; i++)
+                {
+                    coeffs[k] += (ushort)(a.coeffs[k + i] * b.coeffs[n - i]);
+                }
+
+                for (i = 0; i < k + 1; i++)
+                {
+                    coeffs[k] += (ushort)(a.coeffs[k - i] * b.coeffs[i]);
+                }
+            }
+        }
+
+
+        // Defined in: poly.c
+        public void SqMul(Polynomial a, Polynomial b)
+        {
+            RqMul(a, b);
+            ModQPhiN();
+        }
+
+
+        // Defined in: 
+        public void S3Mul(Polynomial a, Polynomial b)
+        {
+            RqMul(a, b);
+            Mod3PhiN();
+        }
+
+        public abstract void Lift(Polynomial a);
+
+        public void RqToS3(Polynomial a)
+        {
+            int n = coeffs.Length;
+            ushort flag;
+
+            for (int i = 0; i < n; i++)
+            {
+                coeffs[i] = (ushort)ModQ(a.coeffs[i], (uint)ParameterSet.Q());
+                //Console.Write(a.coeffs[i].ToString("X2"));
+                flag = (ushort)(coeffs[i] >> ParameterSet.LogQ - 1);
+                coeffs[i] += (ushort)(flag << (1 - (ParameterSet.LogQ & 1)));
+            }
+            //Console.WriteLine();
+
+            Mod3PhiN();
+        }
+
+        public abstract void R2Inv(Polynomial a);
+
+        internal void R2Inv(Polynomial a, Polynomial f, Polynomial g, Polynomial v, Polynomial w)
+        {
+            int n = coeffs.Length;
+            int i, loop;
+            short delta, sign, swap, t;
+
+            w.coeffs[0] = 1;
+
+            for (i = 0; i < n; ++i)
+            {
+                f.coeffs[i] = 1;
+            }
+
+            for (i = 0; i < n - 1; ++i)
+            {
+                g.coeffs[n - 2 - i] = (ushort)((a.coeffs[i] ^ a.coeffs[n - 1]) & 1);
+            }
+
+            g.coeffs[n - 1] = 0;
+
+            delta = 1;
+
+            for (loop = 0; loop < 2 * (n - 1) - 1; ++loop)
+            {
+                for (i = n - 1; i > 0; --i)
+                {
+                    v.coeffs[i] = v.coeffs[i - 1];
+                }
+
+                v.coeffs[0] = 0;
+
+                sign = (short)(g.coeffs[0] & f.coeffs[0]);
+                swap = BothNegativeMask((short)-delta, (short)-g.coeffs[0]);
+                delta ^= (short)(swap & (delta ^ -delta));
+                delta++;
+
+                for (i = 0; i < n; ++i)
+                {
+                    t = (short)(swap & (f.coeffs[i] ^ g.coeffs[i]));
+                    f.coeffs[i] ^= (ushort)t;
+                    g.coeffs[i] ^= (ushort)t;
+                    t = (short)(swap & (v.coeffs[i] ^ w.coeffs[i]));
+                    v.coeffs[i] ^= (ushort)t;
+                    w.coeffs[i] ^= (ushort)t;
+                }
+
+                for (i = 0; i < n; ++i)
+                {
+                    g.coeffs[i] = (ushort)(g.coeffs[i] ^ (sign & f.coeffs[i]));
+                }
+
+                for (i = 0; i < n; ++i)
+                {
+                    w.coeffs[i] = (ushort)(w.coeffs[i] ^ (sign & v.coeffs[i]));
+                }
+
+                for (i = 0; i < n - 1; ++i)
+                {
+                    g.coeffs[i] = g.coeffs[i + 1];
+                }
+
+                g.coeffs[n - 1] = 0;
+            }
+
+            for (i = 0; i < n - 1; ++i)
+            {
+                coeffs[i] = v.coeffs[n - 2 - i];
+            }
+
+            coeffs[n - 1] = 0;
+        }
+
+        public abstract void RqInv(Polynomial a);
+
+        internal void RqInv(Polynomial a, Polynomial ai2, Polynomial b, Polynomial c, Polynomial s)
+        {
+            ai2.R2Inv(a);
+            R2InvToRqInv(ai2, a, b, c, s);
+        }
+
+        private void R2InvToRqInv(Polynomial ai, Polynomial a, Polynomial b, Polynomial c, Polynomial s)
+        {
+            int n = coeffs.Length;
+            int i;
+
+            for (i = 0; i < n; i++)
+            {
+                b.coeffs[i] = (ushort)-a.coeffs[i];
+            }
+
+            for (i = 0; i < n; i++)
+            {
+                coeffs[i] = ai.coeffs[i];
+            }
+
+            c.RqMul(this, b);
+            c.coeffs[0] += 2;
+            s.RqMul(c, this);
+
+            c.RqMul(s, b);
+            c.coeffs[0] += 2;
+            RqMul(c, s);
+
+            c.RqMul(this, b);
+            c.coeffs[0] += 2;
+            s.RqMul(c, this);
+
+            c.RqMul(s, b);
+            c.coeffs[0] += 2;
+            RqMul(c, s);
+        }
+
+
+        public abstract void S3Inv(Polynomial a);
+
+        internal void S3Inv(Polynomial a, Polynomial f, Polynomial g, Polynomial v, Polynomial w)
+        {
+            int n = coeffs.Length;
+            int i, loop;
+            short delta, sign, swap, t;
+
+            w.coeffs[0] = 1;
+
+            for (i = 0; i < n; ++i)
+            {
+                f.coeffs[i] = 1;
+            }
+
+            for (i = 0; i < n - 1; ++i)
+            {
+                g.coeffs[n - 2 - i] = Mod3((ushort)((a.coeffs[i] & 3) + 2 * (a.coeffs[n - 1] & 3)));
+            }
+
+            g.coeffs[n - 1] = 0;
+
+            delta = 1;
+
+            for (loop = 0; loop < 2 * (n - 1) - 1; ++loop)
+            {
+                for (i = n - 1; i > 0; --i)
+                {
+                    v.coeffs[i] = v.coeffs[i - 1];
+                }
+
+                v.coeffs[0] = 0;
+
+                sign = Mod3((byte)(2 * g.coeffs[0] * f.coeffs[0]));
+                swap = BothNegativeMask((short)-delta, (short)-g.coeffs[0]);
+                delta ^= (short)(swap & (delta ^ -delta));
+                delta++;
+
+                for (i = 0; i < n; ++i)
+                {
+                    t = (short)(swap & (f.coeffs[i] ^ g.coeffs[i]));
+                    f.coeffs[i] ^= (ushort)t;
+                    g.coeffs[i] ^= (ushort)t;
+                    t = (short)(swap & (v.coeffs[i] ^ w.coeffs[i]));
+                    v.coeffs[i] ^= (ushort)t;
+                    w.coeffs[i] ^= (ushort)t;
+                }
+
+                for (i = 0; i < n; ++i)
+                {
+                    g.coeffs[i] = Mod3((byte)(g.coeffs[i] + sign * f.coeffs[i]));
+                }
+
+                for (i = 0; i < n; ++i)
+                {
+                    w.coeffs[i] = Mod3((byte)(w.coeffs[i] + sign * v.coeffs[i]));
+                }
+
+                for (i = 0; i < n - 1; ++i)
+                {
+                    g.coeffs[i] = g.coeffs[i + 1];
+                }
+
+                g.coeffs[n - 1] = 0;
+            }
+
+            sign = (short)f.coeffs[0];
+            for (i = 0; i < n - 1; ++i)
+            {
+                coeffs[i] = Mod3((byte)(sign * v.coeffs[n - 2 - i]));
+            }
+
+            coeffs[n - 1] = 0;
+        }
+
+        public void Z3ToZq()
+        {
+            int n = coeffs.Length;
+            for (int i = 0; i < n; i++)
+            {
+                coeffs[i] = (ushort)(coeffs[i] | (-(coeffs[i] >> 1) & (ParameterSet.Q() - 1)));
+            }
+        }
+
+        public void TrinaryZqToZ3()
+        {
+            int n = coeffs.Length;
+            for (int i = 0; i < n; i++)
+            {
+                coeffs[i] = (ushort)ModQ((uint)(coeffs[i] & 0xffff), (uint)ParameterSet.Q());
+                coeffs[i] = (ushort)(3 & (coeffs[i] ^ (coeffs[i] >> (ParameterSet.LogQ - 1))));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemExtractor.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemExtractor.cs
new file mode 100644
index 000000000..57dce2438
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemExtractor.cs
@@ -0,0 +1,30 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class NtruLPRimeKemExtractor : IEncapsulatedSecretExtractor
+    {
+        private NtruPrimeEngine _primeEngine;
+        private readonly NtruLPRimeKeyParameters _primeKey;
+
+        public NtruLPRimeKemExtractor(NtruLPRimeKeyParameters privParams)
+        {
+            this._primeKey = privParams;
+            InitCipher(_primeKey.Parameters);
+        }
+
+        private void InitCipher(NtruLPRimeParameters param)
+        {
+            _primeEngine = param.PrimeEngine;
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            byte[] session_key = new byte[_primeEngine.SessionKeySize];
+            _primeEngine.kem_dec(session_key, encapsulation, ((NtruLPRimePrivateKeyParameters)_primeKey).privKey);
+            return session_key;
+        }
+
+        public int EncapsulationLength => _primeEngine.CipherTextSize;
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKemGenerator.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemGenerator.cs
index 9c2e0461b..d7c52e357 100644
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKemGenerator.cs
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKemGenerator.cs
@@ -5,23 +5,23 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
 {
-    public class NtruPrimeKemGenerator : IEncapsulatedSecretGenerator
+    public class NtruLPRimeKemGenerator : IEncapsulatedSecretGenerator
     {
         private SecureRandom sr;
         
-        public NtruPrimeKemGenerator(SecureRandom sr)
+        public NtruLPRimeKemGenerator(SecureRandom sr)
         {
             this.sr = sr;
         }
 
         public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
         {
-            NtruPrimePublicKeyParameters key = (NtruPrimePublicKeyParameters)recipientKey;
-            NtruPrimeEngine pEngine = key.Parameters.PEngine;
-            byte[] cipherText = new byte[pEngine.CipherTextSize];
-            byte[] sessionKey = new byte[pEngine.SessionKeySize];
-            pEngine.kem_enc(cipherText, sessionKey,key.pubKey, sr);
-            return new NtruPrimeKemGenerator.SecretWithEncapsulationImpl(sessionKey, cipherText);
+            NtruLPRimePublicKeyParameters key = (NtruLPRimePublicKeyParameters)recipientKey;
+            NtruPrimeEngine primeEngine = key.Parameters.PrimeEngine;
+            byte[] cipherText = new byte[primeEngine.CipherTextSize];
+            byte[] sessionKey = new byte[primeEngine.SessionKeySize];
+            primeEngine.kem_enc(cipherText, sessionKey,key.pubKey, sr);
+            return new NtruLPRimeKemGenerator.SecretWithEncapsulationImpl(sessionKey, cipherText);
         }
 
         public class SecretWithEncapsulationImpl : ISecretWithEncapsulation
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyGenerationParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyGenerationParameters.cs
new file mode 100644
index 000000000..cc9264f7f
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyGenerationParameters.cs
@@ -0,0 +1,18 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class NtruLPRimeKeyGenerationParameters : KeyGenerationParameters
+    {
+        private NtruLPRimeParameters _primeParameters;
+        
+        public NtruLPRimeKeyGenerationParameters(SecureRandom random, NtruLPRimeParameters ntruPrimeParameters) : base(random,256)
+        {
+            this._primeParameters = ntruPrimeParameters;
+        }
+
+        public NtruLPRimeParameters Parameters => _primeParameters;
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyPairGenerator.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyPairGenerator.cs
new file mode 100644
index 000000000..83e2f24ac
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyPairGenerator.cs
@@ -0,0 +1,49 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class NtruLPRimeKeyPairGenerator
+    {
+        private NtruLPRimeKeyGenerationParameters _ntruPrimeParams;
+
+        private int p;
+        private int q;
+
+        private SecureRandom random;
+
+        private void Initialize(KeyGenerationParameters param)
+        {
+            _ntruPrimeParams = (NtruLPRimeKeyGenerationParameters) param;
+            random = param.Random;
+
+            // n = ntruParams.Parameters.N;
+
+            p = _ntruPrimeParams.Parameters.P;
+            q = _ntruPrimeParams.Parameters.Q;
+
+        }
+
+        private AsymmetricCipherKeyPair GenKeyPair()
+        {
+            NtruPrimeEngine primeEngine = _ntruPrimeParams.Parameters.PrimeEngine;
+            byte[] sk = new byte[primeEngine.PrivateKeySize];
+            byte[] pk = new byte[primeEngine.PublicKeySize];
+            primeEngine.kem_keypair( pk,sk,random);
+
+            NtruLPRimePublicKeyParameters pubKey = new NtruLPRimePublicKeyParameters(_ntruPrimeParams.Parameters, pk);
+            NtruLPRimePrivateKeyParameters privKey = new NtruLPRimePrivateKeyParameters(_ntruPrimeParams.Parameters, sk);
+            return new AsymmetricCipherKeyPair(pubKey, privKey);
+        }
+        
+        public void Init(KeyGenerationParameters param)
+        {
+            this.Initialize(param);
+        }
+        
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            return GenKeyPair();
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyParameters.cs
new file mode 100644
index 000000000..da38bf4d8
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeKeyParameters.cs
@@ -0,0 +1,17 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class NtruLPRimeKeyParameters : AsymmetricKeyParameter
+    {
+        private NtruLPRimeParameters _primeParameters;
+        
+        public NtruLPRimeKeyParameters(bool isPrivate, NtruLPRimeParameters primeParameters) : base(isPrivate)
+        {
+            this._primeParameters = primeParameters;
+        }
+
+        public NtruLPRimeParameters Parameters => _primeParameters;
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruLPRimeParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeParameters.cs
new file mode 100644
index 000000000..eac349267
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimeParameters.cs
@@ -0,0 +1,67 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public sealed class NtruLPRimeParameters
+        : ICipherParameters
+    {
+        private string name;
+        private int p;
+        private int q;
+        private int _roundedBytes;
+        private bool m_LPR;
+        private int _w;
+        private int _rqBytes;
+        private int _tau0;
+        private int _tau1;
+        private int _tau2;
+        private int _tau3;
+        private int _skBytes;
+        private int _pkBytes;
+        private int _ctBytes;
+        private int _defaultKeySize;
+        private NtruPrimeEngine _primeEngine;
+        
+        private NtruLPRimeParameters(string name, int p, int q, bool LPR, int w, int tau0,
+            int tau1, int tau2, int tau3, int skBytes, int pkBytes, int ctBytes, int roundedBytes, int rqBytes, int defaultKeySize)
+        {
+            this.name = name;
+            this.p = p;
+            this.q = q;
+            m_LPR = LPR;
+            this._w = w;
+            this._tau0 = tau0;
+            this._tau1 = tau1;
+            this._tau2 = tau2;
+            this._tau3 = tau3;
+            
+            // KEM Parameters
+            this._roundedBytes = roundedBytes;
+            this._rqBytes = rqBytes;
+            this._skBytes = skBytes;
+            this._pkBytes = pkBytes;
+            this._ctBytes = ctBytes;
+            this._primeEngine = new NtruPrimeEngine(p,q, LPR, w, tau0, tau1, tau2, tau3, skBytes, pkBytes, ctBytes, roundedBytes, rqBytes, defaultKeySize / 8);
+            this._defaultKeySize = defaultKeySize;
+        }
+
+        public static NtruLPRimeParameters ntrulpr653 = new NtruLPRimeParameters("NTRU_LPRime_653", 653, 4621, true, 252, 2175,113,2031,290,1125,897,1025, 865, -1, 128);
+        public static NtruLPRimeParameters ntrulpr761 = new NtruLPRimeParameters("NTRU_LPRime_761", 761, 4591, true, 250, 2156,114,2007,287,1294,1039,1167, 1007, -1, 128);
+        public static NtruLPRimeParameters ntrulpr857 = new NtruLPRimeParameters("NTRU_LPRime_857", 857, 5167, true, 281, 2433,101,2265,324,1463,1184,1312, 1152, -1, 128);
+        public static NtruLPRimeParameters ntrulpr953 = new NtruLPRimeParameters("NTRU_LPRime_953", 953, 6343, true, 345, 2997,82,2798,400,1652,1349,1477, 1317, -1, 192);
+        public static NtruLPRimeParameters ntrulpr1013 = new NtruLPRimeParameters("NTRU_LPRime_1013", 1013, 7177, true, 392, 3367,73,3143,449,1773,1455,1583, 1423, -1, 192);
+        public static NtruLPRimeParameters ntrulpr1277 = new NtruLPRimeParameters("NTRU_LPRime_1277", 1277, 7879, true, 429, 3724,66,3469,496,2231,1847,1975, 1815, -1, 256);
+
+        public int P => p;
+
+        public bool LPR => m_LPR;
+
+        public int Q => q;
+
+        public int DefaultKeySize => _defaultKeySize;
+
+        internal NtruPrimeEngine PrimeEngine => _primeEngine;
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimePrivateKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimePrivateKeyParameters.cs
index 69cfc4744..1164ab197 100644
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimePrivateKeyParameters.cs
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimePrivateKeyParameters.cs
@@ -3,18 +3,23 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
 {
-    public class NtruPrimePrivateKeyParameters : NtruPrimeKeyParameters
+    public class NtruLPRimePrivateKeyParameters : NtruLPRimeKeyParameters
     {
         internal byte[] privKey;
 
-        public NtruPrimePrivateKeyParameters(NtruPrimeParameters pParameters, byte[] privKey) : base(true, pParameters)
+        public byte[] GetPrivateKey()
+        {
+            return Arrays.Clone(privKey);
+        }
+
+        public NtruLPRimePrivateKeyParameters(NtruLPRimeParameters primeParameters, byte[] privKey) : base(true, primeParameters)
         {
             this.privKey = Arrays.Clone(privKey);
         }
         
         public byte[] GetEncoded()
         {
-            return Arrays.Clone(privKey);
+            return GetPrivateKey();
         }
     }
 }
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimePublicKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruLPRimePublicKeyParameters.cs
index 265382455..9566165d1 100644
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimePublicKeyParameters.cs
+++ b/crypto/src/pqc/crypto/ntruprime/NtruLPRimePublicKeyParameters.cs
@@ -2,16 +2,21 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
 {
-    public class NtruPrimePublicKeyParameters : NtruPrimeKeyParameters
+    public class NtruLPRimePublicKeyParameters : NtruLPRimeKeyParameters
     {
         internal byte[] pubKey;
 
-        public byte[] GetEncoded()
+        public byte[] GetPublicKey()
         {
             return Arrays.Clone(pubKey);
         }
 
-        public NtruPrimePublicKeyParameters(NtruPrimeParameters pParameters, byte[] pubKey) : base(false,pParameters)
+        public byte[] GetEncoded()
+        {
+            return GetPublicKey();
+        }
+
+        public NtruLPRimePublicKeyParameters(NtruLPRimeParameters primeParameters, byte[] pubKey) : base(false,primeParameters)
         {
             this.pubKey = Arrays.Clone(pubKey);
         }
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeEngine.cs b/crypto/src/pqc/crypto/ntruprime/NtruPrimeEngine.cs
index 701a53cee..64e4e19fc 100644
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeEngine.cs
+++ b/crypto/src/pqc/crypto/ntruprime/NtruPrimeEngine.cs
@@ -28,7 +28,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
         private readonly int _smallBytes;
         private readonly int _hashBytes;
         
-        private const int SessionKeyBytes = 32;
+        private readonly int SessionKeyBytes;
 
         // Parameters for NTRU
         private readonly int _p;
@@ -54,7 +54,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
         public int SessionKeySize => SessionKeyBytes;
         
         public NtruPrimeEngine(int p, int q, bool lpr, int w, int tau0,
-            int tau1, int tau2, int tau3, int skBytes, int pkBytes, int ctBytes, int roundedBytes, int rqBytes)
+            int tau1, int tau2, int tau3, int skBytes, int pkBytes, int ctBytes, int roundedBytes, int rqBytes, int defaultKeyLen)
         {
             this._p = p;
             this._q = q;
@@ -73,6 +73,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
             this._lpr = lpr;
 
             this._confirmBytes = 32;
+            this.SessionKeyBytes = defaultKeyLen;
 
             _smallBytes = ((p + 3) / 4);
             _q12 = ((q - 1) / 2);
@@ -915,7 +916,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
             uint[] L_uint = new uint[_p];
 
             // AES256 CTR
-            BufferedBlockCipher cipher = new BufferedBlockCipher(new SicBlockCipher(new AesEngine()));
+            BufferedBlockCipher cipher = new BufferedBlockCipher(new SicBlockCipher(AesUtilities.CreateEngine()));
             KeyParameter kp = new KeyParameter(k);
             cipher.Init(true, new ParametersWithIV(kp, new byte[16]));
             int len = cipher.ProcessBytes(cipherInput, 0, 4 * _p, L, 0);
@@ -1233,7 +1234,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
                 }
             }
 
-            HashPrefix(ref output, b, ref x, x.Length);
+            byte[] hash = new byte[32];
+            HashPrefix(ref hash, b, ref x, x.Length);
+            Array.Copy(hash, 0, output, 0, output.Length);
         }
         
         private int NegativeMask(short x)
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKemExtractor.cs b/crypto/src/pqc/crypto/ntruprime/NtruPrimeKemExtractor.cs
deleted file mode 100644
index 7ae67db50..000000000
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKemExtractor.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Pqc.Crypto.Frodo;
-
-namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
-{
-    public class NtruPrimeKemExtractor : IEncapsulatedSecretExtractor
-    {
-        private NtruPrimeEngine _pEngine;
-        private NtruPrimeKeyParameters _pKey;
-
-        public NtruPrimeKemExtractor(NtruPrimeKeyParameters privParams)
-        {
-            this._pKey = privParams;
-            InitCipher(_pKey.Parameters);
-        }
-
-        private void InitCipher(NtruPrimeParameters param)
-        {
-            _pEngine = param.PEngine;
-        }
-
-        public byte[] ExtractSecret(byte[] encapsulation)
-        {
-            byte[] session_key = new byte[_pEngine.SessionKeySize];
-            _pEngine.kem_dec(session_key, encapsulation, ((NtruPrimePrivateKeyParameters)_pKey).privKey);
-            return session_key;
-        }
-
-        public int GetInputSize()
-        {
-            return _pEngine.CipherTextSize;
-        }
-        
-    }
-}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyGenerationParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyGenerationParameters.cs
deleted file mode 100644
index 56e7315ae..000000000
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyGenerationParameters.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-
-namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
-{
-    public class NtruKeyGenerationParameters : KeyGenerationParameters
-    {
-        private NtruPrimeParameters _pParameters;
-        
-        public NtruKeyGenerationParameters(SecureRandom random, NtruPrimeParameters ntruPParameters) : base(random,256)
-        {
-            this._pParameters = ntruPParameters;
-        }
-
-        public NtruPrimeParameters PParameters => _pParameters;
-
-    }
-}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyPairGenerator.cs b/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyPairGenerator.cs
deleted file mode 100644
index eff4828f8..000000000
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyPairGenerator.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-
-namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
-{
-    public class NtruKeyPairGenerator
-    {
-        private NtruKeyGenerationParameters ntruParams;
-
-        private int p;
-        private int q;
-
-        private SecureRandom random;
-
-        private void Initialize(KeyGenerationParameters param)
-        {
-            ntruParams = (NtruKeyGenerationParameters) param;
-            random = param.Random;
-
-            // n = ntruParams.Parameters.N;
-
-            p = ntruParams.PParameters.P;
-            q = ntruParams.PParameters.Q;
-
-        }
-
-        private AsymmetricCipherKeyPair GenKeyPair()
-        {
-            NtruPrimeEngine pEngine = ntruParams.PParameters.PEngine;
-            byte[] sk = new byte[pEngine.PrivateKeySize];
-            byte[] pk = new byte[pEngine.PublicKeySize];
-            pEngine.kem_keypair( pk,sk,random);
-
-            NtruPrimePublicKeyParameters pubKey = new NtruPrimePublicKeyParameters(ntruParams.PParameters, pk);
-            NtruPrimePrivateKeyParameters privKey = new NtruPrimePrivateKeyParameters(ntruParams.PParameters, sk);
-            return new AsymmetricCipherKeyPair(pubKey, privKey);
-        }
-        
-        public void Init(KeyGenerationParameters param)
-        {
-            this.Initialize(param);
-        }
-        
-        public AsymmetricCipherKeyPair GenerateKeyPair()
-        {
-            return GenKeyPair();
-        }
-        
-        // private AsymmetricCipherKeyPair GenKeyPair()
-        // {
-        //     NtruEngine engine = ntruParams.Parameters.Engine;
-        //     byte[] sk = new byte[engine.PrivateKeySize];
-        //     byte[] pk = new byte[engine.PublicKeySize];
-        //     
-        //     
-        // }
-        //
-        // public void Init(KeyGenerationParameters param)
-        // {
-        //     this.Initialize(param);
-        // }
-        //
-        // public AsymmetricCipherKeyPair GenerateKeyPair()
-        // {
-        //     return GenKeyPair();
-        // }
-
-    }
-}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyParameters.cs
deleted file mode 100644
index fb77d8567..000000000
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeKeyParameters.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Org.BouncyCastle.Crypto;
-
-namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
-{
-    public class NtruPrimeKeyParameters : AsymmetricKeyParameter
-    {
-        private NtruPrimeParameters _pParameters;
-        
-        public NtruPrimeKeyParameters(bool isPrivate, NtruPrimeParameters pParameters) : base(isPrivate)
-        {
-            this._pParameters = pParameters;
-        }
-
-        public NtruPrimeParameters Parameters => _pParameters;
-
-    }
-}
diff --git a/crypto/src/pqc/crypto/ntruprime/NtruPrimeParameters.cs b/crypto/src/pqc/crypto/ntruprime/NtruPrimeParameters.cs
deleted file mode 100644
index 3cf691565..000000000
--- a/crypto/src/pqc/crypto/ntruprime/NtruPrimeParameters.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System;
-using System.ComponentModel;
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Digests;
-using Org.BouncyCastle.Crypto.Modes;
-
-namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
-{
-    public class NtruPrimeParameters : ICipherParameters
-    {
-
-        private String name;
-        private int p;
-        private int q;
-        private int _roundedBytes;
-        private bool LPR;
-        private int _w;
-        private int _rqBytes;
-        private int _tau0;
-        private int _tau1;
-        private int _tau2;
-        private int _tau3;
-        private int _skBytes;
-        private int _pkBytes;
-        private int _ctBytes;
-        private NtruPrimeEngine _pEngine;
-        
-        public NtruPrimeParameters(String name, int p, int q, bool LPR, int w, int tau0,
-            int tau1, int tau2, int tau3, int skBytes, int pkBytes, int ctBytes, int roundedBytes, int rqBytes)
-        {
-            this.name = name;
-            this.p = p;
-            this.q = q;
-            this.LPR = LPR;
-            this._w = w;
-            this._tau0 = tau0;
-            this._tau1 = tau1;
-            this._tau2 = tau2;
-            this._tau3 = tau3;
-            
-            // KEM Parameters
-            this._roundedBytes = roundedBytes;
-            this._rqBytes = rqBytes;
-            this._skBytes = skBytes;
-            this._pkBytes = pkBytes;
-            this._ctBytes = ctBytes;
-            this._pEngine = new NtruPrimeEngine(p,q, LPR, w, tau0, tau1, tau2, tau3, skBytes, pkBytes, ctBytes, roundedBytes, rqBytes);
-        }
-
-        public static NtruPrimeParameters ntrulpr653 = new NtruPrimeParameters("NTRU_LPRime_653", 653, 4621, true, 252, 2175,113,2031,290,1125,897,1025, 865, -1);
-        public static NtruPrimeParameters ntrulpr761 = new NtruPrimeParameters("NTRU_LPRime_761", 761, 4591, true, 250, 2156,114,2007,287,1294,1039,1167, 1007, -1);
-        public static NtruPrimeParameters ntrulpr857 = new NtruPrimeParameters("NTRU_LPRime_857", 857, 5167, true, 281, 2433,101,2265,324,1463,1184,1312, 1152, -1);
-        public static NtruPrimeParameters ntrulpr953 = new NtruPrimeParameters("NTRU_LPRime_953", 953, 6343, true, 345, 2997,82,2798,400,1652,1349,1477, 1317, -1);
-        public static NtruPrimeParameters ntrulpr1013 = new NtruPrimeParameters("NTRU_LPRime_1013", 1013, 7177, true, 392, 3367,73,3143,449,1773,1455,1583, 1423, -1);
-        public static NtruPrimeParameters ntrulpr1277 = new NtruPrimeParameters("NTRU_LPRime_1277", 1277, 7879, true, 429, 3724,66,3469,496,2231,1847,1975, 1815, -1);
-        
-        public static NtruPrimeParameters sntrup653 = new NtruPrimeParameters("SNTRU_Prime_653", 653, 4621, false, 288, -1,-1,-1,-1,1518,994,897, 865, 994);
-        public static NtruPrimeParameters sntrup761 = new NtruPrimeParameters("SNTRU_Prime_761", 761, 4591, false, 286, -1,-1,-1,-1,1763,1158,1039, 1007, 1158);
-        public static NtruPrimeParameters sntrup857 = new NtruPrimeParameters("SNTRU_Prime_857", 857, 5167, false, 322, -1,-1,-1,-1,1999,1322,1184, 1152, 1322);
-        public static NtruPrimeParameters sntrup953 = new NtruPrimeParameters("SNTRU_Prime_953", 953, 6343, false, 396, -1,-1,-1,-1,2254,1505,1349, 1317, 1505);
-        public static NtruPrimeParameters sntrup1013 = new NtruPrimeParameters("SNTRU_Prime_1013", 1013, 7177, false, 448, -1,-1,-1,-1,2417,1623,1455, 1423, 1623);
-        public static NtruPrimeParameters sntrup1277 = new NtruPrimeParameters("SNTRU_Prime_1277", 1277, 7879, false, 492, -1,-1,-1,-1,3059,2067,1847, 1815, 2067);
-        
-        public int P => p;
-        public bool lpr => LPR;
-        
-        public int Q => q;
-
-        internal NtruPrimeEngine PEngine => _pEngine;
-
-    }
-}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemExtractor.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemExtractor.cs
new file mode 100644
index 000000000..2229599e1
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemExtractor.cs
@@ -0,0 +1,30 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimeKemExtractor : IEncapsulatedSecretExtractor
+    {
+        private NtruPrimeEngine _primeEngine;
+        private readonly SNtruPrimeKeyParameters _primeKey;
+
+        public SNtruPrimeKemExtractor(SNtruPrimeKeyParameters privParams)
+        {
+            this._primeKey = privParams;
+            InitCipher(_primeKey.Parameters);
+        }
+
+        private void InitCipher(SNtruPrimeParameters param)
+        {
+            _primeEngine = param.PrimeEngine;
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            byte[] session_key = new byte[_primeEngine.SessionKeySize];
+            _primeEngine.kem_dec(session_key, encapsulation, ((SNtruPrimePrivateKeyParameters)_primeKey).privKey);
+            return session_key;
+        }
+
+        public int EncapsulationLength => _primeEngine.CipherTextSize;
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemGenerator.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemGenerator.cs
new file mode 100644
index 000000000..43ca38b09
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKemGenerator.cs
@@ -0,0 +1,77 @@
+using System;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimeKemGenerator : IEncapsulatedSecretGenerator
+    {
+        private SecureRandom sr;
+        
+        public SNtruPrimeKemGenerator(SecureRandom sr)
+        {
+            this.sr = sr;
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            SNtruPrimePublicKeyParameters key = (SNtruPrimePublicKeyParameters)recipientKey;
+            NtruPrimeEngine primeEngine = key.Parameters.PrimeEngine;
+            byte[] cipherText = new byte[primeEngine.CipherTextSize];
+            byte[] sessionKey = new byte[primeEngine.SessionKeySize];
+            primeEngine.kem_enc(cipherText, sessionKey,key.pubKey, sr);
+            return new NtruLPRimeKemGenerator.SecretWithEncapsulationImpl(sessionKey, cipherText);
+        }
+
+        public class SecretWithEncapsulationImpl : ISecretWithEncapsulation
+        {
+            private volatile bool hasBeenDestroyed = false;
+            
+            private byte[] sessionKey;
+            private byte[] cipherText;
+            
+            public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipherText)
+            {
+                this.sessionKey = sessionKey;
+                this.cipherText = cipherText;
+            }
+
+            public byte[] GetSecret()
+            {
+                CheckDestroyed();
+                return Arrays.Clone(sessionKey);
+            }
+
+            public byte[] GetEncapsulation()
+            {
+                return Arrays.Clone(cipherText);
+            }
+
+            public void Dispose()
+            {
+                if (!hasBeenDestroyed)
+                {
+                    hasBeenDestroyed = true;
+                    Arrays.Clear(sessionKey);
+                    Arrays.Clear(cipherText);
+                }
+            }
+            
+            public bool IsDestroyed()
+            {
+                return hasBeenDestroyed;
+            }
+            
+            void CheckDestroyed()
+            {
+                if (IsDestroyed())
+                {
+                    throw new Exception("data has been destroyed");
+                }
+            }
+            
+        }
+        
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyGenerationParameters.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyGenerationParameters.cs
new file mode 100644
index 000000000..cd4f37893
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyGenerationParameters.cs
@@ -0,0 +1,18 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimeKeyGenerationParameters : KeyGenerationParameters
+    {
+        private SNtruPrimeParameters _primeParameters;
+        
+        public SNtruPrimeKeyGenerationParameters(SecureRandom random, SNtruPrimeParameters ntruPrimeParameters) : base(random,256)
+        {
+            this._primeParameters = ntruPrimeParameters;
+        }
+
+        public SNtruPrimeParameters Parameters => _primeParameters;
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyPairGenerator.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyPairGenerator.cs
new file mode 100644
index 000000000..8f0629b60
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyPairGenerator.cs
@@ -0,0 +1,49 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimeKeyPairGenerator
+    {
+        private SNtruPrimeKeyGenerationParameters _ntruPrimeParams;
+
+        private int p;
+        private int q;
+
+        private SecureRandom random;
+
+        private void Initialize(KeyGenerationParameters param)
+        {
+            _ntruPrimeParams = (SNtruPrimeKeyGenerationParameters) param;
+            random = param.Random;
+
+            // n = ntruParams.Parameters.N;
+
+            p = _ntruPrimeParams.Parameters.P;
+            q = _ntruPrimeParams.Parameters.Q;
+
+        }
+
+        private AsymmetricCipherKeyPair GenKeyPair()
+        {
+            NtruPrimeEngine primeEngine = _ntruPrimeParams.Parameters.PrimeEngine;
+            byte[] sk = new byte[primeEngine.PrivateKeySize];
+            byte[] pk = new byte[primeEngine.PublicKeySize];
+            primeEngine.kem_keypair( pk,sk,random);
+
+            SNtruPrimePublicKeyParameters pubKey = new SNtruPrimePublicKeyParameters(_ntruPrimeParams.Parameters, pk);
+            SNtruPrimePrivateKeyParameters privKey = new SNtruPrimePrivateKeyParameters(_ntruPrimeParams.Parameters, sk);
+            return new AsymmetricCipherKeyPair(pubKey, privKey);
+        }
+        
+        public void Init(KeyGenerationParameters param)
+        {
+            this.Initialize(param);
+        }
+        
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            return GenKeyPair();
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyParameters.cs
new file mode 100644
index 000000000..e4e03a2bb
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeKeyParameters.cs
@@ -0,0 +1,17 @@
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimeKeyParameters : AsymmetricKeyParameter
+    {
+        private SNtruPrimeParameters _primeParameters;
+        
+        public SNtruPrimeKeyParameters(bool isPrivate, SNtruPrimeParameters primeParameters) : base(isPrivate)
+        {
+            this._primeParameters = primeParameters;
+        }
+
+        public SNtruPrimeParameters Parameters => _primeParameters;
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimeParameters.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeParameters.cs
new file mode 100644
index 000000000..4ec03a497
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimeParameters.cs
@@ -0,0 +1,66 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public sealed class SNtruPrimeParameters
+        : ICipherParameters
+    {
+        private string name;
+        private int p;
+        private int q;
+        private int _roundedBytes;
+        private bool m_LPR;
+        private int _w;
+        private int _rqBytes;
+        private int _tau0;
+        private int _tau1;
+        private int _tau2;
+        private int _tau3;
+        private int _skBytes;
+        private int _pkBytes;
+        private int _ctBytes;
+        private int _defaultKeySize;
+        private NtruPrimeEngine _primeEngine;
+        private SNtruPrimeParameters(string name, int p, int q, bool LPR, int w, int tau0, int tau1, int tau2, int tau3,
+            int skBytes, int pkBytes, int ctBytes, int roundedBytes, int rqBytes, int defaultKeySize)
+        {
+            this.name = name;
+            this.p = p;
+            this.q = q;
+            m_LPR = LPR;
+            this._w = w;
+            this._tau0 = tau0;
+            this._tau1 = tau1;
+            this._tau2 = tau2;
+            this._tau3 = tau3;
+
+            // KEM Parameters
+            this._roundedBytes = roundedBytes;
+            this._rqBytes = rqBytes;
+            this._skBytes = skBytes;
+            this._pkBytes = pkBytes;
+            this._ctBytes = ctBytes;
+            this._primeEngine = new NtruPrimeEngine(p, q, LPR, w, tau0, tau1, tau2, tau3, skBytes, pkBytes, ctBytes, roundedBytes, rqBytes, defaultKeySize / 8);
+            this._defaultKeySize = defaultKeySize;
+        }
+
+        public static SNtruPrimeParameters sntrup653 = new SNtruPrimeParameters("SNTRU_Prime_653", 653, 4621, false, 288, -1,-1,-1,-1,1518,994,897, 865, 994, 128);
+        public static SNtruPrimeParameters sntrup761 = new SNtruPrimeParameters("SNTRU_Prime_761", 761, 4591, false, 286, -1,-1,-1,-1,1763,1158,1039, 1007, 1158, 128);
+        public static SNtruPrimeParameters sntrup857 = new SNtruPrimeParameters("SNTRU_Prime_857", 857, 5167, false, 322, -1,-1,-1,-1,1999,1322,1184, 1152, 1322, 128);
+        public static SNtruPrimeParameters sntrup953 = new SNtruPrimeParameters("SNTRU_Prime_953", 953, 6343, false, 396, -1,-1,-1,-1,2254,1505,1349, 1317, 1505, 192);
+        public static SNtruPrimeParameters sntrup1013 = new SNtruPrimeParameters("SNTRU_Prime_1013", 1013, 7177, false, 448, -1,-1,-1,-1,2417,1623,1455, 1423, 1623, 192);
+        public static SNtruPrimeParameters sntrup1277 = new SNtruPrimeParameters("SNTRU_Prime_1277", 1277, 7879, false, 492, -1,-1,-1,-1,3059,2067,1847, 1815, 2067, 256);
+
+        public int P => p;
+
+        public bool LPR => m_LPR;
+
+        public int Q => q;
+
+        public int DefaultKeySize => _defaultKeySize;
+
+        internal NtruPrimeEngine PrimeEngine => _primeEngine;
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimePrivateKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimePrivateKeyParameters.cs
new file mode 100644
index 000000000..62f336459
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimePrivateKeyParameters.cs
@@ -0,0 +1,25 @@
+using System;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimePrivateKeyParameters : SNtruPrimeKeyParameters
+    {
+        internal byte[] privKey;
+
+        public byte[] GetPrivateKey()
+        {
+            return Arrays.Clone(privKey);
+        }
+
+        public SNtruPrimePrivateKeyParameters(SNtruPrimeParameters primeParameters, byte[] privKey) : base(true, primeParameters)
+        {
+            this.privKey = Arrays.Clone(privKey);
+        }
+        
+        public byte[] GetEncoded()
+        {
+            return GetPrivateKey();
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/ntruprime/SNtruPrimePublicKeyParameters.cs b/crypto/src/pqc/crypto/ntruprime/SNtruPrimePublicKeyParameters.cs
new file mode 100644
index 000000000..80bd6ddd0
--- /dev/null
+++ b/crypto/src/pqc/crypto/ntruprime/SNtruPrimePublicKeyParameters.cs
@@ -0,0 +1,24 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.NtruPrime
+{
+    public class SNtruPrimePublicKeyParameters : SNtruPrimeKeyParameters
+    {
+        internal byte[] pubKey;
+
+        public byte[] GetPublicKey()
+        {
+            return Arrays.Clone(pubKey);
+        }
+
+        public byte[] GetEncoded()
+        {
+            return GetPublicKey();
+        }
+
+        public SNtruPrimePublicKeyParameters(SNtruPrimeParameters primeParameters, byte[] pubKey) : base(false,primeParameters)
+        {
+            this.pubKey = Arrays.Clone(pubKey);
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/picnic/KMatrices.cs b/crypto/src/pqc/crypto/picnic/KMatrices.cs
index 64e6be00a..a6d280985 100644
--- a/crypto/src/pqc/crypto/picnic/KMatrices.cs
+++ b/crypto/src/pqc/crypto/picnic/KMatrices.cs
@@ -1,5 +1,3 @@
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
     internal class KMatrices
@@ -9,36 +7,35 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         private int columns;
         private uint[] data;
 
-        public KMatrices(int nmatrices, int rows, int columns, uint[] data)
+        internal KMatrices(int nmatrices, int rows, int columns, uint[] data)
         {
             this.nmatrices = nmatrices;
             this.rows = rows;
             this.columns = columns;
             this.data = data;
         }
-        
 
-        public int GetNmatrices()
+        internal int GetNmatrices()
         {
             return nmatrices;
         }
 
-        public int GetSize()
+        internal int GetSize()
         {
             return rows * columns;
         }
 
-        public int GetRows()
+        internal int GetRows()
         {
             return rows;
         }
 
-        public int GetColumns()
+        internal int GetColumns()
         {
             return columns;
         }
 
-        public uint[] GetData()
+        internal uint[] GetData()
         {
             return data;
         }
@@ -48,20 +45,21 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         : KMatrices
     {
         private int matrixPointer;
-        public int GetMatrixPointer()
+
+        internal int GetMatrixPointer()
         {
             return matrixPointer;
         }
 
-        public void SetMatrixPointer(int matrixPointer)
+        internal void SetMatrixPointer(int matrixPointer)
         {
             this.matrixPointer = matrixPointer;
         }
 
-        public KMatricesWithPointer(KMatrices m)
+        internal KMatricesWithPointer(KMatrices m)
             : base(m.GetNmatrices(), m.GetRows(), m.GetColumns(), m.GetData())
         {
             this.matrixPointer = 0;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/picnic/Msg.cs b/crypto/src/pqc/crypto/picnic/Msg.cs
index 4a8c145e7..a9a13b383 100644
--- a/crypto/src/pqc/crypto/picnic/Msg.cs
+++ b/crypto/src/pqc/crypto/picnic/Msg.cs
@@ -1,12 +1,12 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-    public class Msg
+    internal class Msg
     {
         internal byte[][] msgs; // One for each player
         internal int pos;
         internal int unopened; // Index of the unopened party, or -1 if all parties opened (when signing)
 
-        public Msg(PicnicEngine engine)
+        internal Msg(PicnicEngine engine)
         {
             msgs = new byte[engine.numMPCParties][]; // engine.andSizeBytes 
             for (int i = 0; i < engine.numMPCParties; i++)
@@ -17,4 +17,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             unopened = -1;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/picnic/PicnicEngine.cs b/crypto/src/pqc/crypto/picnic/PicnicEngine.cs
index 605a27764..5557ddcff 100644
--- a/crypto/src/pqc/crypto/picnic/PicnicEngine.cs
+++ b/crypto/src/pqc/crypto/picnic/PicnicEngine.cs
@@ -8,17 +8,17 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-    public class PicnicEngine
+    internal sealed class PicnicEngine
     {
         // same for all parameter sets
-        protected internal static readonly int saltSizeBytes = 32;
+        internal static readonly int saltSizeBytes = 32;
         private static readonly uint MAX_DIGEST_SIZE = 64;
 
         private static readonly int WORD_SIZE_BITS = 32; // the word size for the implementation. Not a LowMC parameter
         private static readonly uint LOWMC_MAX_STATE_SIZE = 64;
-        protected internal static readonly uint LOWMC_MAX_WORDS = (LOWMC_MAX_STATE_SIZE / 4);
-        protected internal static readonly uint LOWMC_MAX_KEY_BITS = 256;
-        protected internal static readonly uint LOWMC_MAX_AND_GATES = (3 * 38 * 10 + 4); /* Rounded to nearest byte */
+        internal static readonly uint LOWMC_MAX_WORDS = (LOWMC_MAX_STATE_SIZE / 4);
+        internal static readonly uint LOWMC_MAX_KEY_BITS = 256;
+        internal static readonly uint LOWMC_MAX_AND_GATES = (3 * 38 * 10 + 4); /* Rounded to nearest byte */
         private static readonly uint MAX_AUX_BYTES = ((LOWMC_MAX_AND_GATES + LOWMC_MAX_KEY_BITS) / 8 + 1);
 
         /* Maximum lengths in bytes */
@@ -28,8 +28,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         /** Largest serialized public key size, in bytes */
         private static readonly uint PICNIC_MAX_PRIVATEKEY_SIZE = (3 * PICNIC_MAX_LOWMC_BLOCK_SIZE + 2);
 
-        /** Largest serialized private key size, in bytes */
-        private static readonly uint PICNIC_MAX_SIGNATURE_SIZE = 209522;
+        //private static readonly uint PICNIC_MAX_SIGNATURE_SIZE = 209522;
 
         /** Largest signature size, in bytes */
 
@@ -47,45 +46,45 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
 
         // varies between parameter sets
-        protected internal int numRounds;
+        internal int numRounds;
         private int numSboxes;
-        protected internal int stateSizeBits;
-        protected internal int stateSizeBytes;
-        protected internal int stateSizeWords;
-        protected internal int andSizeBytes;
+        internal int stateSizeBits;
+        internal int stateSizeBytes;
+        internal int stateSizeWords;
+        internal int andSizeBytes;
         private int UnruhGWithoutInputBytes;
-        protected internal int UnruhGWithInputBytes;
-        protected internal int numMPCRounds; // T
-        protected internal int numOpenedRounds; // u
-        protected internal int numMPCParties; // N
-        protected internal int seedSizeBytes;
-        protected internal int digestSizeBytes;
-        protected internal int pqSecurityLevel;
+        internal int UnruhGWithInputBytes;
+        internal int numMPCRounds; // T
+        internal int numOpenedRounds; // u
+        internal int numMPCParties; // N
+        internal int seedSizeBytes;
+        internal int digestSizeBytes;
+        internal int pqSecurityLevel;
 
         ///
         private uint transform;
 
         private int parameters;
-        protected internal IXof digest;
+        internal IXof digest;
         private int signatureLength;
 
-        public int GetSecretKeySize()
+        internal int GetSecretKeySize()
         {
             return CRYPTO_SECRETKEYBYTES;
         }
 
-        public int GetPublicKeySize()
+        internal int GetPublicKeySize()
         {
             return CRYPTO_PUBLICKEYBYTES;
         }
 
-        public int GetSignatureSize(int messageLength)
+        internal int GetSignatureSize(int messageLength)
         {
             return CRYPTO_BYTES + messageLength;
         }
 
         //todo dont do this
-        public int GetTrueSignatureSize()
+        internal int GetTrueSignatureSize()
         {
             return signatureLength + 4;
         }
@@ -95,174 +94,174 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             parameters = picnicParams;
             switch (parameters)
             {
-                case 1:
-                case 2:
-                    /*Picnic_L1_FS
-                      Picnic_L1_UR*/
-                    pqSecurityLevel = 64;
-                    stateSizeBits = 128;
-                    numMPCRounds = 219;
-                    numMPCParties = 3;
-                    numSboxes = 10;
-                    numRounds = 20;
-                    digestSizeBytes = 32;
-                    break;
-                case 3:
-                case 4:
-                    /* Picnic_L3_FS
-                       Picnic_L3_UR*/
-                    pqSecurityLevel = 96;
-                    stateSizeBits = 192;
-                    numMPCRounds = 329;
-                    numMPCParties = 3;
-                    numSboxes = 10;
-                    numRounds = 30;
-                    digestSizeBytes = 48;
-                    break;
-                case 5:
-                case 6:
-                    /* Picnic_L5_FS
-                       Picnic_L5_UR*/
-                    pqSecurityLevel = 128;
-                    stateSizeBits = 256;
-                    numMPCRounds = 438;
-                    numMPCParties = 3;
-                    numSboxes = 10;
-                    numRounds = 38;
-                    digestSizeBytes = 64;
-                    break;
-                case 7:
-                    /*Picnic3_L1*/
-                    pqSecurityLevel = 64;
-                    stateSizeBits = 129;
-                    numMPCRounds = 250;
-                    numOpenedRounds = 36;
-                    numMPCParties = 16;
-                    numSboxes = 43;
-                    numRounds = 4;
-                    digestSizeBytes = 32;
-                    break;
-                case 8:
-                    /*Picnic3_L3*/
-                    pqSecurityLevel = 96;
-                    stateSizeBits = 192;
-                    numMPCRounds = 419;
-                    numOpenedRounds = 52;
-                    numMPCParties = 16;
-                    numSboxes = 64;
-                    numRounds = 4;
-                    digestSizeBytes = 48;
-                    break;
-                case 9:
-                    /*Picnic3_L5*/
-                    pqSecurityLevel = 128;
-                    stateSizeBits = 255;
-                    numMPCRounds = 601;
-                    numOpenedRounds = 68;
-                    numMPCParties = 16;
-                    numSboxes = 85;
-                    numRounds = 4;
-                    digestSizeBytes = 64;
-                    break;
-                case 10:
-                    /*Picnic_L1_full*/
-                    pqSecurityLevel = 64;
-                    stateSizeBits = 129;
-                    numMPCRounds = 219;
-                    numMPCParties = 3;
-                    numSboxes = 43;
-                    numRounds = 4;
-                    digestSizeBytes = 32;
-                    break;
-                case 11:
-                    /*Picnic_L3_full*/
-                    pqSecurityLevel = 96;
-                    stateSizeBits = 192;
-                    numMPCRounds = 329;
-                    numMPCParties = 3;
-                    numSboxes = 64;
-                    numRounds = 4;
-                    digestSizeBytes = 48;
-                    break;
-                case 12:
-                    /*Picnic_L5_full*/
-                    pqSecurityLevel = 128;
-                    stateSizeBits = 255;
-                    numMPCRounds = 438;
-                    numMPCParties = 3;
-                    numSboxes = 85;
-                    numRounds = 4;
-                    digestSizeBytes = 64;
-                    break;
+            case 1:
+            case 2:
+                /*Picnic_L1_FS
+                    Picnic_L1_UR*/
+                pqSecurityLevel = 64;
+                stateSizeBits = 128;
+                numMPCRounds = 219;
+                numMPCParties = 3;
+                numSboxes = 10;
+                numRounds = 20;
+                digestSizeBytes = 32;
+                break;
+            case 3:
+            case 4:
+                /* Picnic_L3_FS
+                    Picnic_L3_UR*/
+                pqSecurityLevel = 96;
+                stateSizeBits = 192;
+                numMPCRounds = 329;
+                numMPCParties = 3;
+                numSboxes = 10;
+                numRounds = 30;
+                digestSizeBytes = 48;
+                break;
+            case 5:
+            case 6:
+                /* Picnic_L5_FS
+                    Picnic_L5_UR*/
+                pqSecurityLevel = 128;
+                stateSizeBits = 256;
+                numMPCRounds = 438;
+                numMPCParties = 3;
+                numSboxes = 10;
+                numRounds = 38;
+                digestSizeBytes = 64;
+                break;
+            case 7:
+                /*Picnic3_L1*/
+                pqSecurityLevel = 64;
+                stateSizeBits = 129;
+                numMPCRounds = 250;
+                numOpenedRounds = 36;
+                numMPCParties = 16;
+                numSboxes = 43;
+                numRounds = 4;
+                digestSizeBytes = 32;
+                break;
+            case 8:
+                /*Picnic3_L3*/
+                pqSecurityLevel = 96;
+                stateSizeBits = 192;
+                numMPCRounds = 419;
+                numOpenedRounds = 52;
+                numMPCParties = 16;
+                numSboxes = 64;
+                numRounds = 4;
+                digestSizeBytes = 48;
+                break;
+            case 9:
+                /*Picnic3_L5*/
+                pqSecurityLevel = 128;
+                stateSizeBits = 255;
+                numMPCRounds = 601;
+                numOpenedRounds = 68;
+                numMPCParties = 16;
+                numSboxes = 85;
+                numRounds = 4;
+                digestSizeBytes = 64;
+                break;
+            case 10:
+                /*Picnic_L1_full*/
+                pqSecurityLevel = 64;
+                stateSizeBits = 129;
+                numMPCRounds = 219;
+                numMPCParties = 3;
+                numSboxes = 43;
+                numRounds = 4;
+                digestSizeBytes = 32;
+                break;
+            case 11:
+                /*Picnic_L3_full*/
+                pqSecurityLevel = 96;
+                stateSizeBits = 192;
+                numMPCRounds = 329;
+                numMPCParties = 3;
+                numSboxes = 64;
+                numRounds = 4;
+                digestSizeBytes = 48;
+                break;
+            case 12:
+                /*Picnic_L5_full*/
+                pqSecurityLevel = 128;
+                stateSizeBits = 255;
+                numMPCRounds = 438;
+                numMPCParties = 3;
+                numSboxes = 85;
+                numRounds = 4;
+                digestSizeBytes = 64;
+                break;
             }
 
             switch (parameters)
             {
-                case 1: /*Picnic_L1_FS*/
-                    CRYPTO_SECRETKEYBYTES = 49;
-                    CRYPTO_PUBLICKEYBYTES = 33;
-                    CRYPTO_BYTES = 34036;
-                    break;
-                case 2: /* Picnic_L1_UR*/
-                    CRYPTO_SECRETKEYBYTES = 49;
-                    CRYPTO_PUBLICKEYBYTES = 33;
-                    CRYPTO_BYTES = 53965;
-                    break;
-                case 3: /*Picnic_L3_FS*/
-                    CRYPTO_SECRETKEYBYTES = 73;
-                    CRYPTO_PUBLICKEYBYTES = 49;
-                    CRYPTO_BYTES = 76784;
-                    break;
-                case 4: /*Picnic_L3_UR*/
-                    CRYPTO_SECRETKEYBYTES = 73;
-                    CRYPTO_PUBLICKEYBYTES = 49;
-                    CRYPTO_BYTES = 121857;
-                    break;
-                case 5: /*Picnic_L5_FS*/
-                    CRYPTO_SECRETKEYBYTES = 97;
-                    CRYPTO_PUBLICKEYBYTES = 65;
-                    CRYPTO_BYTES = 132876;
-                    break;
-                case 6: /*Picnic_L5_UR*/
-                    CRYPTO_SECRETKEYBYTES = 97;
-                    CRYPTO_PUBLICKEYBYTES = 65;
-                    CRYPTO_BYTES = 209526;
-                    break;
-                case 7: /*Picnic3_L1*/
-                    CRYPTO_SECRETKEYBYTES = 52;
-                    CRYPTO_PUBLICKEYBYTES = 35;
-                    CRYPTO_BYTES = 14612;
-                    break;
-                case 8: /*Picnic3_L3*/
-                    CRYPTO_SECRETKEYBYTES = 73;
-                    CRYPTO_PUBLICKEYBYTES = 49;
-                    CRYPTO_BYTES = 35028;
-                    break;
-                case 9: /*Picnic3_L5*/
-                    CRYPTO_SECRETKEYBYTES = 97;
-                    CRYPTO_PUBLICKEYBYTES = 65;
-                    CRYPTO_BYTES = 61028;
-                    break;
-                case 10: /*Picnic_L1_full*/
-                    CRYPTO_SECRETKEYBYTES = 52;
-                    CRYPTO_PUBLICKEYBYTES = 35;
-                    CRYPTO_BYTES = 32061;
-                    break;
-                case 11: /*Picnic_L3_full*/
-                    CRYPTO_SECRETKEYBYTES = 73;
-                    CRYPTO_PUBLICKEYBYTES = 49;
-                    CRYPTO_BYTES = 71179;
-                    break;
-                case 12: /*Picnic_L5_full*/
-                    CRYPTO_SECRETKEYBYTES = 97;
-                    CRYPTO_PUBLICKEYBYTES = 65;
-                    CRYPTO_BYTES = 126286;
-                    break;
-                default:
-                    CRYPTO_SECRETKEYBYTES = -1;
-                    CRYPTO_PUBLICKEYBYTES = -1;
-                    CRYPTO_BYTES = -1;
-                    break;
+            case 1: /*Picnic_L1_FS*/
+                CRYPTO_SECRETKEYBYTES = 49;
+                CRYPTO_PUBLICKEYBYTES = 33;
+                CRYPTO_BYTES = 34036;
+                break;
+            case 2: /* Picnic_L1_UR*/
+                CRYPTO_SECRETKEYBYTES = 49;
+                CRYPTO_PUBLICKEYBYTES = 33;
+                CRYPTO_BYTES = 53965;
+                break;
+            case 3: /*Picnic_L3_FS*/
+                CRYPTO_SECRETKEYBYTES = 73;
+                CRYPTO_PUBLICKEYBYTES = 49;
+                CRYPTO_BYTES = 76784;
+                break;
+            case 4: /*Picnic_L3_UR*/
+                CRYPTO_SECRETKEYBYTES = 73;
+                CRYPTO_PUBLICKEYBYTES = 49;
+                CRYPTO_BYTES = 121857;
+                break;
+            case 5: /*Picnic_L5_FS*/
+                CRYPTO_SECRETKEYBYTES = 97;
+                CRYPTO_PUBLICKEYBYTES = 65;
+                CRYPTO_BYTES = 132876;
+                break;
+            case 6: /*Picnic_L5_UR*/
+                CRYPTO_SECRETKEYBYTES = 97;
+                CRYPTO_PUBLICKEYBYTES = 65;
+                CRYPTO_BYTES = 209526;
+                break;
+            case 7: /*Picnic3_L1*/
+                CRYPTO_SECRETKEYBYTES = 52;
+                CRYPTO_PUBLICKEYBYTES = 35;
+                CRYPTO_BYTES = 14612;
+                break;
+            case 8: /*Picnic3_L3*/
+                CRYPTO_SECRETKEYBYTES = 73;
+                CRYPTO_PUBLICKEYBYTES = 49;
+                CRYPTO_BYTES = 35028;
+                break;
+            case 9: /*Picnic3_L5*/
+                CRYPTO_SECRETKEYBYTES = 97;
+                CRYPTO_PUBLICKEYBYTES = 65;
+                CRYPTO_BYTES = 61028;
+                break;
+            case 10: /*Picnic_L1_full*/
+                CRYPTO_SECRETKEYBYTES = 52;
+                CRYPTO_PUBLICKEYBYTES = 35;
+                CRYPTO_BYTES = 32061;
+                break;
+            case 11: /*Picnic_L3_full*/
+                CRYPTO_SECRETKEYBYTES = 73;
+                CRYPTO_PUBLICKEYBYTES = 49;
+                CRYPTO_BYTES = 71179;
+                break;
+            case 12: /*Picnic_L5_full*/
+                CRYPTO_SECRETKEYBYTES = 97;
+                CRYPTO_PUBLICKEYBYTES = 65;
+                CRYPTO_BYTES = 126286;
+                break;
+            default:
+                CRYPTO_SECRETKEYBYTES = -1;
+                CRYPTO_PUBLICKEYBYTES = -1;
+                CRYPTO_BYTES = -1;
+                break;
             }
 
             // calculated depending on above parameters
@@ -304,7 +303,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest = new ShakeDigest(shakeSize);
         }
 
-        public bool crypto_sign_open(byte[] m, byte[] sm, byte[] pk)
+        internal bool crypto_sign_open(byte[] m, byte[] sm, byte[] pk)
         {
             uint sigLen = Pack.LE_To_UInt32(sm, 0);
             byte[] m_from_sm = Arrays.CopyOfRange(sm, 4, 4 + m.Length);
@@ -432,7 +431,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return status;
         }
 
-        void VerifyProof(Signature.Proof proof, View view1, View view2, int challenge, byte[] salt, 
+        private void VerifyProof(Signature.Proof proof, View view1, View view2, int challenge, byte[] salt, 
             uint roundNumber, byte[] tmp, uint[] plaintext, Tape tape)
         {
             Array.Copy(proof.communicatedBits, 0, view2.communicatedBits, 0, andSizeBytes);
@@ -534,7 +533,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             mpc_LowMC_verify(view1, view2, tape, tmp_ints, plaintext, challenge);
         }
 
-        void mpc_LowMC_verify(View view1, View view2, Tape tapes, uint[] tmp, uint[] plaintext,  int challenge)
+        private void mpc_LowMC_verify(View view1, View view2, Tape tapes, uint[] tmp, uint[] plaintext,  int challenge)
         {
             Utils.Fill(tmp, 0, tmp.Length, 0);
 
@@ -576,7 +575,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             Array.Copy(tmp, 3 * stateSizeWords, view2.outputShare, 0, stateSizeWords);
         }
 
-        void mpc_substitution_verify(uint[] state, Tape rand, View view1, View view2)
+        private void mpc_substitution_verify(uint[] state, Tape rand, View view1, View view2)
         {
             uint[] a = new uint[2];
             uint[] b = new uint[2];
@@ -611,7 +610,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             }
         }
 
-        void mpc_AND_verify(uint[] in1, uint[] in2, uint[] output, Tape rand, View view1, View view2)
+        private void mpc_AND_verify(uint[] in1, uint[] in2, uint[] output, Tape rand, View view1, View view2)
         {
             uint[] r = {Utils.GetBit(rand.tapes[0], rand.pos), Utils.GetBit(rand.tapes[1], rand.pos)};
 
@@ -646,7 +645,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
         }
 
-
         private int DeserializeSignature(Signature sig, byte[] sigBytes, uint sigBytesLen, int sigBytesOffset)
         {
             Signature.Proof[] proofs = sig.proofs;
@@ -1086,7 +1084,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return true;
         }
 
-        public void crypto_sign(byte[] sm, byte[] m, byte[] sk)
+        internal void crypto_sign(byte[] sm, byte[] m, byte[] sk)
         {
             picnic_sign(sk, m, sm);
             Array.Copy(m, 0, sm, 4, m.Length);
@@ -1142,7 +1140,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
         /*** Serialization functions ***/
 
-        int SerializeSignature(Signature sig, byte[] sigBytes, int sigOffset)
+        private int SerializeSignature(Signature sig, byte[] sigBytes, int sigOffset)
         {
             Signature.Proof[] proofs = sig.proofs;
             byte[] challengeBits = sig.challengeBits;
@@ -1204,7 +1202,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return sigByteIndex - sigOffset;
         }
 
-        int GetChallenge(byte[] challenge, int round)
+        private int GetChallenge(byte[] challenge, int round)
         {
             return (Utils.GetBit(challenge, 2 * round + 1) << 1) | Utils.GetBit(challenge, 2 * round);
         }
@@ -1418,7 +1416,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         }
 
         /* Caller must allocate the first parameter */
-        void Prove(Signature.Proof proof, int challenge, byte[] seeds, int seedsOffset,
+        private void Prove(Signature.Proof proof, int challenge, byte[] seeds, int seedsOffset,
             View[] views, byte[][] commitments, byte[][] gs)
         {
             if (challenge == 0)
@@ -1456,7 +1454,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             }
         }
 
-        void H3(uint[] circuitOutput, uint[] plaintext, uint[][][] viewOutputs,
+        private void H3(uint[] circuitOutput, uint[] plaintext, uint[][][] viewOutputs,
             byte[][][] AS, byte[] challengeBits, byte[] salt,
             byte[] message, byte[][][] gs)
         {
@@ -1508,7 +1506,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             /* Hash the salt & message */
             digest.BlockUpdate(salt, 0, saltSizeBytes);
             digest.BlockUpdate(message, 0, message.Length);
-            digest.DoFinal(hash, 0, digestSizeBytes);
+            digest.OutputFinal(hash, 0, digestSizeBytes);
 
             /* Convert hash to a packed string of values in {0,1,2} */
             int round = 0;
@@ -1548,7 +1546,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
                 /* We need more bits; hash set hash = H_1(hash) */
                 digest.Update((byte) 1);
                 digest.BlockUpdate(hash, 0, digestSizeBytes);
-                digest.DoFinal(hash, 0, digestSizeBytes);
+                digest.OutputFinal(hash, 0, digestSizeBytes);
             }
         }
 
@@ -1569,7 +1567,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             /* Hash the seed with H_5, store digest in output */
             digest.Update((byte) 5);
             digest.BlockUpdate(seed, seedOffset, seedSizeBytes);
-            digest.DoFinal(output, 0, digestSizeBytes);
+            digest.OutputFinal(output, 0, digestSizeBytes);
 
             /* Hash H_5(seed), the view, and the length */
             digest.BlockUpdate(output, 0, digestSizeBytes);
@@ -1582,7 +1580,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(view.communicatedBits, 0, andSizeBytes);
 
             digest.BlockUpdate(Pack.UInt32_To_LE((uint)outputBytes), 0, 2);
-            digest.DoFinal(output, 0, outputBytes);
+            digest.OutputFinal(output, 0, outputBytes);
         }
 
         private void mpc_LowMC(Tape tapes, View[] views, uint[] plaintext, uint[] slab)
@@ -1635,7 +1633,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             /* Hash the seed, store result in `hash` */
             digest.Update((byte) 4);
             digest.BlockUpdate(seed, seedOffset, seedSizeBytes);
-            digest.DoFinal(hash, 0, digestSizeBytes);
+            digest.OutputFinal(hash, 0, digestSizeBytes);
 
             /* Compute H_0(H_4(seed), view) */
             digest.Update((byte) 0);
@@ -1643,7 +1641,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(Pack.UInt32_To_LE(view.inputShare), 0, stateSizeBytes);
             digest.BlockUpdate(view.communicatedBits, 0, andSizeBytes);
             digest.BlockUpdate(Pack.UInt32_To_LE(view.outputShare), 0, stateSizeBytes);
-            digest.DoFinal(hash, 0, digestSizeBytes);
+            digest.OutputFinal(hash, 0, digestSizeBytes);
         }
 
         private void mpc_substitution(uint[] state, Tape rand, View[] views)
@@ -1659,7 +1657,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             int stateOffset;
             for (int i = 0; i < numSboxes * 3; i += 3)
             {
-
                 for (int j = 0; j < 3; j++)
                 {
                     stateOffset = ((3 + j) * stateSizeWords) * 32;
@@ -1747,8 +1744,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             /* Hash the seed and a constant, store the result in tape. */
             digest.Update((byte) 2);
             digest.BlockUpdate(seed, seedOffset, seedSizeBytes);
-            digest.DoFinal(tape, 0, digestSizeBytes);
-//        Console.Error.Write("tape: " + Hex.toHexString(tape));
+            digest.OutputFinal(tape, 0, digestSizeBytes);
 
             /* Expand the hashed seed, salt, round and player indices, and output
              * length to create the tape. */
@@ -1757,7 +1753,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(Pack.UInt32_To_LE(roundNumber), 0, 2);
             digest.BlockUpdate(Pack.UInt32_To_LE(playerNumber), 0, 2);
             digest.BlockUpdate(Pack.UInt32_To_LE((uint)tapeLen), 0, 2);
-            digest.DoFinal(tape, 0, tapeLen);
+            digest.OutputFinal(tape, 0, tapeLen);
 
             return true;
         }
@@ -1773,7 +1769,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(Pack.UInt32_To_LE((uint)stateSizeBits), 0, 2);
 
             // Derive the N*T seeds + 1 salt
-            digest.DoFinal(allSeeds, 0, seedSizeBytes * (numMPCParties * numMPCRounds) + saltSizeBytes);
+            digest.OutputFinal(allSeeds, 0, seedSizeBytes * (numMPCParties * numMPCRounds) + saltSizeBytes);
 
             return allSeeds;
         }
@@ -1949,7 +1945,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return missingLeaves;
         }
 
-
         private void HCP(byte[] challengeHash, uint[] challengeC, uint[] challengeP, byte[][] Ch,
             byte[] hCv, byte[] salt, uint[] pubKey, uint[] plaintext, byte[] message)
         {
@@ -1963,7 +1958,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(Pack.UInt32_To_LE(pubKey), 0, stateSizeBytes);
             digest.BlockUpdate(Pack.UInt32_To_LE(plaintext), 0, stateSizeBytes);
             digest.BlockUpdate(message, 0, message.Length);
-            digest.DoFinal(challengeHash, 0, digestSizeBytes);
+            digest.OutputFinal(challengeHash, 0, digestSizeBytes);
 
             if ((challengeC != null) && (challengeP != null))
             {
@@ -1971,7 +1966,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             }
         }
 
-        static int BitsToChunks(int chunkLenBits, byte[] input, int inputLen, uint[] chunks)
+        private static int BitsToChunks(int chunkLenBits, byte[] input, int inputLen, uint[] chunks)
         {
             if (chunkLenBits > inputLen * 8)
             {
@@ -1992,7 +1987,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return chunkCount;
         }
 
-        static uint AppendUnique(uint[] list,  uint value,  uint position)
+        private static uint AppendUnique(uint[] list,  uint value,  uint position)
         {
             if (position == 0)
             {
@@ -2041,7 +2036,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
                 digest.Update((byte) 1);
                 digest.BlockUpdate(h, 0, digestSizeBytes);
-                digest.DoFinal(h, 0, digestSizeBytes);
+                digest.OutputFinal(h, 0, digestSizeBytes);
             }
 
             // Note that we always compute h = H(h) after setting C
@@ -2066,7 +2061,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
                 digest.Update((byte) 1);
                 digest.BlockUpdate(h, 0, digestSizeBytes);
-                digest.DoFinal(h, 0, digestSizeBytes);
+                digest.OutputFinal(h, 0, digestSizeBytes);
             }
         }
 
@@ -2077,7 +2072,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
                 digest.BlockUpdate(C[i], 0, digestSizeBytes);
             }
 
-            digest.DoFinal(digest_arr, 0, digestSizeBytes);
+            digest.OutputFinal(digest_arr, 0, digestSizeBytes);
         }
 
         private void commit_v(byte[] digest_arr, byte[] input, Msg msg)
@@ -2089,7 +2084,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
                 digest.BlockUpdate(msg.msgs[i], 0, msgs_size);
             }
 
-            digest.DoFinal(digest_arr, 0, digestSizeBytes);
+            digest.OutputFinal(digest_arr, 0, digestSizeBytes);
         }
 
         private int SimulateOnline(uint[] maskedKey, Tape tape, uint[] tmp_shares,
@@ -2139,7 +2134,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
                 digest.BlockUpdate(salt, 0, saltSizeBytes);
                 digest.BlockUpdate(Pack.UInt32_To_LE(t), 0, 2);
                 digest.BlockUpdate(Pack.UInt32_To_LE(i), 0, 2);
-                digest.DoFinal(tape.tapes[i], 0, tapeSizeBytes);
+                digest.OutputFinal(tape.tapes[i], 0, tapeSizeBytes);
             }
         }
 
@@ -2171,7 +2166,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return true;
         }
 
-        static uint Extend(uint bit)
+        private static uint Extend(uint bit)
         {
             return ~(bit - 1);
         }
@@ -2233,7 +2228,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             }
         }
 
-        protected internal void aux_mpc_sbox(uint[] input, uint[] output, Tape tape)
+        internal void aux_mpc_sbox(uint[] input, uint[] output, Tape tape)
         {
             for (int i = 0; i < numSboxes * 3; i += 3)
             {
@@ -2314,7 +2309,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(salt, 0, saltSizeBytes);
             digest.BlockUpdate(Pack.UInt32_To_LE(t), 0, 2);
             digest.BlockUpdate(Pack.UInt32_To_LE(j), 0, 2);
-            digest.DoFinal(digest_arr, 0, digestSizeBytes);
+            digest.OutputFinal(digest_arr, 0, digestSizeBytes);
         }
 
         private void ComputeSaltAndRootSeed(byte[] saltAndRoot, uint[] privateKey, uint[] pubKey, uint[] plaintext, byte[] message)
@@ -2338,10 +2333,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             digest.BlockUpdate(pubkey_bytes, 0, stateSizeBytes);
             digest.BlockUpdate(plaintext_bytes, 0, stateSizeBytes);
             digest.BlockUpdate(Pack.UInt16_To_LE((ushort) (stateSizeBits & 0xffff)), 0, 2);
-            digest.DoFinal(saltAndRoot, 0, saltAndRoot.Length);
+            digest.OutputFinal(saltAndRoot, 0, saltAndRoot.Length);
         }
 
-        static bool is_picnic3(int parameters)
+        private static bool is_picnic3(int parameters)
         {
             return parameters == 7 /*Picnic3_L1*/ ||
                    parameters == 8 /*Picnic3_L3*/ ||
@@ -2349,7 +2344,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         }
 
         //todo return int;
-        public void crypto_sign_keypair(byte[] pk, byte[] sk, SecureRandom random)
+        internal void crypto_sign_keypair(byte[] pk, byte[] sk, SecureRandom random)
         {
             // set array sizes
             byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
@@ -2474,7 +2469,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             }
         }
 
-        protected internal void xor_array(uint[] output, uint[] in1, uint[] in2, int in2_offset, int length)
+        internal void xor_array(uint[] output, uint[] in1, uint[] in2, int in2_offset, int length)
         {
             for (int i = 0; i < length; i++)
             {
@@ -2482,12 +2477,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             }
         }
 
-        protected internal void matrix_mul(uint[] output, uint[] state, uint[] matrix, int matrixOffset)
+        internal void matrix_mul(uint[] output, uint[] state, uint[] matrix, int matrixOffset)
         {
             matrix_mul_offset(output, 0, state, 0, matrix, matrixOffset);
         }
 
-        protected void matrix_mul_offset(uint[] output, int outputOffset, uint[] state, int stateOffset, uint[] matrix,
+        internal void matrix_mul_offset(uint[] output, int outputOffset, uint[] state, int stateOffset, uint[] matrix,
             int matrixOffset)
         {
             // Use temp to correctly handle the case when state = output
diff --git a/crypto/src/pqc/crypto/picnic/PicnicParameters.cs b/crypto/src/pqc/crypto/picnic/PicnicParameters.cs
index ed2030054..24ade6676 100644
--- a/crypto/src/pqc/crypto/picnic/PicnicParameters.cs
+++ b/crypto/src/pqc/crypto/picnic/PicnicParameters.cs
@@ -4,9 +4,9 @@ using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-    public class PicnicParameters
+    public sealed class PicnicParameters
         : ICipherParameters
-        {
+    {
         public static PicnicParameters picnicl1fs = new PicnicParameters("picnicl1fs", 1);
         public static PicnicParameters picnicl1ur = new PicnicParameters("picnicl1ur", 2);
         public static PicnicParameters picnicl3fs = new PicnicParameters("picnicl3fs", 3);
@@ -20,18 +20,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         public static PicnicParameters picnicl3full = new PicnicParameters("picnicl3full", 11);
         public static PicnicParameters picnicl5full = new PicnicParameters("picnicl5full", 12);
 
-        private String name;
+        private string name;
         private int param;
-        private PicnicParameters(String name, int param)
+
+        private PicnicParameters(string name, int param)
         {
             this.name = name;
             this.param = param;
         }
 
-        public String GetName()
-        {
-            return name;
-        }
+        public string Name => name;
 
         internal PicnicEngine GetEngine()
         {
diff --git a/crypto/src/pqc/crypto/picnic/Signature2.cs b/crypto/src/pqc/crypto/picnic/Signature2.cs
index 7659fb314..c6f44380c 100644
--- a/crypto/src/pqc/crypto/picnic/Signature2.cs
+++ b/crypto/src/pqc/crypto/picnic/Signature2.cs
@@ -1,7 +1,7 @@
 
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-    public class Signature2
+    internal class Signature2
     {
         internal byte[] salt;
         internal byte[] iSeedInfo; // Info required to recompute the tree of all initial seeds
@@ -14,7 +14,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         internal Proof2[] proofs; // One proof for each online execution the verifier checks
 
         //todo initialize in engine!
-        public Signature2(PicnicEngine engine)
+        internal Signature2(PicnicEngine engine)
         {
             challengeHash = new byte[engine.digestSizeBytes];
             salt = new byte[PicnicEngine.saltSizeBytes];
@@ -23,7 +23,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             proofs = new Proof2[engine.numMPCRounds];
         }
 
-        public class Proof2
+        internal class Proof2
         {
             internal byte[] seedInfo; // Information required to compute the tree with seeds of of all opened parties
             internal int seedInfoLen; // Length of seedInfo buffer
@@ -32,7 +32,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             internal byte[] input; // Masked input used in online execution
             internal byte[] msgs; // Broadcast messages of unopened party P[t]
 
-            public Proof2(PicnicEngine engine)
+            internal Proof2(PicnicEngine engine)
             {
                 seedInfo = null;
                 seedInfoLen = 0;
@@ -45,6 +45,3 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         }
     }
 }
-
-
-
diff --git a/crypto/src/pqc/crypto/picnic/Tape.cs b/crypto/src/pqc/crypto/picnic/Tape.cs
index dd1a44de8..c433b3267 100644
--- a/crypto/src/pqc/crypto/picnic/Tape.cs
+++ b/crypto/src/pqc/crypto/picnic/Tape.cs
@@ -1,125 +1,128 @@
 using Org.BouncyCastle.Crypto.Utilities;
-using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Utilities;
 
-public class Tape
+namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-    internal byte[][] tapes;
-    internal int pos;
-    int nTapes;
-
-    private PicnicEngine engine;
-    public Tape(PicnicEngine engine)
+    internal class Tape
     {
-        this.engine = engine;
-        tapes = new byte[engine.numMPCParties][]; //[2 * engine.andSizeBytes];
-        for (int i = 0; i < engine.numMPCParties; i++)
-        {
-            tapes[i] = new byte[2 * engine.andSizeBytes];
-        }
-        pos = 0;
-        nTapes = engine.numMPCParties;
-    }
+        internal byte[][] tapes;
+        internal int pos;
+        int nTapes;
 
-    protected internal void SetAuxBits(byte[] input)
-    {
-        int last = engine.numMPCParties - 1;
-        int pos = 0;
-        int n = engine.stateSizeBits;
+        private PicnicEngine engine;
 
-        for(int j = 0; j < engine.numRounds; j++)
+        internal Tape(PicnicEngine engine)
         {
-            for(int i = 0; i < n; i++)
+            this.engine = engine;
+            tapes = new byte[engine.numMPCParties][]; //[2 * engine.andSizeBytes];
+            for (int i = 0; i < engine.numMPCParties; i++)
             {
-                Utils.SetBit(this.tapes[last], n + n*2*j  + i, Utils.GetBit(input, pos++));
+                tapes[i] = new byte[2 * engine.andSizeBytes];
             }
+            pos = 0;
+            nTapes = engine.numMPCParties;
         }
-    }
-
-    /* Input is the tapes for one parallel repitition; i.e., tapes[t]
-     * Updates the random tapes of all players with the mask values for the output of
-     * AND gates, and computes the N-th party's share such that the AND gate invariant
-     * holds on the mask values.
-     */
-    protected internal void ComputeAuxTape(byte[] inputs)
-    {
-        uint[] roundKey = new uint[PicnicEngine.LOWMC_MAX_WORDS];
-        uint[] x = new uint[PicnicEngine.LOWMC_MAX_WORDS];
-        uint[] y = new uint[PicnicEngine.LOWMC_MAX_WORDS];
-        uint[] key = new uint[PicnicEngine.LOWMC_MAX_WORDS];
-        uint[] key0 = new uint[PicnicEngine.LOWMC_MAX_WORDS];
 
-        key0[engine.stateSizeWords - 1] = 0;
-        TapesToParityBits(key0, engine.stateSizeBits);
-
-//        System.out.print("key0: ");
-//        for (int i = 0; i < key0.Length; i++)
-//        {System.out.printf("%08x ", key0[i]);}System.out.Println();
-
-        // key = key0 x KMatrix[0]^(-1)
-        KMatricesWithPointer current = LowmcConstants.Instance.KMatrixInv(engine, 0);
-        engine.matrix_mul(key, key0, current.GetData(), current.GetMatrixPointer());
-
-//        System.out.print("key: ");
-//        for (int i = 0; i < key0.Length; i++)
-//        {System.out.printf("%08x ", key[i]);}System.out.Println();
+        internal void SetAuxBits(byte[] input)
+        {
+            int last = engine.numMPCParties - 1;
+            int pos = 0;
+            int n = engine.stateSizeBits;
 
+            for(int j = 0; j < engine.numRounds; j++)
+            {
+                for(int i = 0; i < n; i++)
+                {
+                    Utils.SetBit(this.tapes[last], n + n*2*j  + i, Utils.GetBit(input, pos++));
+                }
+            }
+        }
 
-        if(inputs != null)
+        /* Input is the tapes for one parallel repitition; i.e., tapes[t]
+         * Updates the random tapes of all players with the mask values for the output of
+         * AND gates, and computes the N-th party's share such that the AND gate invariant
+         * holds on the mask values.
+         */
+        internal void ComputeAuxTape(byte[] inputs)
         {
-            Pack.UInt32_To_LE(Arrays.CopyOf(key, engine.stateSizeWords), inputs, 0);
-        }
+            uint[] roundKey = new uint[PicnicEngine.LOWMC_MAX_WORDS];
+            uint[] x = new uint[PicnicEngine.LOWMC_MAX_WORDS];
+            uint[] y = new uint[PicnicEngine.LOWMC_MAX_WORDS];
+            uint[] key = new uint[PicnicEngine.LOWMC_MAX_WORDS];
+            uint[] key0 = new uint[PicnicEngine.LOWMC_MAX_WORDS];
 
+            key0[engine.stateSizeWords - 1] = 0;
+            TapesToParityBits(key0, engine.stateSizeBits);
 
-        for (int r = engine.numRounds; r > 0; r--)
-        {
-            current = LowmcConstants.Instance.KMatrix(engine, r);
-            engine.matrix_mul(roundKey, key, current.GetData(), current.GetMatrixPointer());    // roundKey = key * KMatrix(r)
+    //        System.out.print("key0: ");
+    //        for (int i = 0; i < key0.Length; i++)
+    //        {System.out.printf("%08x ", key0[i]);}System.out.Println();
+
+            // key = key0 x KMatrix[0]^(-1)
+            KMatricesWithPointer current = LowmcConstants.Instance.KMatrixInv(engine, 0);
+            engine.matrix_mul(key, key0, current.GetData(), current.GetMatrixPointer());
 
-            engine.xor_array(x, x, roundKey, 0, engine.stateSizeWords);
+    //        System.out.print("key: ");
+    //        for (int i = 0; i < key0.Length; i++)
+    //        {System.out.printf("%08x ", key[i]);}System.out.Println();
 
-            current = LowmcConstants.Instance.LMatrixInv(engine, r-1);
-            engine.matrix_mul(y, x, current.GetData(), current.GetMatrixPointer());
 
-            if(r == 1)
+            if(inputs != null)
             {
-                // Use key as input
-                System.Array.Copy(key0, 0, x, 0, key0.Length);
+                Pack.UInt32_To_LE(Arrays.CopyOf(key, engine.stateSizeWords), inputs, 0);
             }
-            else
+
+
+            for (int r = engine.numRounds; r > 0; r--)
             {
-                this.pos = engine.stateSizeBits * 2 * (r - 1);
-                // Read input mask shares from tapes
-                TapesToParityBits(x, engine.stateSizeBits);
+                current = LowmcConstants.Instance.KMatrix(engine, r);
+                engine.matrix_mul(roundKey, key, current.GetData(), current.GetMatrixPointer());    // roundKey = key * KMatrix(r)
+
+                engine.xor_array(x, x, roundKey, 0, engine.stateSizeWords);
+
+                current = LowmcConstants.Instance.LMatrixInv(engine, r-1);
+                engine.matrix_mul(y, x, current.GetData(), current.GetMatrixPointer());
+
+                if(r == 1)
+                {
+                    // Use key as input
+                    System.Array.Copy(key0, 0, x, 0, key0.Length);
+                }
+                else
+                {
+                    this.pos = engine.stateSizeBits * 2 * (r - 1);
+                    // Read input mask shares from tapes
+                    TapesToParityBits(x, engine.stateSizeBits);
+                }
+
+                this.pos = engine.stateSizeBits * 2 * (r - 1) + engine.stateSizeBits;
+                engine.aux_mpc_sbox(x, y, this);
             }
 
-            this.pos = engine.stateSizeBits * 2 * (r - 1) + engine.stateSizeBits;
-            engine.aux_mpc_sbox(x, y, this);
+            // Reset the random tape counter so that the online execution uses the
+            // same random bits as when computing the aux shares
+            this.pos = 0;
         }
 
-        // Reset the random tape counter so that the online execution uses the
-        // same random bits as when computing the aux shares
-        this.pos = 0;
-    }
-
-    private void TapesToParityBits(uint[] output, int outputBitLen)
-    {
-        for (int i = 0; i < outputBitLen; i++)
+        private void TapesToParityBits(uint[] output, int outputBitLen)
         {
-            Utils.SetBitInWordArray(output, i, Utils.Parity16(TapesToWord()));
+            for (int i = 0; i < outputBitLen; i++)
+            {
+                Utils.SetBitInWordArray(output, i, Utils.Parity16(TapesToWord()));
+            }
         }
-    }
-
-    protected internal uint TapesToWord()
-    {
-        byte[] shares = new byte[4];
 
-        for (int i = 0; i < 16; i++)
+        internal uint TapesToWord()
         {
-            byte bit = Utils.GetBit(this.tapes[i], this.pos);
-            Utils.SetBit(shares, i, bit);
+            byte[] shares = new byte[4];
+
+            for (int i = 0; i < 16; i++)
+            {
+                byte bit = Utils.GetBit(this.tapes[i], this.pos);
+                Utils.SetBit(shares, i, bit);
+            }
+            this.pos++;
+            return Pack.LE_To_UInt32(shares, 0);
         }
-        this.pos++;
-        return Pack.LE_To_UInt32(shares, 0);
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/picnic/Tree.cs b/crypto/src/pqc/crypto/picnic/Tree.cs
index 36efea831..80b2f87ba 100644
--- a/crypto/src/pqc/crypto/picnic/Tree.cs
+++ b/crypto/src/pqc/crypto/picnic/Tree.cs
@@ -1,12 +1,11 @@
 using System;
+
 using Org.BouncyCastle.Crypto.Utilities;
-using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-
-    public class Tree
+    internal sealed class Tree
     {
         private static int MAX_SEED_SIZE_BYTES = 32;
         private uint MAX_AUX_BYTES;
@@ -22,17 +21,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
         private PicnicEngine engine;
 
-        protected internal byte[][] GetLeaves()
+        internal byte[][] GetLeaves()
         {
             return this.nodes;
         }
 
-        protected internal uint GetLeavesOffset()
+        internal uint GetLeavesOffset()
         {
             return this.numNodes - this.numLeaves;
         }
 
-        public Tree(PicnicEngine engine, uint numLeaves, int dataSize)
+        internal Tree(PicnicEngine engine, uint numLeaves, int dataSize)
         {
             this.engine = engine;
             MAX_AUX_BYTES = ((PicnicEngine.LOWMC_MAX_AND_GATES + PicnicEngine.LOWMC_MAX_KEY_BITS) / 8 + 1);
@@ -69,7 +68,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
         /* Create a Merkle tree by hashing up all nodes.
          * leafData must have Length this.numNodes, but some may be NULL. */
-        protected internal void BuildMerkleTree(byte[][] leafData, byte[] salt)
+        internal void BuildMerkleTree(byte[][] leafData, byte[] salt)
         {
             uint firstLeaf = this.numNodes - this.numLeaves;
 
@@ -92,7 +91,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         }
 
         /* verifyMerkleTree: verify for each leaf that is set */
-        protected internal int VerifyMerkleTree(byte[][] leafData, byte[] salt)
+        internal int VerifyMerkleTree(byte[][] leafData, byte[] salt)
         {
             uint firstLeaf = this.numNodes - this.numLeaves;
 
@@ -131,7 +130,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return 0;
         }
 
-        protected internal int ReconstructSeeds(uint[] hideList, uint hideListSize,
+        internal int ReconstructSeeds(uint[] hideList, uint hideListSize,
             byte[] input, uint inputLen, byte[] salt, uint repIndex)
         {
             int ret = 0;
@@ -163,7 +162,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         }
 
         /* Serialze the missing nodes that the verifier will require to check commitments for non-missing leaves */
-        protected internal byte[] OpenMerkleTree(uint[] missingLeaves, uint missingLeavesSize, int[] outputSizeBytes)
+        internal byte[] OpenMerkleTree(uint[] missingLeaves, uint missingLeavesSize, int[] outputSizeBytes)
         {
             uint[] revealedSize = new uint[1];
             uint[] revealed = this.GetRevealedMerkleNodes(missingLeaves, missingLeavesSize, revealedSize);
@@ -293,7 +292,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
         }
 
 
-        protected internal uint RevealSeedsSize(uint[] hideList, uint hideListSize)
+        internal uint RevealSeedsSize(uint[] hideList, uint hideListSize)
         {
             uint[] numNodesRevealed = new uint[1];
             numNodesRevealed[0] = 0;
@@ -301,7 +300,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return numNodesRevealed[0] * (uint)engine.seedSizeBytes;
         }
 
-        protected internal int RevealSeeds(uint[] hideList, uint hideListSize, byte[] output, int outputSize)
+        internal int RevealSeeds(uint[] hideList, uint hideListSize, byte[] output, int outputSize)
         {
 //        byte[] outputBase = Arrays.clone(output);
             uint[] revealedSize = new uint[1];
@@ -330,7 +329,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return output.Length - outLen;
         }
 
-        protected internal uint OpenMerkleTreeSize(uint[] missingLeaves, uint missingLeavesSize)
+        internal uint OpenMerkleTreeSize(uint[] missingLeaves, uint missingLeavesSize)
         {
             uint[] revealedSize = new uint[1];
             uint[] revealed = this.GetRevealedMerkleNodes(missingLeaves, missingLeavesSize, revealedSize);
@@ -450,19 +449,18 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
             engine.digest.BlockUpdate(salt, 0, PicnicEngine.saltSizeBytes);
             engine.digest.BlockUpdate(Pack.UInt32_To_LE(parent), 0, 2);
-            engine.digest.DoFinal(this.nodes[parent], 0, engine.digestSizeBytes);
+            engine.digest.OutputFinal(this.nodes[parent], 0, engine.digestSizeBytes);
             this.haveNode[parent] = true;
         }
 
-
-        protected internal byte[] GetLeaf(uint leafIndex)
+        internal byte[] GetLeaf(uint leafIndex)
         {
             uint firstLeaf = this.numNodes - this.numLeaves;
             return this.nodes[firstLeaf + leafIndex];
         }
 
         /* addMerkleNodes: deserialize and add the data for nodes provided by the committer */
-        protected internal int AddMerkleNodes(uint[] missingLeaves, uint missingLeavesSize, byte[] input, uint inputSize)
+        internal int AddMerkleNodes(uint[] missingLeaves, uint missingLeavesSize, byte[] input, uint inputSize)
         {
 //        if (inputSize > INT_MAX) {
 //            return -1;
@@ -495,7 +493,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             return 0;
         }
 
-        protected internal void GenerateSeeds(byte[] rootSeed, byte[] salt, uint repIndex)
+        internal void GenerateSeeds(byte[] rootSeed, byte[] salt, uint repIndex)
         {
             this.nodes[0] = rootSeed;
             this.haveNode[0] = true;
@@ -545,7 +543,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
             engine.digest.BlockUpdate(salt, 0, PicnicEngine.saltSizeBytes);
             engine.digest.BlockUpdate(Pack.UInt16_To_LE((ushort) (repIndex & 0xffff)), 0, 2); //todo check endianness
             engine.digest.BlockUpdate(Pack.UInt16_To_LE((ushort) (nodeIndex & 0xffff)), 0, 2); //todo check endianness
-            engine.digest.DoFinal(digest_arr, 0, 2 * engine.seedSizeBytes);
+            engine.digest.OutputFinal(digest_arr, 0, 2 * engine.seedSizeBytes);
 //        System.out.println("hash: " + Hex.toHexString(digest_arr));
         }
 
@@ -578,6 +576,5 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 
             return this.exists[i] == 1;
         }
-
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/picnic/View.cs b/crypto/src/pqc/crypto/picnic/View.cs
index d72afa9d4..cac47631a 100644
--- a/crypto/src/pqc/crypto/picnic/View.cs
+++ b/crypto/src/pqc/crypto/picnic/View.cs
@@ -1,18 +1,16 @@
-using Org.BouncyCastle.Pqc.Crypto.Picnic;
-
 namespace Org.BouncyCastle.Pqc.Crypto.Picnic
 {
-    public class View
+    internal class View
     {
         internal uint[] inputShare;
         internal byte[] communicatedBits;
         internal uint[] outputShare;
 
-        public View(PicnicEngine engine)
+        internal View(PicnicEngine engine)
         {
             inputShare = new uint[engine.stateSizeBytes];
             communicatedBits = new byte[engine.andSizeBytes];
             outputShare = new uint[engine.stateSizeBytes];
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/Poly.cs b/crypto/src/pqc/crypto/saber/Poly.cs
index f36b62031..1a7312201 100644
--- a/crypto/src/pqc/crypto/saber/Poly.cs
+++ b/crypto/src/pqc/crypto/saber/Poly.cs
@@ -1,65 +1,70 @@
-
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-
-    class Poly
+    internal class Poly
     {
-        private static int KARATSUBA_N = 64;
-
-        private static int SCHB_N = 16;
-
-        private int N_RES;
-        private int N_SB;
-        private int N_SB_RES;
-        private int SABER_N;
-        private int SABER_L;
+        private const int KARATSUBA_N = 64;
 
-        private SABEREngine engine;
-        private Utils utils;
+        //private readonly int N_RES;
+        private readonly int N_SB;
+        private readonly int N_SB_RES;
+        private readonly int SABER_N;
+        private readonly int SABER_L;
 
+        private readonly SaberEngine engine;
+        private readonly SaberUtilities utils;
 
-        public Poly(SABEREngine engine)
+        public Poly(SaberEngine engine)
         {
             this.engine = engine;
-            this.SABER_L = engine.getSABER_L();
-            this.SABER_N = engine.getSABER_N();
-            this.N_RES = (SABER_N << 1);
-            this.N_SB = (SABER_N >> 2);
-            this.N_SB_RES = (2 * N_SB - 1);
-            this.utils = engine.GetUtils();
+            this.SABER_L = engine.L;
+            this.SABER_N = engine.N;
+            //this.N_RES = SABER_N << 1;
+            this.N_SB = SABER_N >> 2;
+            this.N_SB_RES = 2 * N_SB - 1;
+            this.utils = engine.Utilities;
         }
 
         public void GenMatrix(short[][][] A, byte[] seed)
         {
-            byte[] buf = new byte[SABER_L * engine.getSABER_POLYVECBYTES()];
+            byte[] buf = new byte[SABER_L * engine.PolyVecBytes];
             int i;
 
-            IXof digest = new ShakeDigest(128);
-            digest.BlockUpdate(seed, 0, engine.getSABER_SEEDBYTES());
-            digest.DoFinal(buf, 0, buf.Length);
+            engine.Symmetric.Prf(buf, seed, engine.SeedBytes, buf.Length);
+
 
             for (i = 0; i < SABER_L; i++)
             {
-                utils.BS2POLVECq(buf, i * engine.getSABER_POLYVECBYTES(), A[i]);
+                utils.BS2POLVECq(buf, i * engine.PolyVecBytes, A[i]);
             }
         }
 
         public void GenSecret(short[][] s, byte[] seed)
         {
-            byte[] buf = new byte[SABER_L * engine.getSABER_POLYCOINBYTES()];
-            int i;
-            IXof digest = new ShakeDigest(128);
-            digest.BlockUpdate(seed, 0, engine.getSABER_NOISE_SEEDBYTES());
-            digest.DoFinal(buf, 0, buf.Length);
+            byte[] buf = new byte[SABER_L * engine.PolyCoinBytes];
 
-            for (i = 0; i < SABER_L; i++)
+            engine.Symmetric.Prf(buf, seed, engine.NoiseSeedBytes, buf.Length);
+
+
+            for (int i = 0; i < SABER_L; i++)
             {
-                Cbd(s[i], buf, i * engine.getSABER_POLYCOINBYTES());
+                if (!engine.UsingEffectiveMasking)
+                {
+                    Cbd(s[i], buf, i * engine.PolyCoinBytes);
+                }
+                else
+                {
+                    for(int j = 0; j<SABER_N/4; j++)
+                    {
+                        s[i][4*j] = (short) ((((buf[j + i * engine.PolyCoinBytes]) & 0x03) ^ 2) - 2);
+                        s[i][4*j+1] = (short) ((((buf[j + i * engine.PolyCoinBytes] >> 2) & 0x03)  ^ 2) - 2);
+                        s[i][4*j+2] = (short) ((((buf[j + i * engine.PolyCoinBytes] >> 4) & 0x03)  ^ 2) - 2);
+                        s[i][4*j+3] = (short) ((((buf[j + i * engine.PolyCoinBytes] >> 6) & 0x03)  ^ 2) - 2);
+                    }
+                }
             }
-
         }
 
         private long LoadLittleEndian(byte[] x, int offset, int bytes)
@@ -78,7 +83,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
         {
             int[] a = new int[4], b = new int[4];
             int i, j;
-            if (engine.getSABER_MU() == 6)
+            if (engine.MU == 6)
             {
                 int t, d;
                 for (i = 0; i < SABER_N / 4; i++)
@@ -103,7 +108,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
                     s[4 * i + 3] = (short) (a[3] - b[3]);
                 }
             }
-            else if (engine.getSABER_MU() == 8)
+            else if (engine.MU == 8)
             {
                 int t, d;
                 for (i = 0; i < SABER_N / 4; i++)
@@ -129,7 +134,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
                     s[4 * i + 3] = (short) (a[3] - b[3]);
                 }
             }
-            else if (engine.getSABER_MU() == 10)
+            else if (engine.MU == 10)
             {
                 long t, d;
                 for (i = 0; i < SABER_N / 4; i++)
@@ -435,4 +440,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
 
 
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABEREngine.cs b/crypto/src/pqc/crypto/saber/SABEREngine.cs
index 4be553957..e57c0a23f 100644
--- a/crypto/src/pqc/crypto/saber/SABEREngine.cs
+++ b/crypto/src/pqc/crypto/saber/SABEREngine.cs
@@ -1,5 +1,5 @@
-
 using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Security;
@@ -7,103 +7,75 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABEREngine
+    internal sealed class SaberEngine
     {
         // constant parameters
-        public static int SABER_EQ = 13;
-        public static int SABER_EP = 10;
-        public static int SABER_N = 256;
-
-        private static int SABER_SEEDBYTES = 32;
-        private static int SABER_NOISE_SEEDBYTES = 32;
-        private static int SABER_KEYBYTES = 32;
-        private static int SABER_HASHBYTES = 32;
+        internal const int SABER_EP = 10;
+        internal const int SABER_N = 256;
 
+        private const int SABER_SEEDBYTES = 32;
+        private const int SABER_NOISE_SEEDBYTES = 32;
+        private const int SABER_KEYBYTES = 32;
+        private const int SABER_HASHBYTES = 32;
 
         // parameters for SABER{n}
-        private int SABER_L;
-        private int SABER_MU;
-        private int SABER_ET;
-
-        private int SABER_POLYCOINBYTES;
-        private int SABER_POLYBYTES;
-        private int SABER_POLYVECBYTES;
-        private int SABER_POLYCOMPRESSEDBYTES;
-        private int SABER_POLYVECCOMPRESSEDBYTES;
-        private int SABER_SCALEBYTES_KEM;
-        private int SABER_INDCPA_PUBLICKEYBYTES;
-        private int SABER_INDCPA_SECRETKEYBYTES;
-        private int SABER_PUBLICKEYBYTES;
-        private int SABER_SECRETKEYBYTES;
-        private int SABER_BYTES_CCA_DEC;
-        private int defaultKeySize;
+        private readonly int SABER_L;
+        private readonly int SABER_MU;
+        private readonly int SABER_ET;
+
+        private readonly int SABER_POLYCOINBYTES;
+        private readonly int SABER_EQ;
+        private readonly int SABER_POLYBYTES;
+        private readonly int SABER_POLYVECBYTES;
+        private readonly int SABER_POLYCOMPRESSEDBYTES;
+        private readonly int SABER_POLYVECCOMPRESSEDBYTES;
+        private readonly int SABER_SCALEBYTES_KEM;
+        private readonly int SABER_INDCPA_PUBLICKEYBYTES;
+        private readonly int SABER_INDCPA_SECRETKEYBYTES;
+        private readonly int SABER_PUBLICKEYBYTES;
+        private readonly int SABER_SECRETKEYBYTES;
+        private readonly int SABER_BYTES_CCA_DEC;
+        private readonly int defaultKeySize;
 
         //
         private int h1;
         private int h2;
 
-        private Utils utils;
+        private Symmetric symmetric;
+        private SaberUtilities utils;
         private Poly poly;
 
-        public int getSABER_N()
-        {
-            return SABER_N;
-        }
+        private readonly bool usingAes;
+        private readonly bool usingEffectiveMasking;
 
-        public int getSABER_EP()
-        {
-            return SABER_EP;
-        }
+        public bool UsingAes => usingAes;
+        public bool UsingEffectiveMasking => usingEffectiveMasking;
+        public Symmetric Symmetric => symmetric;
+        
+        public int EQ => SABER_EQ;
+        public int N => SABER_N;
 
-        public int getSABER_KEYBYTES()
-        {
-            return SABER_KEYBYTES;
-        }
+        public int EP => SABER_EP;
 
-        public int getSABER_L()
-        {
-            return SABER_L;
-        }
+        public int KeyBytes => SABER_KEYBYTES;
 
-        public int getSABER_ET()
-        {
-            return SABER_ET;
-        }
+        public int L => SABER_L;
 
-        public int getSABER_POLYBYTES()
-        {
-            return SABER_POLYBYTES;
-        }
+        public int ET => SABER_ET;
 
-        public int getSABER_POLYVECBYTES()
-        {
-            return SABER_POLYVECBYTES;
-        }
+        public int PolyBytes => SABER_POLYBYTES;
 
-        public int getSABER_SEEDBYTES()
-        {
-            return SABER_SEEDBYTES;
-        }
+        public int PolyVecBytes => SABER_POLYVECBYTES;
 
-        public int getSABER_POLYCOINBYTES()
-        {
-            return SABER_POLYCOINBYTES;
-        }
+        public int SeedBytes => SABER_SEEDBYTES;
 
-        public int getSABER_NOISE_SEEDBYTES()
-        {
-            return SABER_NOISE_SEEDBYTES;
-        }
+        public int PolyCoinBytes => SABER_POLYCOINBYTES;
 
-        public int getSABER_MU()
-        {
-            return SABER_MU;
-        }
+        public int NoiseSeedBytes => SABER_NOISE_SEEDBYTES;
 
-        public Utils GetUtils()
-        {
-            return utils;
-        }
+        public int MU => SABER_MU;
+
+        public SaberUtilities Utilities => utils;
 
         public int GetSessionKeySize()
         {
@@ -126,10 +98,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
             return SABER_SECRETKEYBYTES;
         }
 
-        public SABEREngine(int l, int defaultKeySize)
+        internal SaberEngine(int l, int defaultKeySize, bool usingAes, bool usingEffectiveMasking)
         {
             this.defaultKeySize = defaultKeySize;
-
+            this.usingAes = usingAes;
+            this.usingEffectiveMasking = usingEffectiveMasking;
             this.SABER_L = l;
             if (l == 2)
             {
@@ -146,8 +119,25 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
                 this.SABER_MU = 6;
                 this.SABER_ET = 6;
             }
+            if(usingAes)
+            {
+                symmetric = new Symmetric.AesSymmetric();
+            }
+            else
+            {
+                symmetric = new Symmetric.ShakeSymmetric();
+            }
 
-            this.SABER_POLYCOINBYTES = (SABER_MU * SABER_N / 8);
+            if(usingEffectiveMasking)
+            {
+                this.SABER_EQ = 12;
+                this.SABER_POLYCOINBYTES = (2 * SABER_N / 8);
+            }
+            else
+            {
+                this.SABER_EQ = 13;
+                this.SABER_POLYCOINBYTES = (SABER_MU * SABER_N / 8);
+            }
             this.SABER_POLYBYTES = (SABER_EQ * SABER_N / 8);
             this.SABER_POLYVECBYTES = (SABER_L * SABER_POLYBYTES);
             this.SABER_POLYCOMPRESSEDBYTES = (SABER_EP * SABER_N / 8);
@@ -162,7 +152,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
 
             this.h1 = (1 << (SABER_EQ - SABER_EP - 1));
             this.h2 = ((1 << (SABER_EP - 2)) - (1 << (SABER_EP - SABER_ET - 1)) + (1 << (SABER_EQ - SABER_EP - 1)));
-            utils = new Utils(this);
+            utils = new SaberUtilities(this);
             poly = new Poly(this);
         }
 
@@ -202,9 +192,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
 
             random.NextBytes(seed_A);
 
-            IXof digest = new ShakeDigest(128);
-            digest.BlockUpdate(seed_A, 0, SABER_SEEDBYTES);
-            digest.DoFinal(seed_A, 0, SABER_SEEDBYTES);
+            symmetric.Prf(seed_A, seed_A, SABER_SEEDBYTES, SABER_SEEDBYTES);
 
             random.NextBytes(seed_s);
 
@@ -233,13 +221,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
             int i;
             indcpa_kem_keypair(pk, sk, random); // sk[0:SABER_INDCPA_SECRETKEYBYTES-1] <-- sk
             for (i = 0; i < SABER_INDCPA_PUBLICKEYBYTES; i++)
+            {
                 sk[i + SABER_INDCPA_SECRETKEYBYTES] =
                     pk[i]; // sk[SABER_INDCPA_SECRETKEYBYTES:SABER_INDCPA_SECRETKEYBYTES+SABER_INDCPA_SECRETKEYBYTES-1] <-- pk
-
+            }
             // Then hash(pk) is appended.
-            Sha3Digest digest = new Sha3Digest(256);
-            digest.BlockUpdate(pk, 0, SABER_INDCPA_PUBLICKEYBYTES);
-            digest.DoFinal(sk, SABER_SECRETKEYBYTES - 64);
+            symmetric.Hash_h(sk, pk, SABER_SECRETKEYBYTES - 64);
 
             // Remaining part of sk contains a pseudo-random number.
             byte[] nonce = new byte[SABER_KEYBYTES];
@@ -289,7 +276,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
             }
 
             short[] mp = new short[SABER_N];
-            ;
             short[] vp = new short[SABER_N];
             byte[] seed_A = Arrays.CopyOfRange(pk, SABER_POLYVECCOMPRESSEDBYTES, pk.Length);
 
@@ -327,35 +313,30 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
             byte[] nonce = new byte[32];
             random.NextBytes(nonce);
 
-            Sha3Digest digest_256 = new Sha3Digest(256);
-            Sha3Digest digest_512 = new Sha3Digest(512);
+           
 
             // BUF[0:31] <-- random message (will be used as the key for client) Note: hash doesnot release system RNG output
-            digest_256.BlockUpdate(nonce, 0, 32);
-            digest_256.DoFinal(nonce, 0);
+            symmetric.Hash_h(nonce, nonce, 0);
+
             Array.Copy(nonce, 0, buf, 0, 32);
 
             // BUF[32:63] <-- Hash(public key);  Multitarget countermeasure for coins + contributory KEM
-            digest_256.BlockUpdate(pk, 0, SABER_INDCPA_PUBLICKEYBYTES);
-            digest_256.DoFinal(buf, 32);
+            symmetric.Hash_h(buf, pk, 32);
 
             // kr[0:63] <-- Hash(buf[0:63]);
-            digest_512.BlockUpdate(buf, 0, 64);
-            digest_512.DoFinal(kr, 0);
+            symmetric.Hash_g(kr, buf);
 
             // K^ <-- kr[0:31]
             // noiseseed (r) <-- kr[32:63];
             // buf[0:31] contains message; kr[32:63] contains randomness r;
             indcpa_kem_enc(buf, Arrays.CopyOfRange(kr, 32, kr.Length), pk, c);
 
-            digest_256.BlockUpdate(c, 0, SABER_BYTES_CCA_DEC);
-            digest_256.DoFinal(kr, 32);
+            symmetric.Hash_h(kr, c, 32);
 
             // hash concatenation of pre-k and h(c) to k
             //todo support 128 and 192 bit keys
             byte[] temp_k = new byte[32];
-            digest_256.BlockUpdate(kr, 0, 64);
-            digest_256.DoFinal(temp_k, 0);
+            symmetric.Hash_h(temp_k, kr, 0);
 
             Array.Copy(temp_k, 0, k, 0, defaultKeySize / 8);
 
@@ -407,14 +388,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
 
             // Multitarget countermeasure for coins + contributory KEM
             for (i = 0; i < 32; i++) // Save hash by storing h(pk) in sk
+            {
                 buf[32 + i] = sk[SABER_SECRETKEYBYTES - 64 + i];
+            }
 
 
-            Sha3Digest digest_256 = new Sha3Digest(256);
-            Sha3Digest digest_512 = new Sha3Digest(512);
-
-            digest_512.BlockUpdate(buf, 0, 64);
-            digest_512.DoFinal(kr, 0);
+            symmetric.Hash_g(kr, buf);
 
             indcpa_kem_enc(buf, Arrays.CopyOfRange(kr, 32, kr.Length), pk, cmp);
 
@@ -422,16 +401,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
 
             // overwrite coins in kr with h(c)
 
-            digest_256.BlockUpdate(c, 0, SABER_BYTES_CCA_DEC);
-            digest_256.DoFinal(kr, 32);
-
+            symmetric.Hash_h(kr, c, 32);
+            
             cmov(kr, sk, SABER_SECRETKEYBYTES - SABER_KEYBYTES, SABER_KEYBYTES, (byte) fail);
 
             // hash concatenation of pre-k and h(c) to k
             //todo support 128 and 192 bit keys
             byte[] temp_k = new byte[32];
-            digest_256.BlockUpdate(kr, 0, 64);
-            digest_256.DoFinal(temp_k, 0);
+            symmetric.Hash_h(temp_k, kr, 0);
 
             Array.Copy(temp_k, 0, k, 0, defaultKeySize / 8);
             return 0;
@@ -461,4 +438,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
 
 
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERKEMExtractor.cs b/crypto/src/pqc/crypto/saber/SABERKEMExtractor.cs
index 6fe447e80..ce0b374f3 100644
--- a/crypto/src/pqc/crypto/saber/SABERKEMExtractor.cs
+++ b/crypto/src/pqc/crypto/saber/SABERKEMExtractor.cs
@@ -1,33 +1,32 @@
-
 using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERKEMExtractor
+    public sealed class SaberKemExtractor
         : IEncapsulatedSecretExtractor
     {
-        private SABEREngine engine;
+        private readonly SaberKeyParameters key;
 
-        private SABERKeyParameters key;
+        private SaberEngine engine;
 
-        public SABERKEMExtractor(SABERKeyParameters privParams)
+        public SaberKemExtractor(SaberKeyParameters privParams)
         {
             this.key = privParams;
-            InitCipher(key.GetParameters());
+            InitCipher(key.Parameters);
         }
 
-        private void InitCipher(SABERParameters param)
+        private void InitCipher(SaberParameters param)
         {
-            engine = param.GetEngine();
+            engine = param.Engine;
         }
 
         public byte[] ExtractSecret(byte[] encapsulation)
         {
             byte[] session_key = new byte[engine.GetSessionKeySize()];
-            engine.crypto_kem_dec(session_key, encapsulation, ((SABERPrivateKeyParameters) key).GetPrivateKey());
+            engine.crypto_kem_dec(session_key, encapsulation, ((SaberPrivateKeyParameters) key).GetPrivateKey());
             return session_key;
         }
 
-        public int InputSize => engine.GetCipherTextSize();
+        public int EncapsulationLength => engine.GetCipherTextSize();
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERKEMGenerator.cs b/crypto/src/pqc/crypto/saber/SABERKEMGenerator.cs
index 4baf1d0c6..f948717b1 100644
--- a/crypto/src/pqc/crypto/saber/SABERKEMGenerator.cs
+++ b/crypto/src/pqc/crypto/saber/SABERKEMGenerator.cs
@@ -1,32 +1,28 @@
-
-using System;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Pqc.Crypto.Utilities;
 using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERKEMGenerator
+    public sealed class SaberKemGenerator
         : IEncapsulatedSecretGenerator
     {
         // the source of randomness
         private SecureRandom sr;
 
-        public SABERKEMGenerator(SecureRandom random)
+        public SaberKemGenerator(SecureRandom random)
         {
-            this.sr = random;
+            this.sr = CryptoServicesRegistrar.GetSecureRandom(random);
         }
 
         public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
         {
-            SABERPublicKeyParameters key = (SABERPublicKeyParameters) recipientKey;
-            SABEREngine engine = key.GetParameters().GetEngine();
+            SaberPublicKeyParameters key = (SaberPublicKeyParameters)recipientKey;
+            SaberEngine engine = key.Parameters.Engine;
             byte[] cipher_text = new byte[engine.GetCipherTextSize()];
             byte[] sessionKey = new byte[engine.GetSessionKeySize()];
-            engine.crypto_kem_enc(cipher_text, sessionKey, key.PublicKey, sr);
+            engine.crypto_kem_enc(cipher_text, sessionKey, key.GetPublicKey(), sr);
             return new SecretWithEncapsulationImpl(sessionKey, cipher_text);
         }
-        
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERKeyGenerationParameters.cs b/crypto/src/pqc/crypto/saber/SABERKeyGenerationParameters.cs
index 48851076d..c76ec6234 100644
--- a/crypto/src/pqc/crypto/saber/SABERKeyGenerationParameters.cs
+++ b/crypto/src/pqc/crypto/saber/SABERKeyGenerationParameters.cs
@@ -1,25 +1,19 @@
-
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERKeyGenerationParameters
+    public sealed class SaberKeyGenerationParameters
         : KeyGenerationParameters
     {
-        private SABERParameters parameters;
+        private SaberParameters parameters;
 
-        public SABERKeyGenerationParameters(
-            SecureRandom random,
-            SABERParameters saberParameters)
+        public SaberKeyGenerationParameters(SecureRandom random, SaberParameters saberParameters)
             : base(random, 256)
         {
             this.parameters = saberParameters;
         }
 
-        public SABERParameters GetParameters()
-        {
-            return parameters;
-        }
+        public SaberParameters Parameters => parameters;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERKeyPairGenerator.cs b/crypto/src/pqc/crypto/saber/SABERKeyPairGenerator.cs
index 1b74a2833..1407f74a3 100644
--- a/crypto/src/pqc/crypto/saber/SABERKeyPairGenerator.cs
+++ b/crypto/src/pqc/crypto/saber/SABERKeyPairGenerator.cs
@@ -1,13 +1,12 @@
-
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERKeyPairGenerator
+    public class SaberKeyPairGenerator
         : IAsymmetricCipherKeyPairGenerator
     {
-        private SABERKeyGenerationParameters saberParams;
+        private SaberKeyGenerationParameters saberParams;
 
         private int l;
 
@@ -16,21 +15,21 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
         private void Initialize(
             KeyGenerationParameters param)
         {
-            this.saberParams = (SABERKeyGenerationParameters) param;
+            this.saberParams = (SaberKeyGenerationParameters)param;
             this.random = param.Random;
 
-            this.l = this.saberParams.GetParameters().GetL();
+            this.l = this.saberParams.Parameters.L;
         }
 
         private AsymmetricCipherKeyPair GenKeyPair()
         {
-            SABEREngine engine = saberParams.GetParameters().GetEngine();
+            SaberEngine engine = saberParams.Parameters.Engine;
             byte[] sk = new byte[engine.GetPrivateKeySize()];
             byte[] pk = new byte[engine.GetPublicKeySize()];
             engine.crypto_kem_keypair(pk, sk, random);
 
-            SABERPublicKeyParameters pubKey = new SABERPublicKeyParameters(saberParams.GetParameters(), pk);
-            SABERPrivateKeyParameters privKey = new SABERPrivateKeyParameters(saberParams.GetParameters(), sk);
+            SaberPublicKeyParameters pubKey = new SaberPublicKeyParameters(saberParams.Parameters, pk);
+            SaberPrivateKeyParameters privKey = new SaberPrivateKeyParameters(saberParams.Parameters, sk);
             return new AsymmetricCipherKeyPair(pubKey, privKey);
         }
 
@@ -44,4 +43,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
             return GenKeyPair();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERKeyParameters.cs b/crypto/src/pqc/crypto/saber/SABERKeyParameters.cs
index f44e21e00..d83d2e3ea 100644
--- a/crypto/src/pqc/crypto/saber/SABERKeyParameters.cs
+++ b/crypto/src/pqc/crypto/saber/SABERKeyParameters.cs
@@ -1,24 +1,18 @@
-
 using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERKeyParameters
+    public abstract class SaberKeyParameters
         : AsymmetricKeyParameter
     {
-        private SABERParameters parameters;
+        private readonly SaberParameters parameters;
 
-        public SABERKeyParameters(
-            bool isPrivate,
-            SABERParameters parameters)
+        public SaberKeyParameters(bool isPrivate, SaberParameters parameters)
             : base(isPrivate)
         {
             this.parameters = parameters;
         }
 
-        public SABERParameters GetParameters()
-        {
-            return parameters;
-        }
+        public SaberParameters Parameters => parameters;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERParameters.cs b/crypto/src/pqc/crypto/saber/SABERParameters.cs
index b53c9cf6f..e2992d4c4 100644
--- a/crypto/src/pqc/crypto/saber/SABERParameters.cs
+++ b/crypto/src/pqc/crypto/saber/SABERParameters.cs
@@ -1,56 +1,54 @@
-
-using System;
 using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERParameters
+    public sealed class SaberParameters
         : ICipherParameters
     {
+        public static SaberParameters lightsaberkem128r3 = new SaberParameters("lightsaberkem128r3", 2, 128, false, false);
+        public static SaberParameters saberkem128r3 = new SaberParameters("saberkem128r3", 3, 128, false, false);
+        public static SaberParameters firesaberkem128r3 = new SaberParameters("firesaberkem128r3", 4, 128, false, false);
+
+        public static SaberParameters lightsaberkem192r3 = new SaberParameters("lightsaberkem192r3", 2, 192, false, false);
+        public static SaberParameters saberkem192r3 = new SaberParameters("saberkem192r3", 3, 192, false, false);
+        public static SaberParameters firesaberkem192r3 = new SaberParameters("firesaberkem192r3", 4, 192, false, false);
+
+        public static SaberParameters lightsaberkem256r3 = new SaberParameters("lightsaberkem256r3", 2, 256, false, false);
+        public static SaberParameters saberkem256r3 = new SaberParameters("saberkem256r3", 3, 256, false, false);
+        public static SaberParameters firesaberkem256r3 = new SaberParameters("firesaberkem256r3", 4, 256, false, false);
+        
+        public static SaberParameters lightsaberkem90sr3 = new SaberParameters("lightsaberkem90sr3", 2, 256, true, false);
+        public static SaberParameters saberkem90sr3 = new SaberParameters("saberkem90sr3", 3, 256, true, false);
+        public static SaberParameters firesaberkem90sr3 = new SaberParameters("firesaberkem90sr3", 4, 256, true, false);
 
-        public static SABERParameters lightsaberkem128r3 = new SABERParameters("lightsaberkem128r3", 2, 128);
-        public static SABERParameters saberkem128r3 = new SABERParameters("saberkem128r3", 3, 128);
-        public static SABERParameters firesaberkem128r3 = new SABERParameters("firesaberkem128r3", 4, 128);
+        public static SaberParameters ulightsaberkemr3 = new SaberParameters("ulightsaberkemr3", 2, 256, false, true);
+        public static SaberParameters usaberkemr3 = new SaberParameters("usaberkemr3", 3, 256, false, true);
+        public static SaberParameters ufiresaberkemr3 = new SaberParameters("ufiresaberkemr3", 4, 256, false, true);
 
-        public static SABERParameters lightsaberkem192r3 = new SABERParameters("lightsaberkem192r3", 2, 192);
-        public static SABERParameters saberkem192r3 = new SABERParameters("saberkem192r3", 3, 192);
-        public static SABERParameters firesaberkem192r3 = new SABERParameters("firesaberkem192r3", 4, 192);
+        public static SaberParameters ulightsaberkem90sr3 = new SaberParameters("ulightsaberkem90sr3", 2, 256, true, true);
+        public static SaberParameters usaberkem90sr3 = new SaberParameters("usaberkem90sr3", 3, 256, true, true);
+        public static SaberParameters ufiresaberkem90sr3 = new SaberParameters("ufiresaberkem90sr3", 4, 256, true, true);
 
-        public static SABERParameters lightsaberkem256r3 = new SABERParameters("lightsaberkem256r3", 2, 256);
-        public static SABERParameters saberkem256r3 = new SABERParameters("saberkem256r3", 3, 256);
-        public static SABERParameters firesaberkem256r3 = new SABERParameters("firesaberkem256r3", 4, 256);
 
-        private String name;
-        private int l;
-        private int defaultKeySize;
-        private SABEREngine engine;
+        private readonly string name;
+        private readonly int l;
+        private readonly int defaultKeySize;
+        private readonly SaberEngine engine;
 
-        public SABERParameters(String name, int l, int defaultKeySize)
+        private SaberParameters(string name, int l, int defaultKeySize, bool usingAes, bool usingEffectiveMasking)
         {
             this.name = name;
             this.l = l;
             this.defaultKeySize = defaultKeySize;
-            this.engine = new SABEREngine(l, defaultKeySize);
+            this.engine = new SaberEngine(l, defaultKeySize, usingAes, usingEffectiveMasking);
         }
 
-        public String GetName()
-        {
-            return name;
-        }
+        public string Name => name;
 
-        public int GetL()
-        {
-            return l;
-        }
+        public int L => l;
 
-        public int GetDefaultKeySize()
-        {
-            return defaultKeySize;
-        }
+        public int DefaultKeySize => defaultKeySize;
 
-        public SABEREngine GetEngine()
-        {
-            return engine;
-        }
+        internal SaberEngine Engine => engine;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERPrivateKeyParameters.cs b/crypto/src/pqc/crypto/saber/SABERPrivateKeyParameters.cs
index 9dc9d1393..6b708af73 100644
--- a/crypto/src/pqc/crypto/saber/SABERPrivateKeyParameters.cs
+++ b/crypto/src/pqc/crypto/saber/SABERPrivateKeyParameters.cs
@@ -2,17 +2,12 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERPrivateKeyParameters
-        : SABERKeyParameters
+    public sealed class SaberPrivateKeyParameters
+        : SaberKeyParameters
     {
-        private byte[] privateKey;
+        private readonly byte[] privateKey;
 
-        public byte[] GetPrivateKey()
-        {
-            return Arrays.Clone(privateKey);
-        }
-
-        public SABERPrivateKeyParameters(SABERParameters parameters, byte[] privateKey)
+        public SaberPrivateKeyParameters(SaberParameters parameters, byte[] privateKey)
             : base(true, parameters)
         {
             this.privateKey = Arrays.Clone(privateKey);
@@ -22,5 +17,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
         {
             return Arrays.Clone(privateKey);
         }
+
+        public byte[] GetPrivateKey()
+        {
+            return Arrays.Clone(privateKey);
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/SABERPublicKeyParameters.cs b/crypto/src/pqc/crypto/saber/SABERPublicKeyParameters.cs
index d8dc16cb4..573ca2661 100644
--- a/crypto/src/pqc/crypto/saber/SABERPublicKeyParameters.cs
+++ b/crypto/src/pqc/crypto/saber/SABERPublicKeyParameters.cs
@@ -2,22 +2,25 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class SABERPublicKeyParameters
-        : SABERKeyParameters
+    public sealed class SaberPublicKeyParameters
+        : SaberKeyParameters
     {
-        public byte[] publicKey;
+        public readonly byte[] publicKey;
 
-        public byte[] PublicKey => Arrays.Clone(publicKey);
+        public SaberPublicKeyParameters(SaberParameters parameters, byte[] publicKey)
+            : base(false, parameters)
+        {
+            this.publicKey = Arrays.Clone(publicKey);
+        }
 
         public byte[] GetEncoded()
         {
-            return PublicKey;
+            return Arrays.Clone(publicKey);
         }
 
-        public SABERPublicKeyParameters(SABERParameters parameters, byte[] publicKey)
-            : base(false, parameters)
+        public byte[] GetPublicKey()
         {
-            this.publicKey = Arrays.Clone(publicKey);
+            return Arrays.Clone(publicKey);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/Utils.cs b/crypto/src/pqc/crypto/saber/SaberUtilities.cs
index 32a544c1f..90c2b4ea4 100644
--- a/crypto/src/pqc/crypto/saber/Utils.cs
+++ b/crypto/src/pqc/crypto/saber/SaberUtilities.cs
@@ -1,23 +1,23 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Saber
 {
-    public class Utils
+    internal class SaberUtilities
     {
-
-        private int SABER_N;
-        private int SABER_L;
-        private int SABER_ET;
-        private int SABER_POLYBYTES;
-        private int SABER_EP;
-        private int SABER_KEYBYTES;
-
-        public Utils(SABEREngine engine)
+        private readonly int SABER_N;
+        private readonly int SABER_L;
+        private readonly int SABER_ET;
+        private readonly int SABER_POLYBYTES;
+        private readonly int SABER_EP;
+        private readonly int SABER_KEYBYTES;
+        private readonly bool usingEffectiveMasking;
+        internal SaberUtilities(SaberEngine engine)
         {
-            this.SABER_N = engine.getSABER_N();
-            this.SABER_L = engine.getSABER_L();
-            this.SABER_ET = engine.getSABER_ET();
-            this.SABER_POLYBYTES = engine.getSABER_POLYBYTES();
-            this.SABER_EP = engine.getSABER_EP();
-            this.SABER_KEYBYTES = engine.getSABER_KEYBYTES();
+            this.SABER_N = engine.N;
+            this.SABER_L = engine.L;
+            this.SABER_ET = engine.ET;
+            this.SABER_POLYBYTES = engine.PolyBytes;
+            this.SABER_EP = engine.EP;
+            this.SABER_KEYBYTES = engine.KeyBytes;
+            this.usingEffectiveMasking = engine.UsingEffectiveMasking;
         }
 
         public void POLT2BS(byte[] bytes, int byteIndex, short[] data)
@@ -118,60 +118,87 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
         private void POLq2BS(byte[] bytes, int byteIndex, short[] data)
         {
             short j, offset_byte, offset_data;
-            for (j = 0; j < SABER_N / 8; j++)
+            if (!usingEffectiveMasking)
             {
-                offset_byte = (short) (13 * j);
-                offset_data = (short) (8 * j);
-                bytes[byteIndex + offset_byte + 0] = (byte) (data[offset_data + 0] & (0xff));
-                bytes[byteIndex + offset_byte + 1] =
-                    (byte) (((data[offset_data + 0] >> 8) & 0x1f) | ((data[offset_data + 1] & 0x07) << 5));
-                bytes[byteIndex + offset_byte + 2] = (byte) ((data[offset_data + 1] >> 3) & 0xff);
-                bytes[byteIndex + offset_byte + 3] =
-                    (byte) (((data[offset_data + 1] >> 11) & 0x03) | ((data[offset_data + 2] & 0x3f) << 2));
-                bytes[byteIndex + offset_byte + 4] =
-                    (byte) (((data[offset_data + 2] >> 6) & 0x7f) | ((data[offset_data + 3] & 0x01) << 7));
-                bytes[byteIndex + offset_byte + 5] = (byte) ((data[offset_data + 3] >> 1) & 0xff);
-                bytes[byteIndex + offset_byte + 6] =
-                    (byte) (((data[offset_data + 3] >> 9) & 0x0f) | ((data[offset_data + 4] & 0x0f) << 4));
-                bytes[byteIndex + offset_byte + 7] = (byte) ((data[offset_data + 4] >> 4) & 0xff);
-                bytes[byteIndex + offset_byte + 8] =
-                    (byte) (((data[offset_data + 4] >> 12) & 0x01) | ((data[offset_data + 5] & 0x7f) << 1));
-                bytes[byteIndex + offset_byte + 9] =
-                    (byte) (((data[offset_data + 5] >> 7) & 0x3f) | ((data[offset_data + 6] & 0x03) << 6));
-                bytes[byteIndex + offset_byte + 10] = (byte) ((data[offset_data + 6] >> 2) & 0xff);
-                bytes[byteIndex + offset_byte + 11] =
-                    (byte) (((data[offset_data + 6] >> 10) & 0x07) | ((data[offset_data + 7] & 0x1f) << 3));
-                bytes[byteIndex + offset_byte + 12] = (byte) ((data[offset_data + 7] >> 5) & 0xff);
+                for (j = 0; j < SABER_N / 8; j++)
+                {
+                    offset_byte = (short)(13 * j);
+                    offset_data = (short)(8 * j);
+                    bytes[byteIndex + offset_byte + 0] = (byte)(data[offset_data + 0] & (0xff));
+                    bytes[byteIndex + offset_byte + 1] =
+                        (byte)(((data[offset_data + 0] >> 8) & 0x1f) | ((data[offset_data + 1] & 0x07) << 5));
+                    bytes[byteIndex + offset_byte + 2] = (byte)((data[offset_data + 1] >> 3) & 0xff);
+                    bytes[byteIndex + offset_byte + 3] =
+                        (byte)(((data[offset_data + 1] >> 11) & 0x03) | ((data[offset_data + 2] & 0x3f) << 2));
+                    bytes[byteIndex + offset_byte + 4] =
+                        (byte)(((data[offset_data + 2] >> 6) & 0x7f) | ((data[offset_data + 3] & 0x01) << 7));
+                    bytes[byteIndex + offset_byte + 5] = (byte)((data[offset_data + 3] >> 1) & 0xff);
+                    bytes[byteIndex + offset_byte + 6] =
+                        (byte)(((data[offset_data + 3] >> 9) & 0x0f) | ((data[offset_data + 4] & 0x0f) << 4));
+                    bytes[byteIndex + offset_byte + 7] = (byte)((data[offset_data + 4] >> 4) & 0xff);
+                    bytes[byteIndex + offset_byte + 8] =
+                        (byte)(((data[offset_data + 4] >> 12) & 0x01) | ((data[offset_data + 5] & 0x7f) << 1));
+                    bytes[byteIndex + offset_byte + 9] =
+                        (byte)(((data[offset_data + 5] >> 7) & 0x3f) | ((data[offset_data + 6] & 0x03) << 6));
+                    bytes[byteIndex + offset_byte + 10] = (byte)((data[offset_data + 6] >> 2) & 0xff);
+                    bytes[byteIndex + offset_byte + 11] =
+                        (byte)(((data[offset_data + 6] >> 10) & 0x07) | ((data[offset_data + 7] & 0x1f) << 3));
+                    bytes[byteIndex + offset_byte + 12] = (byte)((data[offset_data + 7] >> 5) & 0xff);
+                }
+            }
+            else
+            {
+                for (j = 0; j < SABER_N / 2; j++)
+                {
+                    offset_byte = (short) (3 * j);
+                    offset_data = (short) (2 * j);
+                    bytes[byteIndex + offset_byte + 0] = (byte) (data[offset_data + 0] & (0xff));
+                    bytes[byteIndex + offset_byte + 1] = (byte) (((data[offset_data + 0] >> 8) & 0xf) | ((data[offset_data + 1] & 0xf) << 4));
+                    bytes[byteIndex + offset_byte + 2] = (byte) ((data[offset_data + 1] >> 4) & 0xff);
+                }
             }
         }
 
         private void BS2POLq(byte[] bytes, int byteIndex, short[] data)
         {
             short j, offset_byte, offset_data;
-            for (j = 0; j < SABER_N / 8; j++)
+            if (!usingEffectiveMasking)
             {
-                offset_byte = (short) (13 * j);
-                offset_data = (short) (8 * j);
-                data[offset_data + 0] = (short) ((bytes[byteIndex + offset_byte + 0] & (0xff)) |
-                                                 ((bytes[byteIndex + offset_byte + 1] & 0x1f) << 8));
-                data[offset_data + 1] = (short) ((bytes[byteIndex + offset_byte + 1] >> 5 & (0x07)) |
-                                                 ((bytes[byteIndex + offset_byte + 2] & 0xff) << 3) |
-                                                 ((bytes[byteIndex + offset_byte + 3] & 0x03) << 11));
-                data[offset_data + 2] = (short) ((bytes[byteIndex + offset_byte + 3] >> 2 & (0x3f)) |
-                                                 ((bytes[byteIndex + offset_byte + 4] & 0x7f) << 6));
-                data[offset_data + 3] = (short) ((bytes[byteIndex + offset_byte + 4] >> 7 & (0x01)) |
-                                                 ((bytes[byteIndex + offset_byte + 5] & 0xff) << 1) |
-                                                 ((bytes[byteIndex + offset_byte + 6] & 0x0f) << 9));
-                data[offset_data + 4] = (short) ((bytes[byteIndex + offset_byte + 6] >> 4 & (0x0f)) |
-                                                 ((bytes[byteIndex + offset_byte + 7] & 0xff) << 4) |
-                                                 ((bytes[byteIndex + offset_byte + 8] & 0x01) << 12));
-                data[offset_data + 5] = (short) ((bytes[byteIndex + offset_byte + 8] >> 1 & (0x7f)) |
-                                                 ((bytes[byteIndex + offset_byte + 9] & 0x3f) << 7));
-                data[offset_data + 6] = (short) ((bytes[byteIndex + offset_byte + 9] >> 6 & (0x03)) |
-                                                 ((bytes[byteIndex + offset_byte + 10] & 0xff) << 2) |
-                                                 ((bytes[byteIndex + offset_byte + 11] & 0x07) << 10));
-                data[offset_data + 7] = (short) ((bytes[byteIndex + offset_byte + 11] >> 3 & (0x1f)) |
-                                                 ((bytes[byteIndex + offset_byte + 12] & 0xff) << 5));
+                for (j = 0; j < SABER_N / 8; j++)
+                {
+                    offset_byte = (short)(13 * j);
+                    offset_data = (short)(8 * j);
+                    data[offset_data + 0] = (short)((bytes[byteIndex + offset_byte + 0] & (0xff)) |
+                                                    ((bytes[byteIndex + offset_byte + 1] & 0x1f) << 8));
+                    data[offset_data + 1] = (short)((bytes[byteIndex + offset_byte + 1] >> 5 & (0x07)) |
+                                                    ((bytes[byteIndex + offset_byte + 2] & 0xff) << 3) |
+                                                    ((bytes[byteIndex + offset_byte + 3] & 0x03) << 11));
+                    data[offset_data + 2] = (short)((bytes[byteIndex + offset_byte + 3] >> 2 & (0x3f)) |
+                                                    ((bytes[byteIndex + offset_byte + 4] & 0x7f) << 6));
+                    data[offset_data + 3] = (short)((bytes[byteIndex + offset_byte + 4] >> 7 & (0x01)) |
+                                                    ((bytes[byteIndex + offset_byte + 5] & 0xff) << 1) |
+                                                    ((bytes[byteIndex + offset_byte + 6] & 0x0f) << 9));
+                    data[offset_data + 4] = (short)((bytes[byteIndex + offset_byte + 6] >> 4 & (0x0f)) |
+                                                    ((bytes[byteIndex + offset_byte + 7] & 0xff) << 4) |
+                                                    ((bytes[byteIndex + offset_byte + 8] & 0x01) << 12));
+                    data[offset_data + 5] = (short)((bytes[byteIndex + offset_byte + 8] >> 1 & (0x7f)) |
+                                                    ((bytes[byteIndex + offset_byte + 9] & 0x3f) << 7));
+                    data[offset_data + 6] = (short)((bytes[byteIndex + offset_byte + 9] >> 6 & (0x03)) |
+                                                    ((bytes[byteIndex + offset_byte + 10] & 0xff) << 2) |
+                                                    ((bytes[byteIndex + offset_byte + 11] & 0x07) << 10));
+                    data[offset_data + 7] = (short)((bytes[byteIndex + offset_byte + 11] >> 3 & (0x1f)) |
+                                                    ((bytes[byteIndex + offset_byte + 12] & 0xff) << 5));
+                }
+            }
+            else
+            {
+                for (j = 0; j < SABER_N / 2; j++)
+                {
+                    offset_byte = (short) (3 * j);
+                    offset_data = (short) (2 * j);
+                    data[offset_data + 0] = (short) ((bytes[byteIndex + offset_byte + 0] & (0xff)) | ((bytes[byteIndex + offset_byte + 1] & 0xf) << 8));
+                    data[offset_data + 1] = (short) ((bytes[byteIndex + offset_byte + 1] >> 4 & (0xf)) | ((bytes[byteIndex + offset_byte + 2] & 0xff) << 4));
+                }
             }
         }
 
@@ -271,4 +298,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Saber
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/saber/Symmetric.cs b/crypto/src/pqc/crypto/saber/Symmetric.cs
new file mode 100644
index 000000000..bcbe8a9a3
--- /dev/null
+++ b/crypto/src/pqc/crypto/saber/Symmetric.cs
@@ -0,0 +1,99 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Saber
+{
+
+    public abstract class Symmetric
+    {
+
+        internal abstract void Hash_h(byte[] output, byte[] input, int outputOffset);
+
+        internal abstract void Hash_g(byte[] output, byte[] input);
+
+        internal abstract void Prf(byte[] output, byte[] input, int inLen, int outputLen);
+
+        protected internal class ShakeSymmetric
+            : Symmetric
+        {
+
+            private readonly Sha3Digest sha3Digest256;
+            private readonly Sha3Digest sha3Digest512;
+            private readonly IXof shakeDigest;
+
+            internal ShakeSymmetric()
+            {
+                shakeDigest = new ShakeDigest(128);
+                sha3Digest256 = new Sha3Digest(256);
+                sha3Digest512 = new Sha3Digest(512);
+            }
+
+            internal override void Hash_h(byte[] output, byte[] input, int outputOffset)
+            {
+                sha3Digest256.BlockUpdate(input, 0, input.Length);
+                sha3Digest256.DoFinal(output, outputOffset);
+            }
+
+            internal override void Hash_g(byte[] output, byte[] input)
+            {
+                sha3Digest512.BlockUpdate(input, 0, input.Length);
+                sha3Digest512.DoFinal(output, 0);
+            }
+
+            internal override void Prf(byte[] output, byte[] input, int inLen, int outputLen)
+            {
+                shakeDigest.Reset();
+                shakeDigest.BlockUpdate(input, 0, inLen);
+                shakeDigest.OutputFinal(output, 0, outputLen);
+            }
+
+
+        }
+
+        internal class AesSymmetric
+            : Symmetric
+        {
+
+            private readonly Sha256Digest sha256Digest;
+            private readonly Sha512Digest sha512Digest;
+
+            private readonly SicBlockCipher cipher;
+
+
+            protected internal AesSymmetric()
+            {
+                sha256Digest = new Sha256Digest();
+                sha512Digest = new Sha512Digest();
+                cipher = new SicBlockCipher(AesUtilities.CreateEngine());
+            }
+
+            internal override void Hash_h(byte[] output, byte[] input, int outputOffset)
+            {
+                sha256Digest.BlockUpdate(input, 0, input.Length);
+                sha256Digest.DoFinal(output, outputOffset);
+            }
+
+            internal override void Hash_g(byte[] output, byte[] input)
+            {
+                sha512Digest.BlockUpdate(input, 0, input.Length);
+                sha512Digest.DoFinal(output, 0);
+            }
+
+            internal override void Prf(byte[] output, byte[] input, int inLen, int outputLen)
+            {
+                ParametersWithIV kp = new ParametersWithIV(new KeyParameter(input, 0, inLen), new byte[16]);
+                cipher.Init(true, kp);
+                byte[] buf = new byte[outputLen]; // TODO: there might be a more efficient way of doing this...
+                for (int i = 0; i < outputLen; i += 16)
+                {
+                    cipher.ProcessBlock(buf, i, output, i);
+                }
+            }
+
+
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/Fpx.cs b/crypto/src/pqc/crypto/sike/Fpx.cs
index d2ec32f89..140b36afb 100644
--- a/crypto/src/pqc/crypto/sike/Fpx.cs
+++ b/crypto/src/pqc/crypto/sike/Fpx.cs
@@ -1,4 +1,3 @@
-using System;
 using System.Diagnostics;
 #if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
 using System.Runtime.CompilerServices;
@@ -8,11 +7,11 @@ using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-    public class Fpx
+    internal sealed class Fpx
     {
-        private SIKEEngine engine;
+        private readonly SikeEngine engine;
 
-        internal Fpx(SIKEEngine engine)
+        internal Fpx(SikeEngine engine)
         {
             this.engine = engine;
         }
@@ -30,7 +29,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Cyclotomic squaring on elements of norm 1, using a^(p+1) = 1.
-        protected internal void sqr_Fp2_cycl(ulong[][] a, ulong[] one)
+        internal void sqr_Fp2_cycl(ulong[][] a, ulong[] one)
         {
             ulong[] t0 = new ulong[engine.param.NWORDS_FIELD];
 
@@ -45,9 +44,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // n-way simultaneous inversion using Montgomery's trick.
         // SECURITY NOTE: This function does not run in constant time.
         // Also, vec and out CANNOT be the same variable!
-        protected internal void mont_n_way_inv(ulong[][][] vec, uint n, ulong[][][] output)
+        internal void mont_n_way_inv(ulong[][][] vec, uint n, ulong[][][] output)
         {
-            ulong[][] t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            ulong[][] t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
             int i;
 
             fp2copy(vec[0], output[0]);                      // output[0] = vec[0]
@@ -69,7 +68,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
 
         // Copy a field element, c = a.
-        protected internal void fpcopy(ulong[] a, long aOffset, ulong[] c)
+        internal void fpcopy(ulong[] a, long aOffset, ulong[] c)
         {
             for (uint i = 0; i < engine.param.NWORDS_FIELD; i++)
             {
@@ -78,21 +77,21 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // GF(p^2) addition without correction, c = a+b in GF(p^2).
-        protected internal void mp2_add(ulong[][] a, ulong[][] b, ulong[][] c)
+        internal void mp2_add(ulong[][] a, ulong[][] b, ulong[][] c)
         {
             mp_add(a[0], b[0], c[0], engine.param.NWORDS_FIELD);
             mp_add(a[1], b[1], c[1], engine.param.NWORDS_FIELD);
         }
 
         // Modular correction, a = a in GF(p^2).
-        protected internal void fp2correction(ulong[][] a)
+        internal void fp2correction(ulong[][] a)
         {
             fpcorrectionPRIME(a[0]);
             fpcorrectionPRIME(a[1]);
         }
 
         // Multiprecision addition, c = a+b, where lng(a) = lng(b) = nwords. Returns the carry bit.
-        protected internal ulong mp_add(ulong[] a, ulong[] b, ulong[] c, uint nwords)
+        internal ulong mp_add(ulong[] a, ulong[] b, ulong[] c, uint nwords)
         {
             ulong carry = 0;
 
@@ -170,7 +169,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Is x = 0? return 1 (TRUE) if condition is true, 0 (FALSE) otherwise.
         // SECURITY NOTE: This function does not run in constant-time.
-        protected internal bool is_felm_zero(ulong[] x)
+        internal bool is_felm_zero(ulong[] x)
         {
             for (uint i = 0; i < engine.param.NWORDS_FIELD; i++)
             {
@@ -206,7 +205,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Test if a is a square in GF(p^2) and return 1 if true, 0 otherwise
         // If a is a quadratic residue, s will be assigned with a partially computed square root of a
-        protected internal bool is_sqr_fp2(ulong[][] a, ulong[] s)
+        internal bool is_sqr_fp2(ulong[][] a, ulong[] s)
         {
             uint i;
             ulong[] a0 = new ulong[engine.param.NWORDS_FIELD],
@@ -366,9 +365,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // GF(p^2) inversion using Montgomery arithmetic, a = (a0-i*a1)/(a0^2+a1^2)
         // This uses the binary GCD for inversion in fp and is NOT constant time!!!
-        protected internal void fp2inv_mont_bingcd(ulong[][] a)
+        internal void fp2inv_mont_bingcd(ulong[][] a)
         {
-            ulong[][] t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            ulong[][] t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
             fpsqr_mont(a[0], t1[0]);             // t10 = a0^2
             fpsqr_mont(a[1], t1[1]);             // t11 = a1^2
@@ -382,7 +381,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // GF(p^2) division by two, c = a/2  in GF(p^2).
-        protected internal void fp2div2(ulong[][] a, ulong[][] c)
+        internal void fp2div2(ulong[][] a, ulong[][] c)
         {
             //todo/org : make fp class and change this to generic fpdiv2
             fpdiv2_PRIME(a[0], c[0]);
@@ -607,7 +606,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             Debug.Assert(t == 0);
         }
 
-        protected internal static bool subarrayEquals(ulong[] a, ulong[] b, uint length)
+        internal static bool subarrayEquals(ulong[] a, ulong[] b, uint length)
         {
     //        if(a.Length < length || b.Length < length)
     //            return false;
@@ -620,7 +619,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             return true;
         }
 
-        protected internal static bool subarrayEquals(ulong[][] a, ulong[][] b, uint length)
+        internal static bool subarrayEquals(ulong[][] a, ulong[][] b, uint length)
         {
             int nwords_feild = b[0].Length;
     //        if(a[0].Length < length || b[0].Length < length)
@@ -634,7 +633,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             return true;
         }
 
-        protected internal static bool subarrayEquals(ulong[][] a, ulong[][] b, uint bOffset, uint length)
+        internal static bool subarrayEquals(ulong[][] a, ulong[][] b, uint bOffset, uint length)
         {
             int nwords_feild = b[0].Length;
     //        if(a[0].Length*2 < length || b[0].Length*2 < length)
@@ -648,7 +647,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             return true;
         }
 
-        protected internal static bool subarrayEquals(ulong[][] a, ulong[] b, uint bOffset, uint length)
+        internal static bool subarrayEquals(ulong[][] a, ulong[] b, uint bOffset, uint length)
         {
             int nwords_field = a[0].Length;
     //        if(a[0].Length < length || b.Length < length)
@@ -663,9 +662,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Computes square roots of elements in (Fp2)^2 using Hamburg's trick.
-        protected internal void sqrt_Fp2(ulong[][] u, ulong[][] y)
+        internal void sqrt_Fp2(ulong[][] u, ulong[][] y)
         {
-
             ulong[] t0 = new ulong[engine.param.NWORDS_FIELD],
                    t1 = new ulong[engine.param.NWORDS_FIELD],
                    t2 = new ulong[engine.param.NWORDS_FIELD],
@@ -715,7 +713,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // GF(p^2) squaring using Montgomery arithmetic, c = a^2 in GF(p^2).
         // Inputs: a = a0+a1*i, where a0, a1 are in [0, 2*p-1]
         // Output: c = c0+c1*i, where c0, c1 are in [0, 2*p-1]
-        protected internal void fp2sqr_mont(ulong[][] a, ulong[][] c)
+        internal void fp2sqr_mont(ulong[][] a, ulong[][] c)
         {
             ulong[] t1 = new ulong[engine.param.NWORDS_FIELD],
                     t2 = new ulong[engine.param.NWORDS_FIELD],
@@ -732,7 +730,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // Modular addition, c = a+b mod PRIME.
         // Inputs: a, b in [0, 2*PRIME-1]
         // Output: c in [0, 2*PRIME-1]
-        protected internal void fpaddPRIME(ulong[] a, ulong[] b, ulong[] c)
+        internal void fpaddPRIME(ulong[] a, ulong[] b, ulong[] c)
         {
             ulong i, carry = 0;
             ulong mask;
@@ -770,7 +768,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Cyclotomic cubing on elements of norm 1, using a^(p+1) = 1.
-        protected internal void cube_Fp2_cycl(ulong[][] a, ulong[] one)
+        internal void cube_Fp2_cycl(ulong[][] a, ulong[] one)
         {
             ulong[] t0 = new ulong[engine.param.NWORDS_FIELD];
 
@@ -787,7 +785,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // Modular subtraction, c = a-b mod PRIME.
         // Inputs: a, b in [0, 2*PRIME-1]
         // Output: c in [0, 2*PRIME-1]
-        protected internal void fpsubPRIME(ulong[] a, ulong[] b, uint bOffset, ulong[] c)
+        internal void fpsubPRIME(ulong[] a, ulong[] b, uint bOffset, ulong[] c)
         {
             ulong i, borrow = 0;
             ulong mask;
@@ -812,7 +810,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             }
         }
 
-        protected internal void fpsubPRIME(ulong[] a, uint aOffset, ulong[] b, ulong[] c)
+        internal void fpsubPRIME(ulong[] a, uint aOffset, ulong[] b, ulong[] c)
         {
             ulong i, borrow = 0;
             ulong mask;
@@ -837,7 +835,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             }
         }
 
-        protected internal void fpsubPRIME(ulong[] a, ulong[] b, ulong[] c)
+        internal void fpsubPRIME(ulong[] a, ulong[] b, ulong[] c)
         {
             ulong i, borrow = 0;
             ulong mask;
@@ -865,7 +863,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // todo/org : move to fp_generic
         // Modular negation, a = -a mod PRIME.
         // Input/output: a in [0, 2*PRIME-1]
-        protected internal void fpnegPRIME(ulong[] a)
+        internal void fpnegPRIME(ulong[] a)
         {
             ulong i, borrow = 0;
 
@@ -882,7 +880,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // todo/org : move to fp_generic
         // Conversion of a GF(p^2) element from Montgomery representation to standard representation,
         // c_i = ma_i*R^(-1) = a_i in GF(p^2).
-        protected internal void from_fp2mont(ulong[][] ma, ulong[][] c)
+        internal void from_fp2mont(ulong[][] ma, ulong[][] c)
         {
             from_mont(ma[0], c[0]);
             from_mont(ma[1], c[1]);
@@ -890,9 +888,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // todo/org : move to fp_generic
         // Conversion of GF(p^2) element from Montgomery to standard representation, and encoding by removing leading 0 bytes
-        protected internal void fp2_encode(ulong[][] x, byte[] enc, uint encOffset)
+        internal void fp2_encode(ulong[][] x, byte[] enc, uint encOffset)
         {
-            ulong[][] t = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            ulong[][] t = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
             from_fp2mont(x, t);
             encode_to_bytes(t[0], enc, encOffset,engine.param.FP2_ENCODED_BYTES / 2);
@@ -900,7 +898,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Parse byte sequence back uinto GF(p^2) element, and conversion to Montgomery representation
-        protected internal void fp2_decode(byte[] x, ulong[][] dec, uint xOffset)
+        internal void fp2_decode(byte[] x, ulong[][] dec, uint xOffset)
         {
             decode_to_digits(x, xOffset, dec[0], engine.param.FP2_ENCODED_BYTES / 2, engine.param.NWORDS_FIELD);
             decode_to_digits(x,xOffset + (engine.param.FP2_ENCODED_BYTES/2), dec[1], engine.param.FP2_ENCODED_BYTES / 2, engine.param.NWORDS_FIELD);
@@ -908,7 +906,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Conversion of elements in Z_r to Montgomery representation, where the order r is up to NBITS_ORDER bits.
-        protected internal void to_Montgomery_mod_order(ulong[] a, ulong[] mc, ulong[] order, ulong[] Montgomery_rprime, ulong[] Montgomery_Rprime)
+        internal void to_Montgomery_mod_order(ulong[] a, ulong[] mc, ulong[] order, ulong[] Montgomery_rprime, ulong[] Montgomery_Rprime)
         {
             Montgomery_multiply_mod_order(a, Montgomery_Rprime, mc, order, Montgomery_rprime);
         }
@@ -917,7 +915,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // ma, mb and mc are assumed to be in Montgomery representation.
         // The Montgomery constant r' = -r^(-1) mod 2^(log_2(r)) is the value "Montgomery_rprime", where r is the order.
         // Assume log_2(r) is a multiple of RADIX bits
-        protected internal void Montgomery_multiply_mod_order(ulong[] ma, ulong[] mb, ulong[] mc, ulong[] order, ulong[] Montgomery_rprime)
+        internal void Montgomery_multiply_mod_order(ulong[] ma, ulong[] mb, ulong[] mc, ulong[] order, ulong[] Montgomery_rprime)
         {
             ulong i, cout = 0, bout = 0;
             ulong mask;
@@ -950,7 +948,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // Inversion of an odd uinteger modulo an even uinteger of the form 2^m.
         // Algorithm 3: Explicit Quadratic Modular inverse modulo 2^m from Dumas'12: http://arxiv.org/pdf/1209.6626.pdf
         // If the input is invalid (even), the function outputs c = a.
-        protected internal void inv_mod_orderA(ulong[] a, ulong[] c)
+        internal void inv_mod_orderA(ulong[] a, ulong[] c)
         { 
             uint i, f, s = 0;
             ulong[] am1 = new ulong[engine.param.NWORDS_ORDER],
@@ -1000,7 +998,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Multiprecision comba multiply, c = a*b, where lng(a) = lng(b) = nwords.
         // NOTE: a and c CANNOT be the same variable!
-        protected internal void multiply(ulong[] a, ulong[] b, ulong[] c, uint nwords)
+        internal void multiply(ulong[] a, ulong[] b, ulong[] c, uint nwords)
         {
             ulong i, j;
             ulong t = 0, u = 0, v = 0;
@@ -1188,7 +1186,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Montgomery inversion modulo order, c = a^(-1)*R mod order.
-        protected internal void Montgomery_inversion_mod_order_bingcd(ulong[] a, ulong[] c, ulong[] order, ulong[] Montgomery_rprime, ulong[] Montgomery_Rprime)
+        internal void Montgomery_inversion_mod_order_bingcd(ulong[] a, ulong[] c, ulong[] order, ulong[] Montgomery_rprime, ulong[] Montgomery_Rprime)
         {
             ulong[] x = new ulong[engine.param.NWORDS_ORDER],
                    t = new ulong[engine.param.NWORDS_ORDER];
@@ -1212,7 +1210,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Conversion of elements in Z_r from Montgomery to standard representation, where the order is up to NBITS_ORDER bits.
-        protected internal void from_Montgomery_mod_order(ulong[] ma, ulong[] c, ulong[] order, ulong[] Montgomery_rprime)
+        internal void from_Montgomery_mod_order(ulong[] ma, ulong[] c, ulong[] order, ulong[] Montgomery_rprime)
         {
             ulong[] one = new ulong[engine.param.NWORDS_ORDER];
             one[0] = 1;
@@ -1222,7 +1220,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Computes the input modulo 3
         // The input is assumed to be NWORDS_ORDER ulong
-        protected internal uint mod3(ulong[] a)
+        internal uint mod3(ulong[] a)
         {
             ulong result = 0;
             for (int i = 0; i < engine.param.NWORDS_ORDER; ++i)
@@ -1235,7 +1233,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Conversion of a GF(p^2) element to Montgomery representation,
         // mc_i = a_i*R^2*R^(-1) = a_i*R in GF(p^2).
-        protected internal void to_fp2mont(ulong[][] a, ulong[][] mc)
+        internal void to_fp2mont(ulong[][] a, ulong[][] mc)
         {
             to_mont(a[0], mc[0]);
             to_mont(a[1], mc[1]);
@@ -1252,7 +1250,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // todo/org : move to fp_generic
         // Modular correction to reduce field element a in [0, 2*PRIME-1] to [0, PRIME-1].
-        protected internal void fpcorrectionPRIME(ulong[] a)
+        internal void fpcorrectionPRIME(ulong[] a)
         {
             ulong i, borrow = 0;
             ulong mask;
@@ -1277,11 +1275,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             }
         }
 
-        protected internal byte cmp_f2elm(ulong[][] x, ulong[][] y)
+        internal byte cmp_f2elm(ulong[][] x, ulong[][] y)
         { // Comparison of two GF(p^2) elements in constant time. 
             // Is x != y? return -1 if condition is true, 0 otherwise.
-            ulong[][] a = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-                b = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            ulong[][] a = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+                b = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
             byte r = 0;
 
             fp2copy(x, a);
@@ -1299,7 +1297,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
 
         // Encoding digits to bytes according to endianness
-        protected internal void encode_to_bytes(ulong[] x, byte[] enc, uint encOffset, uint nbytes)
+        internal void encode_to_bytes(ulong[] x, byte[] enc, uint encOffset, uint nbytes)
         {
             byte[] test = new byte[((nbytes*4+7)&~7)];
             Pack.UInt64_To_LE(x, test, 0);
@@ -1307,7 +1305,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Decoding bytes to digits according to endianness
-        protected internal void decode_to_digits(byte[] x, uint xOffset, ulong[] dec, uint nbytes, uint ndigits)
+        internal void decode_to_digits(byte[] x, uint xOffset, ulong[] dec, uint nbytes, uint ndigits)
         {
             // x -> dec
             dec[ndigits - 1] = 0;
@@ -1318,7 +1316,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // r = a - b*i where v = a + b*i
-        protected internal void fp2_conj(ulong[][] v, ulong[][] r)
+        internal void fp2_conj(ulong[][] v, ulong[][] r)
         {
             fpcopy(v[0], 0, r[0]);
             fpcopy(v[1], 0, r[1]);
@@ -1369,28 +1367,28 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Copy a GF(p^2) element, c = a.
-        protected internal void fp2copy(ulong[][] a, ulong[][] c)
+        internal void fp2copy(ulong[][] a, ulong[][] c)
         {
             fpcopy(a[0], 0, c[0]);
             fpcopy(a[1], 0, c[1]);
         }
 
         // Copy a GF(p^2) element, c = a.
-        protected internal void fp2copy(ulong[][] a, uint aOffset, ulong[][] c)
+        internal void fp2copy(ulong[][] a, uint aOffset, ulong[][] c)
         {
             fpcopy(a[0 + aOffset], 0, c[0]);
             fpcopy(a[1 + aOffset], 0, c[1]);
         }
 
         // Copy a GF(p^2) element, c = a.
-        protected internal void fp2copy(ulong[] a, uint aOffset, ulong[][] c)
+        internal void fp2copy(ulong[] a, uint aOffset, ulong[][] c)
         {
             fpcopy(a, aOffset, c[0]);
             fpcopy(a, aOffset + engine.param.NWORDS_FIELD, c[1]);
         }
 
         // Zero a field element, a = 0.
-        protected internal void fpzero(ulong[] a)
+        internal void fpzero(ulong[] a)
         {
             uint i;
 
@@ -1401,7 +1399,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // GF(p^2) subtraction with correction with 2*p, c = a-b+2p in GF(p^2).
-        protected internal void mp2_sub_p2(ulong[][] a, ulong[][] b, ulong[][] c)
+        internal void mp2_sub_p2(ulong[][] a, ulong[][] b, ulong[][] c)
         {
             //todo/org : make fp class and change this to generic mp_sub_p2
             mp_subPRIME_p2(a[0], b[0], c[0]);
@@ -1409,7 +1407,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Multiprecision comba multiply, c = a*b, where lng(a) = lng(b) = nwords.
-        protected internal void mp_mul(ulong[] a, ulong[] b, ulong[] c, uint nwords)
+        internal void mp_mul(ulong[] a, ulong[] b, ulong[] c, uint nwords)
         {
             ulong i, j;
             ulong t = 0, u = 0, v = 0;
@@ -1462,7 +1460,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Multiprecision comba multiply, c = a*b, where lng(a) = lng(b) = nwords.
-        protected internal void mp_mul(ulong[] a, uint aOffset, ulong[] b, ulong[] c, uint nwords)
+        internal void mp_mul(ulong[] a, uint aOffset, ulong[] b, ulong[] c, uint nwords)
         {
             ulong i, j;
             ulong t = 0, u = 0, v = 0;
@@ -1517,7 +1515,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         // GF(p^2) multiplication using Montgomery arithmetic, c = a*b in GF(p^2).
         // Inputs: a = a0+a1*i and b = b0+b1*i, where a0, a1, b0, b1 are in [0, 2*p-1]
         // Output: c = c0+c1*i, where c0, c1 are in [0, 2*p-1]
-        protected internal void fp2mul_mont(ulong[][] a, ulong[][] b, ulong[][] c)
+        internal void fp2mul_mont(ulong[][] a, ulong[][] b, ulong[][] c)
         {
             ulong[] t1 = new ulong[engine.param.NWORDS_FIELD],
                    t2 = new ulong[engine.param.NWORDS_FIELD];
@@ -1536,7 +1534,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             rdc_mont(tt1, c[0]);                             // c[0] = a0*b0 - a1*b1
         }
 
-        protected internal void fp2mul_mont(ulong[][] a, ulong[][] b, uint bOffset, ulong[][] c)
+        internal void fp2mul_mont(ulong[][] a, ulong[][] b, uint bOffset, ulong[][] c)
         {
 
     //        System.out.pruint("b: ");
@@ -1560,7 +1558,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             rdc_mont(tt1, c[0]);                             // c[0] = a0*b0 - a1*b1
         }
 
-        protected internal void fp2mul_mont(ulong[][] a, ulong[] b, uint bOffset, ulong[][] c)
+        internal void fp2mul_mont(ulong[][] a, ulong[] b, uint bOffset, ulong[][] c)
         {
             ulong[] t1 = new ulong[engine.param.NWORDS_FIELD],
                     t2 = new ulong[engine.param.NWORDS_FIELD];
@@ -1588,7 +1586,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Multiprecision subtraction, c = a-b, where lng(a) = lng(b) = nwords. Returns the borrow bit.
-        protected internal ulong mp_sub(ulong[] a, ulong[] b, ulong[] c, uint nwords)
+        internal ulong mp_sub(ulong[] a, ulong[] b, ulong[] c, uint nwords)
         {
             ulong i, borrow = 0;
         
@@ -1607,7 +1605,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Is x < y? return 1 (TRUE) if condition is true, 0 (FALSE) otherwise.
         // SECURITY NOTE: This function does not run in constant-time.
-        protected internal bool is_orderelm_lt(ulong[] x, ulong[] y)
+        internal bool is_orderelm_lt(ulong[] x, ulong[] y)
         {
             for (int i = (int)engine.param.NWORDS_ORDER-1; i >= 0; i--)
             {
@@ -1638,7 +1636,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Multiprecision squaring, c = a^2 mod p.
-        protected internal void fpsqr_mont(ulong[] ma, ulong[] mc)
+        internal void fpsqr_mont(ulong[] ma, ulong[] mc)
         {
             ulong[] temp = new ulong[2*engine.param.NWORDS_FIELD];
 
@@ -1659,9 +1657,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // GF(p^2) inversion using Montgomery arithmetic, a = (a0-i*a1)/(a0^2+a1^2).
-        protected internal void fp2inv_mont(ulong[][] a)
+        internal void fp2inv_mont(ulong[][] a)
         {
-            ulong[][] t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            ulong[][] t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
             fpsqr_mont(a[0], t1[0]);                 // t10 = a0^2
             fpsqr_mont(a[1], t1[1]);                 // t11 = a1^2
@@ -1674,7 +1672,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Computes a = 3*a
         // The input is assumed to be OBOB_BITS-2 bits ulong and stored in SECRETKEY_B_BYTES
-        protected internal void mul3(byte[] a)
+        internal void mul3(byte[] a)
         {
             ulong[] temp1 = new ulong[engine.param.NWORDS_ORDER],
                    temp2 = new ulong[engine.param.NWORDS_ORDER];
@@ -1687,7 +1685,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Compare two byte arrays in constant time.
         // Returns 0 if the byte arrays are equal, -1 otherwise.
-        protected internal byte ct_compare(byte[] a, byte[] b, uint len)
+        internal byte ct_compare(byte[] a, byte[] b, uint len)
         {
             byte r = 0;
 
@@ -1700,7 +1698,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
         // Conditional move in constant time.
         // If selector = -1 then load r with a, else if selector = 0 then keep r.
-        protected internal void ct_cmov(byte[] r, byte[] a, uint len, byte selector)
+        internal void ct_cmov(byte[] r, byte[] a, uint len, byte selector)
         {
             for (uint i = 0; i < len; i++)
             {
@@ -1709,7 +1707,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Copy wordsize digits, c = a, where lng(a) = nwords.
-        protected internal void copy_words(ulong[] a, ulong[] c, uint nwords)
+        internal void copy_words(ulong[] a, ulong[] c, uint nwords)
         {
             uint i;
             for (i = 0; i < nwords; i++)
@@ -1719,7 +1717,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // c = (2^k)*a
-        protected internal void fp2shl(ulong[][] a, uint k, ulong[][] c)
+        internal void fp2shl(ulong[][] a, uint k, ulong[][] c)
         {
             fp2copy(a, c);
             for (uint j = 0; j < k; j++)
@@ -1729,7 +1727,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Copy wordsize digits, c = a, where lng(a) = nwords.
-        protected internal void copy_words(PointProj a, PointProj c)
+        internal void copy_words(PointProj a, PointProj c)
         {
             uint i;
             for (i = 0; i < engine.param.NWORDS_FIELD; i++)
@@ -1760,7 +1758,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
 
         // GF(p^2) addition, c = a+b in GF(p^2).
-        protected internal void fp2add(ulong[][] a, ulong[][] b, ulong[][] c)
+        internal void fp2add(ulong[][] a, ulong[][] b, ulong[][] c)
         {
             //todo/org : make fp class and change this to generic function
             fpaddPRIME(a[0], b[0], c[0]);
@@ -1768,7 +1766,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // GF(p^2) subtraction, c = a-b in GF(p^2).
-        protected internal void fp2sub(ulong[][] a, ulong[][] b, ulong[][] c)
+        internal void fp2sub(ulong[][] a, ulong[][] b, ulong[][] c)
         {
             //todo/org : make fp class and change this to generic function
             fpsubPRIME(a[0], b[0], c[0]);
@@ -1784,7 +1782,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Multiprecision multiplication, c = a*b mod p.
-        protected internal void fpmul_mont(ulong[] ma, ulong[] mb, ulong[] mc)
+        internal void fpmul_mont(ulong[] ma, ulong[] mb, ulong[] mc)
         {
             ulong[] temp = new ulong[2*engine.param.NWORDS_FIELD];
 
@@ -1793,7 +1791,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
         }
 
         // Multiprecision multiplication, c = a*b mod p.
-        protected internal void fpmul_mont(ulong[] ma, uint maOffset, ulong[] mb, ulong[] mc)
+        internal void fpmul_mont(ulong[] ma, uint maOffset, ulong[] mb, ulong[] mc)
         {
             ulong[] temp = new ulong[2*engine.param.NWORDS_FIELD];
 
@@ -1808,7 +1806,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             if (engine.param.NBITS_FIELD == 434)
             {
                 ulong[] tt = new ulong[engine.param.NWORDS_FIELD];
-                ulong[][] t =  Utils.InitArray(31, engine.param.NWORDS_FIELD);
+                ulong[][] t =  SikeUtilities.InitArray(31, engine.param.NWORDS_FIELD);
 
                 // Precomputed table
                 fpsqr_mont(a, tt);
@@ -1888,7 +1886,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
             if (engine.param.NBITS_FIELD == 503)
             {
-                ulong[][] t = Utils.InitArray(15, engine.param.NWORDS_FIELD);
+                ulong[][] t = SikeUtilities.InitArray(15, engine.param.NWORDS_FIELD);
                 ulong[] tt = new ulong[engine.param.NWORDS_FIELD];
 
                 // Precomputed table
@@ -1991,7 +1989,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
             if (engine.param.NBITS_FIELD == 610)
             {   
-                ulong[][] t = Utils.InitArray(31, engine.param.NWORDS_FIELD); 
+                ulong[][] t = SikeUtilities.InitArray(31, engine.param.NWORDS_FIELD); 
                 ulong[] tt = new ulong[engine.param.NWORDS_FIELD];
 
                 // Precomputed table
@@ -2096,7 +2094,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
             if (engine.param.NBITS_FIELD == 751)
             {
-                ulong[][] t = Utils.InitArray(27, engine.param.NWORDS_FIELD);
+                ulong[][] t = SikeUtilities.InitArray(27, engine.param.NWORDS_FIELD);
                 ulong[] tt = new ulong[engine.param.NWORDS_FIELD];
 
                 // Precomputed table
@@ -2234,5 +2232,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             }
         }
     }
-
-    }
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sike/Internal.cs b/crypto/src/pqc/crypto/sike/Internal.cs
index edd551a2b..35b1a46e8 100644
--- a/crypto/src/pqc/crypto/sike/Internal.cs
+++ b/crypto/src/pqc/crypto/sike/Internal.cs
@@ -1,217 +1,214 @@
 using System;
 using System.Collections.Generic;
+
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
+    internal abstract class Internal
+    {
+        protected static Dictionary<string, string> _props;
                 
-        public abstract class Internal
+        protected internal static uint RADIX = 64;
+        protected internal static uint LOG2RADIX = 6;
+
+        protected internal uint CRYPTO_PUBLICKEYBYTES;
+        protected internal int CRYPTO_CIPHERTEXTBYTES;
+        protected internal uint CRYPTO_BYTES;
+        protected internal uint CRYPTO_SECRETKEYBYTES;
+
+
+
+        protected internal uint NWORDS_FIELD;     // Number of words of a n-bit field element
+        protected internal uint PRIME_ZERO_WORDS;  // Number of "0" digits in the least significant part of PRIME + 1
+        protected internal uint NBITS_FIELD;
+        protected internal uint MAXBITS_FIELD;
+        protected uint MAXWORDS_FIELD;   // Max. number of words to represent field elements
+        protected uint NWORDS64_FIELD;   // Number of 64-bit words of a 434-bit field element
+        protected internal uint NBITS_ORDER;
+        protected internal uint NWORDS_ORDER;     // Number of words of oA and oB, where oA and oB are the subgroup orders of Alice and Bob, resp.
+        protected uint NWORDS64_ORDER;   // Number of 64-bit words of a x-bit element
+        protected internal uint MAXBITS_ORDER;
+        protected internal uint ALICE;
+        protected internal uint BOB;
+        protected internal uint OALICE_BITS;
+        protected internal uint OBOB_BITS;
+        protected internal uint OBOB_EXPON;
+        protected internal uint MASK_ALICE;
+        protected internal uint MASK_BOB;
+        protected uint PARAM_A;
+        protected uint PARAM_C;
+
+        // Fixed parameters for isogeny tree computation
+        protected internal uint MAX_INT_POINTS_ALICE;
+        protected internal uint MAX_INT_POINTS_BOB;
+        protected internal uint MAX_Alice;
+        protected internal uint MAX_Bob;
+        protected internal uint MSG_BYTES;
+        protected internal uint SECRETKEY_A_BYTES;
+        protected internal uint SECRETKEY_B_BYTES;
+        protected internal uint FP2_ENCODED_BYTES;
+
+        protected bool COMPRESS;
+
+        // Compressed Parameters
+        protected internal uint MASK2_BOB;
+        protected internal uint MASK3_BOB;
+        protected internal uint ORDER_A_ENCODED_BYTES;
+        protected internal uint ORDER_B_ENCODED_BYTES;
+        protected internal uint PARTIALLY_COMPRESSED_CHUNK_CT;
+        protected uint COMPRESSED_CHUNK_CT;
+        protected uint UNCOMPRESSEDPK_BYTES;
+        // Table sizes used by the Entangled basis generation
+        protected uint TABLE_R_LEN;
+        protected internal uint TABLE_V_LEN;
+        protected uint TABLE_V3_LEN;
+        // Parameters for discrete log computations
+        // Binary Pohlig-Hellman reduced to smaller logs of order ell^W
+        protected internal uint W_2;
+        protected internal uint W_3;
+        // ell^w    
+        protected internal uint ELL2_W;
+        protected internal uint ELL3_W;
+        // ell^(e mod w)
+        protected internal uint ELL2_EMODW;
+        protected internal uint ELL3_EMODW;
+        // # of digits in the discrete log
+        protected internal uint DLEN_2; // ceil(eA/W_2)
+        protected internal uint DLEN_3; // ceil(eB/W_3)
+        // Use compressed tables: FULL_SIGNED
+
+
+        // Encoding of field elements
+        protected internal uint PLEN_2;
+        protected internal uint PLEN_3;
+
+        protected internal ulong[] PRIME;
+        protected internal ulong[] PRIMEx2;
+        protected internal ulong[] PRIMEx4;
+        protected internal ulong[] PRIMEp1;
+        protected ulong[] PRIMEx16p;
+        protected ulong[] PRIMEp1x64;
+        protected internal ulong[] Alice_order;        // Order of Alice's subgroup
+        protected internal ulong[] Bob_order;     // Order of Bob's subgroup
+        protected internal ulong[] A_gen;    // Alice's generator values {XPA0 + XPA1*iL, XQA0 + xQA1*iL, XRA0 + XRA1*i} in GF(p^2)L, expressed in Montgomery representation
+        protected internal ulong[] B_gen;    // Bob's generator values {XPB0L, XQB0L, XRB0 + XRB1*i} in GF(p^2)L, expressed in Montgomery representation
+        protected internal ulong[] Montgomery_R2;    // Montgomery constant Montgomery_R2 = (2^448)^2 mod p434
+        protected internal ulong[] Montgomery_one;    // Value one in Montgomery representation
+
+        // Fixed parameters for isogeny tree computation
+        protected internal uint[] strat_Alice;
+        protected internal uint[] strat_Bob;
+
+        //Compressed Encodings
+        //todo: abstract this more?
+        protected internal ulong[] XQB3;
+        protected internal ulong[] A_basis_zero;
+        protected ulong[] B_basis_zero;
+        protected internal ulong[] B_gen_3_tors;
+        protected internal ulong[] g_R_S_im;
+        protected ulong[] g_phiR_phiS_re;
+        protected ulong[] g_phiR_phiS_im;
+        protected ulong[] Montgomery_R;
+        protected internal ulong[] Montgomery_RB1;
+        protected internal ulong[] Montgomery_RB2;
+        protected ulong[] threeinv;
+        protected internal uint[] ph2_path;
+        protected internal uint[] ph3_path;
+        protected ulong[] u_entang;
+        protected ulong[] u0_entang;
+        protected internal ulong[][] table_r_qr;
+        protected internal ulong[][] table_r_qnr;
+        protected internal ulong[][] table_v_qr;
+        protected internal ulong[][] table_v_qnr;
+        protected internal ulong[][][] v_3_torsion;
+
+        protected internal ulong[] T_tate3;
+        protected internal ulong[] T_tate2_firststep_P;
+        protected internal ulong[] T_tate2_P;
+        protected internal ulong[] T_tate2_firststep_Q;
+        protected internal ulong[] T_tate2_Q;
+
+        ///Compressed Dlogs
+        protected internal ulong[] ph2_T;
+        protected internal ulong[] ph2_T1;
+        protected internal ulong[] ph2_T2;
+        protected internal ulong[] ph3_T;
+        protected internal ulong[] ph3_T1;
+        protected internal ulong[] ph3_T2;
+
+
+        static protected uint[] ReadIntsFromProperty(string key, uint intSize)
         {
-                protected static Dictionary<string, string> _props;
-                
-                protected internal static uint RADIX = 64;
-                protected internal static uint LOG2RADIX = 6;
-
-                protected internal uint CRYPTO_PUBLICKEYBYTES;
-                protected internal uint CRYPTO_CIPHERTEXTBYTES;
-                protected internal uint CRYPTO_BYTES;
-                protected internal uint CRYPTO_SECRETKEYBYTES;
-
-
-
-                protected internal uint NWORDS_FIELD;     // Number of words of a n-bit field element
-                protected internal uint PRIME_ZERO_WORDS;  // Number of "0" digits in the least significant part of PRIME + 1
-                protected internal uint NBITS_FIELD;
-                protected internal uint MAXBITS_FIELD;
-                protected uint MAXWORDS_FIELD;   // Max. number of words to represent field elements
-                protected uint NWORDS64_FIELD;   // Number of 64-bit words of a 434-bit field element
-                protected internal uint NBITS_ORDER;
-                protected internal uint NWORDS_ORDER;     // Number of words of oA and oB, where oA and oB are the subgroup orders of Alice and Bob, resp.
-                protected uint NWORDS64_ORDER;   // Number of 64-bit words of a x-bit element
-                protected internal uint MAXBITS_ORDER;
-                protected internal uint ALICE;
-                protected internal uint BOB;
-                protected internal uint OALICE_BITS;
-                protected internal uint OBOB_BITS;
-                protected internal uint OBOB_EXPON;
-                protected internal uint MASK_ALICE;
-                protected internal uint MASK_BOB;
-                protected uint PARAM_A;
-                protected uint PARAM_C;
-
-                // Fixed parameters for isogeny tree computation
-                protected internal uint MAX_INT_POINTS_ALICE;
-                protected internal uint MAX_INT_POINTS_BOB;
-                protected internal uint MAX_Alice;
-                protected internal uint MAX_Bob;
-                protected internal uint MSG_BYTES;
-                protected internal uint SECRETKEY_A_BYTES;
-                protected internal uint SECRETKEY_B_BYTES;
-                protected internal uint FP2_ENCODED_BYTES;
-
-                protected bool COMPRESS;
-
-                // Compressed Parameters
-                protected internal uint MASK2_BOB;
-                protected internal uint MASK3_BOB;
-                protected internal uint ORDER_A_ENCODED_BYTES;
-                protected internal uint ORDER_B_ENCODED_BYTES;
-                protected internal uint PARTIALLY_COMPRESSED_CHUNK_CT;
-                protected uint COMPRESSED_CHUNK_CT;
-                protected uint UNCOMPRESSEDPK_BYTES;
-                // Table sizes used by the Entangled basis generation
-                protected uint TABLE_R_LEN;
-                protected internal uint TABLE_V_LEN;
-                protected uint TABLE_V3_LEN;
-                // Parameters for discrete log computations
-                // Binary Pohlig-Hellman reduced to smaller logs of order ell^W
-                protected internal uint W_2;
-                protected internal uint W_3;
-                // ell^w    
-                protected internal uint ELL2_W;
-                protected internal uint ELL3_W;
-                // ell^(e mod w)
-                protected internal uint ELL2_EMODW;
-                protected internal uint ELL3_EMODW;
-                // # of digits in the discrete log
-                protected internal uint DLEN_2; // ceil(eA/W_2)
-                protected internal uint DLEN_3; // ceil(eB/W_3)
-                // Use compressed tables: FULL_SIGNED
-
-
-                // Encoding of field elements
-                protected internal uint PLEN_2;
-                protected internal uint PLEN_3;
-
-                protected internal ulong[] PRIME;
-                protected internal ulong[] PRIMEx2;
-                protected internal ulong[] PRIMEx4;
-                protected internal ulong[] PRIMEp1;
-                protected ulong[] PRIMEx16p;
-                protected ulong[] PRIMEp1x64;
-                protected internal ulong[] Alice_order;        // Order of Alice's subgroup
-                protected internal ulong[] Bob_order;     // Order of Bob's subgroup
-                protected internal ulong[] A_gen;    // Alice's generator values {XPA0 + XPA1*iL, XQA0 + xQA1*iL, XRA0 + XRA1*i} in GF(p^2)L, expressed in Montgomery representation
-                protected internal ulong[] B_gen;    // Bob's generator values {XPB0L, XQB0L, XRB0 + XRB1*i} in GF(p^2)L, expressed in Montgomery representation
-                protected internal ulong[] Montgomery_R2;    // Montgomery constant Montgomery_R2 = (2^448)^2 mod p434
-                protected internal ulong[] Montgomery_one;    // Value one in Montgomery representation
-
-                // Fixed parameters for isogeny tree computation
-                protected internal uint[] strat_Alice;
-                protected internal uint[] strat_Bob;
-
-                //Compressed Encodings
-                //todo: abstract this more?
-                protected internal ulong[] XQB3;
-                protected internal ulong[] A_basis_zero;
-                protected ulong[] B_basis_zero;
-                protected internal ulong[] B_gen_3_tors;
-                protected internal ulong[] g_R_S_im;
-                protected ulong[] g_phiR_phiS_re;
-                protected ulong[] g_phiR_phiS_im;
-                protected ulong[] Montgomery_R;
-                protected internal ulong[] Montgomery_RB1;
-                protected internal ulong[] Montgomery_RB2;
-                protected ulong[] threeinv;
-                protected internal uint[] ph2_path;
-                protected internal uint[] ph3_path;
-                protected ulong[] u_entang;
-                protected ulong[] u0_entang;
-                protected internal ulong[][] table_r_qr;
-                protected internal ulong[][] table_r_qnr;
-                protected internal ulong[][] table_v_qr;
-                protected internal ulong[][] table_v_qnr;
-                protected internal ulong[][][] v_3_torsion;
-
-                protected internal ulong[] T_tate3;
-                protected internal ulong[] T_tate2_firststep_P;
-                protected internal ulong[] T_tate2_P;
-                protected internal ulong[] T_tate2_firststep_Q;
-                protected internal ulong[] T_tate2_Q;
-
-                ///Compressed Dlogs
-                protected internal ulong[] ph2_T;
-                protected internal ulong[] ph2_T1;
-                protected internal ulong[] ph2_T2;
-                protected internal ulong[] ph3_T;
-                protected internal ulong[] ph3_T1;
-                protected internal ulong[] ph3_T2;
-
-
-                static protected uint[] ReadIntsFromProperty(string key, uint intSize)
-                {
-                        uint[] ints = new uint[intSize];
-                        string s = _props[key];
-                        uint i = 0;
-                        foreach (string number in s.Split(','))
-                        {
-                                ints[i] = UInt32.Parse(number);
-                                i++;
-                        }
-                        return ints;
-                }
+            uint[] ints = new uint[intSize];
+            string s = _props[key];
+            uint i = 0;
+            foreach (string number in s.Split(','))
+            {
+                ints[i] = UInt32.Parse(number);
+                i++;
+            }
+            return ints;
+        }
 
-                static protected ulong[] ReadFromProperty(string key, uint ulongSize)
-                {
-                        string s = _props[key];
-                        s = s.Replace(",", "");
-                        byte[] bytes = Hex.Decode(s);
-                        ulong[] ulongs = new ulong[ulongSize];
-                        for (int i = 0; i < bytes.Length / 8; i++)
-                        {
-                                ulongs[i] = Pack.BE_To_UInt64(bytes, i * 8);
-                        }
-                        return ulongs;
-                }
+        static protected ulong[] ReadFromProperty(string key, uint ulongSize)
+        {
+            string s = _props[key];
+            s = s.Replace(",", "");
+            byte[] bytes = Hex.Decode(s);
+            ulong[] ulongs = new ulong[ulongSize];
+            for (int i = 0; i < bytes.Length / 8; i++)
+            {
+                ulongs[i] = Pack.BE_To_UInt64(bytes, i * 8);
+            }
+            return ulongs;
+        }
 
-                static protected ulong[][] ReadFromProperty(string key, uint d1Size, uint d2Size)
-                {
-                        string s = _props[key];
-                        s = s.Replace(",", "");
-                        byte[] bytes = Hex.Decode(s);
-                        ulong[][] ulongs = new ulong[d1Size][]; //[d2Size];
-                        for (int k = 0; k < d1Size; k++)
-                        {
-                                ulongs[k] = new ulong[d2Size];
-                        }
-                        uint i, j;
-                        for (uint x = 0; x < bytes.Length / 8; x++)
-                        {
-                                i = x/d2Size;
-                                j = x%d2Size;
-                                ulongs[i][j] = Pack.BE_To_UInt64(bytes, (int)x * 8);
-                        }
-                        return ulongs;
-                }
+        static protected ulong[][] ReadFromProperty(string key, uint d1Size, uint d2Size)
+        {
+            string s = _props[key];
+            s = s.Replace(",", "");
+            byte[] bytes = Hex.Decode(s);
+            ulong[][] ulongs = new ulong[d1Size][]; //[d2Size];
+            for (int k = 0; k < d1Size; k++)
+            {
+                ulongs[k] = new ulong[d2Size];
+            }
+            uint i, j;
+            for (uint x = 0; x < bytes.Length / 8; x++)
+            {
+                i = x/d2Size;
+                j = x%d2Size;
+                ulongs[i][j] = Pack.BE_To_UInt64(bytes, (int)x * 8);
+            }
+            return ulongs;
+        }
 
-                static protected ulong[][][] ReadFromProperty(string key, uint d1Size, uint d2Size, uint d3Size)
+        static protected ulong[][][] ReadFromProperty(string key, uint d1Size, uint d2Size, uint d3Size)
+        {
+            string s = _props[key];
+            s = s.Replace(",", "");
+            byte[] bytes = Hex.Decode(s);
+            ulong[][][] ulongs = new ulong[d1Size][][]; //[d2Size][d3Size];
+            for (int l = 0; l < d1Size; l++)
+            {
+                ulongs[l] = new ulong[d2Size][];
+                for (int m = 0; m < d2Size; m++)
                 {
-                        string s = _props[key];
-                        s = s.Replace(",", "");
-                        byte[] bytes = Hex.Decode(s);
-                        ulong[][][] ulongs = new ulong[d1Size][][]; //[d2Size][d3Size];
-                        for (int l = 0; l < d1Size; l++)
-                        {
-                                ulongs[l] = new ulong[d2Size][];
-                                for (int m = 0; m < d2Size; m++)
-                                {
-                                        ulongs[l][m] = new ulong[d3Size];
-                                }
-                        }
-                        
-                        uint i, j, k;
-                        for (uint x = 0; x < bytes.Length / 8; x++)
-                        {
-                                i = x/(d2Size * d3Size);
-                                j = x%(d2Size * d3Size)/d3Size;
-                                k = x % d3Size;
-                                ulongs[i][j][k] = Pack.BE_To_UInt64(bytes, (int)x * 8);
-                        }
-                        return ulongs;
+                    ulongs[l][m] = new ulong[d3Size];
                 }
-
-
+            }
+                        
+            uint i, j, k;
+            for (uint x = 0; x < bytes.Length / 8; x++)
+            {
+                i = x/(d2Size * d3Size);
+                j = x%(d2Size * d3Size)/d3Size;
+                k = x % d3Size;
+                ulongs[i][j][k] = Pack.BE_To_UInt64(bytes, (int)x * 8);
+            }
+            return ulongs;
         }
-
-}
\ No newline at end of file
+    }
+}
diff --git a/crypto/src/pqc/crypto/sike/Isogeny.cs b/crypto/src/pqc/crypto/sike/Isogeny.cs
index ace6e20b9..be9e3ba4d 100644
--- a/crypto/src/pqc/crypto/sike/Isogeny.cs
+++ b/crypto/src/pqc/crypto/sike/Isogeny.cs
@@ -1,25 +1,25 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class Isogeny
+internal sealed class Isogeny
 {
-     SIKEEngine engine;
+    private readonly SikeEngine engine;
 
-     internal Isogeny(SIKEEngine engine)
+    internal Isogeny(SikeEngine engine)
     {
         this.engine = engine;
     }
-    
+
     // Doubling of a Montgomery point in projective coordinates (X:Z) over affine curve coefficient A. 
     // Input: projective Montgomery x-coordinates P = (X1:Z1), where x1=X1/Z1 and Montgomery curve constants (A+2)/4.
     // Output: projective Montgomery x-coordinates Q = 2*P = (X2:Z2). 
-    protected internal void Double(PointProj P, PointProj Q, ulong[][] A24, uint k)
+    internal void Double(PointProj P, PointProj Q, ulong[][] A24, uint k)
     {
-        ulong[][] temp = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            a = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            b = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            c = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            aa = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            bb = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] temp = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            a = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            b = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            c = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            aa = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            bb = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         engine.fpx.fp2copy(P.X, Q.X);
         engine.fpx.fp2copy(P.Z, Q.Z);
 
@@ -37,17 +37,17 @@ public class Isogeny
         }
     }
 
-    protected internal void CompleteMPoint(ulong[][] A, PointProj P, PointProjFull R)
+    internal void CompleteMPoint(ulong[][] A, PointProj P, PointProjFull R)
     { // Given an xz-only representation on a montgomery curve, compute its affine representation
-        ulong[][] zero = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            one = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            xz = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            yz = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            s2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            r2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            invz = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            temp0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            temp1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] zero = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            one = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            xz = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            yz = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            s2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            r2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            invz = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            temp0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            temp1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
 
             engine.fpx.fpcopy(engine.param.Montgomery_one,0, one[0]);
@@ -81,10 +81,9 @@ public class Isogeny
 
     internal void Ladder(PointProj P, ulong[] m, ulong[][] A, uint order_bits, PointProj R)
     {
-        PointProj R0 = new PointProj(engine.param.NWORDS_FIELD),
-                  R1 = new PointProj(engine.param.NWORDS_FIELD);
-        ulong[][] A24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-        uint bit = 0;
+        var R0 = new PointProj(engine.param.NWORDS_FIELD);
+        var R1 = new PointProj(engine.param.NWORDS_FIELD);
+        ulong[][] A24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         ulong mask;
         int j, swap, prevbit = 0;
         
@@ -96,7 +95,7 @@ public class Isogeny
         engine.fpx.fp2div2(A24, A24);  // A24 = (A+2)/4          
 
         j = (int)order_bits - 1;
-        bit = (uint) ((m[j >> (int)Internal.LOG2RADIX] >> (int)(j & (Internal.RADIX-1))) & 1);
+        uint bit = (uint) ((m[j >> (int)Internal.LOG2RADIX] >> (int)(j & (Internal.RADIX-1))) & 1);
         while (bit == 0)
         {
             j--;
@@ -106,7 +105,7 @@ public class Isogeny
         // R0 <- P, R1 <- 2P
         engine.fpx.fp2copy(P.X, R0.X);
         engine.fpx.fp2copy(P.Z, R0.Z);
-        xDBL_e(P, R1, A24, 1);
+        XDblE(P, R1, A24, 1);
 
         // Main loop
         for (int i = (int)j - 1;  i >= 0; i--) 
@@ -116,12 +115,12 @@ public class Isogeny
             prevbit = (int) bit;
             mask = (ulong) (0 - swap);
 
-            swap_points(R0, R1, mask);
-            xDBLADD_proj(R0, R1, P.X, P.Z, A24);
+            SwapPoints(R0, R1, mask);
+            XDblAddProj(R0, R1, P.X, P.Z, A24);
         }
         swap = 0 ^ prevbit;
         mask = (ulong) (0 - swap);
-        swap_points(R0, R1, mask);
+        SwapPoints(R0, R1, mask);
 
         engine.fpx.fp2copy(R0.X, R.X);
         engine.fpx.fp2copy(R0.Z, R.Z);
@@ -130,11 +129,11 @@ public class Isogeny
     // Simultaneous doubling and differential addition.
     // Input: projective Montgomery points P=(XP:ZP) and Q=(XQ:ZQ) such that xP=XP/ZP and xQ=XQ/ZQ, affine difference xPQ=x(P-Q) and Montgomery curve constant A24=(A+2)/4.
     // Output: projective Montgomery points P <- 2*P = (X2P:Z2P) such that x(2P)=X2P/Z2P, and Q <- P+Q = (XQP:ZQP) such that = x(Q+P)=XQP/ZQP.
-    private void xDBLADD_proj(PointProj P, PointProj Q, ulong[][] XPQ, ulong[][] ZPQ, ulong[][] A24)
+    private void XDblAddProj(PointProj P, PointProj Q, ulong[][] XPQ, ulong[][] ZPQ, ulong[][] A24)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
 
         engine.fpx.fp2add(P.X, P.Z, t0);                         // t0 = XP+ZP
@@ -162,14 +161,14 @@ public class Isogeny
     // Doubling of a Montgomery point in projective coordinates (X:Z) over affine curve coefficient A.
     // Input: projective Montgomery x-coordinates P = (X1:Z1), where x1=X1/Z1 and Montgomery curve constants (A+2)/4.
     // Output: projective Montgomery x-coordinates Q = 2*P = (X2:Z2).
-    private void xDBL_e(PointProj P, PointProj Q, ulong[][] A24, int e)
+    private void XDblE(PointProj P, PointProj Q, ulong[][] A24, int e)
     {
-        ulong[][] temp = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            a = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            b = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            c = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            aa = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            bb = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] temp = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            a = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            b = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            c = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            aa = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            bb = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
 
         engine.fpx.fp2copy(P.X,Q.X);
@@ -192,14 +191,14 @@ public class Isogeny
     // Computes [3^e](X:Z) on Montgomery curve with projective constant via e repeated triplings. e triplings in E costs k*(5M + 6S + 9A)
     // Input: projective Montgomery x-coordinates P = (X:Z), where x=X/Z, Montgomery curve constant A2 = A/2 and the number of triplings e.
     // Output: projective Montgomery x-coordinates Q <- [3^e]P.
-    internal void xTPLe_fast(PointProj P, PointProj Q, ulong[][] A2, uint e)
+    internal void XTplEFast(PointProj P, PointProj Q, ulong[][] A2, uint e)
     {
-        PointProj T = new PointProj(engine.param.NWORDS_FIELD);
+        var T = new PointProj(engine.param.NWORDS_FIELD);
 
         engine.fpx.copy_words(P, T);
         for (int j = 0; j < e; j++)
         {
-            xTPL_fast(T, T, A2);
+            XTplFast(T, T, A2);
         }
         engine.fpx.copy_words(T, Q);
     }
@@ -207,12 +206,12 @@ public class Isogeny
     // Montgomery curve (E: y^2 = x^3 + A*x^2 + x) x-only tripling at a cost 5M + 6S + 9A = 27p + 61a.
     // Input : projective Montgomery x-coordinates P = (X:Z), where x=X/Z and Montgomery curve constant A/2.
     // Output: projective Montgomery x-coordinates Q = 3*P = (X3:Z3).
-    private void xTPL_fast(PointProj P, PointProj Q, ulong[][] A2)
+    private void XTplFast(PointProj P, PointProj Q, ulong[][] A2)
     {
-        ulong[][] t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t4 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t4 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
 
         engine.fpx.fp2sqr_mont(P.X, t1);        // t1 = x^2
@@ -238,11 +237,11 @@ public class Isogeny
     }
 
 
-    protected internal void LADDER3PT(ulong[][] xP, ulong[][] xQ, ulong[][] xPQ, ulong[] m, uint AliceOrBob, PointProj R, ulong[][] A)
+    internal void LADDER3PT(ulong[][] xP, ulong[][] xQ, ulong[][] xPQ, ulong[] m, uint AliceOrBob, PointProj R, ulong[][] A)
     {
-        PointProj R0 = new PointProj(engine.param.NWORDS_FIELD),
-                  R2 = new PointProj(engine.param.NWORDS_FIELD);
-        ulong[][] A24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        var R0 = new PointProj(engine.param.NWORDS_FIELD);
+        var R2 = new PointProj(engine.param.NWORDS_FIELD);
+        ulong[][] A24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         ulong mask;
         uint i, nbits, bit, swap, prevbit = 0;
 
@@ -277,26 +276,26 @@ public class Isogeny
             swap = bit ^ prevbit;
             prevbit = bit;
             mask = 0 - (ulong) swap;
-            swap_points(R, R2, mask);
-            xDBLADD(R0, R2, R.X, A24);
+            SwapPoints(R, R2, mask);
+            XDblAdd(R0, R2, R.X, A24);
             engine.fpx.fp2mul_mont(R2.X, R.Z, R2.X);
         }
         swap = 0 ^ prevbit;
         mask = 0 - (ulong)swap;
-        swap_points(R, R2, mask);
+        SwapPoints(R, R2, mask);
     }
 
     // Complete point on A = 0 curve
-    protected internal void CompletePoint(PointProj P, PointProjFull R)
+    internal void CompletePoint(PointProj P, PointProjFull R)
     {
-        ulong[][] xz = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            s2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            r2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            yz = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            invz = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            one = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] xz = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            s2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            r2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            yz = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            invz = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            one = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, one[0]);
         engine.fpx.fp2mul_mont(P.X, P.Z, xz);
@@ -318,13 +317,11 @@ public class Isogeny
 
     // Swap points.
     // If option = 0 then P <- P and Q <- Q, else if option = 0xFF...FF then P <- Q and Q <- P
-    protected internal void swap_points(PointProj P, PointProj Q, ulong option)
+    internal void SwapPoints(PointProj P, PointProj Q, ulong option)
     {
         //todo/org : put this in the PointProj class
         ulong temp;
-        int i;
-
-        for (i = 0; i < engine.param.NWORDS_FIELD; i++)
+        for (int i = 0; i < engine.param.NWORDS_FIELD; i++)
         {
             temp = option & (P.X[0][i] ^ Q.X[0][i]);
             P.X[0][i] = temp ^ P.X[0][i];
@@ -344,11 +341,11 @@ public class Isogeny
     // Simultaneous doubling and differential addition.
     // Input: projective Montgomery points P=(XP:ZP) and Q=(XQ:ZQ) such that xP=XP/ZP and xQ=XQ/ZQ, affine difference xPQ=x(P-Q) and Montgomery curve constant A24=(A+2)/4.
     // Output: projective Montgomery points P <- 2*P = (X2P:Z2P) such that x(2P)=X2P/Z2P, and Q <- P+Q = (XQP:ZQP) such that = x(Q+P)=XQP/ZQP.
-    protected internal void xDBLADD(PointProj P, PointProj Q, ulong[][] xPQ, ulong[][] A24)
+    internal void XDblAdd(PointProj P, PointProj Q, ulong[][] xPQ, ulong[][] A24)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_add(P.X, P.Z, t0);                  // t0 = XP+ZP
         engine.fpx.mp2_sub_p2(P.X, P.Z, t1);               // t1 = XP-ZP
@@ -373,24 +370,24 @@ public class Isogeny
     // Computes [2^e](X:Z) on Montgomery curve with projective constant via e repeated doublings.
     // Input: projective Montgomery x-coordinates P = (XP:ZP), such that xP=XP/ZP and Montgomery curve constants A+2C and 4C.
     // Output: projective Montgomery x-coordinates Q <- (2^e)*P.
-    protected internal void xDBLe(PointProj P, PointProj Q, ulong[][] A24plus, ulong[][] C24, uint e)
+    internal void XDblE(PointProj P, PointProj Q, ulong[][] A24plus, ulong[][] C24, uint e)
     {
         int i;
         engine.fpx.copy_words(P, Q);
 
         for (i = 0; i < e; i++)
         {
-            xDBL(Q, Q, A24plus, C24);
+            XDbl(Q, Q, A24plus, C24);
         }
     }
 
     // Doubling of a Montgomery point in projective coordinates (X:Z).
     // Input: projective Montgomery x-coordinates P = (X1:Z1), where x1=X1/Z1 and Montgomery curve constants A+2C and 4C.
     // Output: projective Montgomery x-coordinates Q = 2*P = (X2:Z2).
-    protected void xDBL(PointProj P, PointProj Q, ulong[][] A24plus, ulong[][] C24)
+    internal void XDbl(PointProj P, PointProj Q, ulong[][] A24plus, ulong[][] C24)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_sub_p2(P.X, P.Z, t0);                // t0 = X1-Z1
         engine.fpx.mp2_add(P.X, P.Z, t1);                   // t1 = X1+Z1
@@ -407,15 +404,15 @@ public class Isogeny
     // Tripling of a Montgomery point in projective coordinates (X:Z).
     // Input: projective Montgomery x-coordinates P = (X:Z), where x=X/Z and Montgomery curve constants A24plus = A+2C and A24minus = A-2C.
     // Output: projective Montgomery x-coordinates Q = 3*P = (X3:Z3).
-    private void xTPL(PointProj P, PointProj Q, ulong[][] A24minus, ulong[][] A24plus)
+    private void XTpl(PointProj P, PointProj Q, ulong[][] A24minus, ulong[][] A24plus)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t4 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t5 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t6 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t4 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t5 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t6 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_sub_p2(P.X, P.Z, t0);               // t0 = X-Z
         engine.fpx.fp2sqr_mont(t0, t2);                    // t2 = (X-Z)^2
@@ -444,24 +441,24 @@ public class Isogeny
     // Computes [3^e](X:Z) on Montgomery curve with projective constant via e repeated triplings.
     // Input: projective Montgomery x-coordinates P = (XP:ZP), such that xP=XP/ZP and Montgomery curve constants A24plus = A+2C and A24minus = A-2C.
     // Output: projective Montgomery x-coordinates Q <- (3^e)*P.
-    protected internal void xTPLe(PointProj P, PointProj Q, ulong[][] A24minus, ulong[][] A24plus, uint e)
+    internal void XTplE(PointProj P, PointProj Q, ulong[][] A24minus, ulong[][] A24plus, uint e)
     {
         int i;
         engine.fpx.copy_words(P, Q);
         for (i = 0; i < e; i++)
         {
-            xTPL(Q, Q, A24minus, A24plus);
+            XTpl(Q, Q, A24minus, A24plus);
         }
     }
 
     // Given the x-coordinates of P, Q, and R, returns the value A corresponding to the Montgomery curve E_A: y^2=x^3+A*x^2+x such that R=Q-P on E_A.
     // Input:  the x-coordinates xP, xQ, and xR of the points P, Q and R.
     // Output: the coefficient A corresponding to the curve E_A: y^2=x^3+A*x^2+x.
-    protected internal void get_A(ulong[][] xP, ulong[][] xQ, ulong[][] xR, ulong[][] A)
+    internal void GetA(ulong[][] xP, ulong[][] xQ, ulong[][] xR, ulong[][] A)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            one = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            one = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, one[0]);
         engine.fpx.fp2add(xP, xQ, t1);                     // t1 = xP+xQ
@@ -482,10 +479,10 @@ public class Isogeny
     // Computes the j-invariant of a Montgomery curve with projective constant.
     // Input: A,C in GF(p^2).
     // Output: j=256*(A^2-3*C^2)^3/(C^4*(A^2-4*C^2)), which is the j-invariant of the Montgomery curve B*y^2=x^3+(A/C)*x^2+x or (equivalently) j-invariant of B'*y^2=C*x^3+A*x^2+C*x.
-    protected internal void j_inv(ulong[][] A, ulong[][] C, ulong[][] jinv)
+    internal void JInv(ulong[][] A, ulong[][] C, ulong[][] jinv)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         
         engine.fpx.fp2sqr_mont(A, jinv);                   // jinv = A^2
         engine.fpx.fp2sqr_mont(C, t1);                     // t1 = C^2
@@ -510,13 +507,13 @@ public class Isogeny
     // Computes the corresponding 3-isogeny of a projective Montgomery point (X3:Z3) of order 3.
     // Input:  projective point of order three P = (X3:Z3).
     // Output: the 3-isogenous Montgomery curve with projective coefficient A/C.
-    protected internal void get_3_isog(PointProj P, ulong[][] A24minus, ulong[][] A24plus, ulong[][][] coeff)
+    internal void Get3Isog(PointProj P, ulong[][] A24minus, ulong[][] A24plus, ulong[][][] coeff)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t4 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t4 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_sub_p2(P.X, P.Z, coeff[0]);         // coeff0 = X-Z
         engine.fpx.fp2sqr_mont(coeff[0], t0);              // t0 = (X-Z)^2
@@ -540,11 +537,11 @@ public class Isogeny
     // a point P with 2 coefficients in coeff (computed in the function get_3_Isog()).
     // Inputs: projective points P = (X3:Z3) and Q = (X:Z).
     // Output: the projective point Q <- phi(Q) = (X3:Z3).
-    protected internal void eval_3_isog(PointProj Q, ulong[][][] coeff)
+    internal void Eval3Isog(PointProj Q, ulong[][][] coeff)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_add(Q.X, Q.Z, t0);                  // t0 = X+Z
         engine.fpx.mp2_sub_p2(Q.X, Q.Z, t1);               // t1 = X-Z
@@ -561,12 +558,12 @@ public class Isogeny
     // 3-way simultaneous inversion
     // Input:  z1,z2,z3
     // Output: 1/z1,1/z2,1/z3 (override inputs).
-    protected internal void inv_3_way(ulong[][] z1, ulong[][] z2, ulong[][] z3)
+    internal void Inv3Way(ulong[][] z1, ulong[][] z2, ulong[][] z3)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2mul_mont(z1, z2, t0);                // t0 = z1*z2
         engine.fpx.fp2mul_mont(z3, t0, t1);                // t1 = z1*z2*z3
@@ -581,7 +578,7 @@ public class Isogeny
     // Computes the corresponding 2-isogeny of a projective Montgomery point (X2:Z2) of order 2.
     // Input:  projective point of order two P = (X2:Z2).
     // Output: the 2-isogenous Montgomery curve with projective coefficients A/C.
-    protected internal void get_2_isog(PointProj P, ulong[][] A, ulong[][] C)
+    internal void Get2Isog(PointProj P, ulong[][] A, ulong[][] C)
     {
         engine.fpx.fp2sqr_mont(P.X, A);                    // A = X2^2
         engine.fpx.fp2sqr_mont(P.Z, C);                    // C = Z2^2
@@ -591,12 +588,12 @@ public class Isogeny
     // Evaluates the isogeny at the point (X:Z) in the domain of the isogeny, given a 2-isogeny phi.
     // Inputs: the projective point P = (X:Z) and the 2-isogeny kernel projetive point Q = (X2:Z2).
     // Output: the projective point P = phi(P) = (X:Z) in the codomain. 
-    protected internal void eval_2_isog(PointProj P, PointProj Q)
+    internal void Eval2Isog(PointProj P, PointProj Q)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_add(Q.X, Q.Z, t0);                  // t0 = X2+Z2
         engine.fpx.mp2_sub_p2(Q.X, Q.Z, t1);               // t1 = X2-Z2
@@ -614,7 +611,7 @@ public class Isogeny
     // Input:  projective point of order four P = (X4:Z4).
     // Output: the 4-isogenous Montgomery curve with projective coefficients A+2C/4C and the 3 coefficients
     //         that are used to evaluate the isogeny at a point in eval_4_Isog().
-    protected internal void get_4_isog(PointProj P, ulong[][] A24plus, ulong[][] C24, ulong[][][] coeff)
+    internal void Get4Isog(PointProj P, ulong[][] A24plus, ulong[][] C24, ulong[][][] coeff)
     {
         engine.fpx.mp2_sub_p2(P.X, P.Z, coeff[1]);         // coeff[1] = X4-Z4
         engine.fpx.mp2_add(P.X, P.Z, coeff[2]);            // coeff[2] = X4+Z4
@@ -631,10 +628,10 @@ public class Isogeny
     // by the 3 coefficients in coeff (computed in the function get_4_Isog()).
     // Inputs: the coefficients defining the isogeny, and the projective point P = (X:Z).
     // Output: the projective point P = phi(P) = (X:Z) in the codomain.
-    protected internal void eval_4_isog(PointProj P, ulong[][][] coeff)
+    internal void Eval4Isog(PointProj P, ulong[][][] coeff)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.mp2_add(P.X, P.Z, t0);                  // t0 = X+Z
         engine.fpx.mp2_sub_p2(P.X, P.Z, t1);               // t1 = X-Z
@@ -651,9 +648,5 @@ public class Isogeny
         engine.fpx.fp2mul_mont(P.X, t1, P.X);              // Xfinal
         engine.fpx.fp2mul_mont(P.Z, t0, P.Z);              // Zfinal
     }
-    
-    
-
 }
-
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sike/P434.cs b/crypto/src/pqc/crypto/sike/P434.cs
index 2d4139927..98c77aa85 100644
--- a/crypto/src/pqc/crypto/sike/P434.cs
+++ b/crypto/src/pqc/crypto/sike/P434.cs
@@ -3,7 +3,7 @@ using System.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class P434 
+internal class P434 
     : Internal
 {
     // Encoding of field elementsL, elements over Z_orderL, elements over GF(p^2) and elliptic curve points:
diff --git a/crypto/src/pqc/crypto/sike/P503.cs b/crypto/src/pqc/crypto/sike/P503.cs
index 31b735873..fb0cb27c8 100644
--- a/crypto/src/pqc/crypto/sike/P503.cs
+++ b/crypto/src/pqc/crypto/sike/P503.cs
@@ -3,8 +3,8 @@ using System.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-    
-public class P503
+
+    internal class P503
     : Internal
 {
     // Encoding of field elements, elements over Z_order, elements over GF(p^2) and elliptic curve points:
diff --git a/crypto/src/pqc/crypto/sike/P610.cs b/crypto/src/pqc/crypto/sike/P610.cs
index 33f9956c7..ac3f0deec 100644
--- a/crypto/src/pqc/crypto/sike/P610.cs
+++ b/crypto/src/pqc/crypto/sike/P610.cs
@@ -3,7 +3,7 @@ using System.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class P610
+    internal class P610
     : Internal
 {
         internal P610(bool isCompressed)
diff --git a/crypto/src/pqc/crypto/sike/P751.cs b/crypto/src/pqc/crypto/sike/P751.cs
index b8d48d3aa..ab281c3a6 100644
--- a/crypto/src/pqc/crypto/sike/P751.cs
+++ b/crypto/src/pqc/crypto/sike/P751.cs
@@ -3,7 +3,7 @@ using System.IO;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class P751
+    internal class P751
     : Internal
 {
     // Encoding of field elements, elements over Z_order, elements over GF(p^2) and elliptic curve points:
diff --git a/crypto/src/pqc/crypto/sike/PointProj.cs b/crypto/src/pqc/crypto/sike/PointProj.cs
index 92b982d4f..a9c82c249 100644
--- a/crypto/src/pqc/crypto/sike/PointProj.cs
+++ b/crypto/src/pqc/crypto/sike/PointProj.cs
@@ -1,14 +1,13 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class PointProj
-{
-    internal PointProj(uint nwords_field)
+    internal sealed class PointProj
     {
-        X = Utils.InitArray(2, nwords_field);
-        Z = Utils.InitArray(2, nwords_field);
+        internal PointProj(uint nwords_field)
+        {
+            X = SikeUtilities.InitArray(2, nwords_field);
+            Z = SikeUtilities.InitArray(2, nwords_field);
+        }
+        internal ulong[][] X;
+        internal ulong[][] Z;
     }
-    public ulong[][] X;
-    public ulong[][] Z;
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/PointProjFull.cs b/crypto/src/pqc/crypto/sike/PointProjFull.cs
index f5d4598a8..8153d9c38 100644
--- a/crypto/src/pqc/crypto/sike/PointProjFull.cs
+++ b/crypto/src/pqc/crypto/sike/PointProjFull.cs
@@ -1,15 +1,15 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class PointProjFull
-{
-    internal PointProjFull(uint nwords_field)
+    internal sealed class PointProjFull
     {
-        X = Utils.InitArray(2, nwords_field);
-        Y = Utils.InitArray(2, nwords_field);
-        Z = Utils.InitArray(2, nwords_field);
+        internal PointProjFull(uint nwords_field)
+        {
+            X = SikeUtilities.InitArray(2, nwords_field);
+            Y = SikeUtilities.InitArray(2, nwords_field);
+            Z = SikeUtilities.InitArray(2, nwords_field);
+        }
+        internal ulong[][] X;
+        internal ulong[][] Y;
+        internal ulong[][] Z;
     }
-    public ulong[][] X;
-    public ulong[][] Y;
-    public ulong[][] Z;
-}
 }
diff --git a/crypto/src/pqc/crypto/sike/SIDH.cs b/crypto/src/pqc/crypto/sike/SIDH.cs
index d2e4b4929..8246291c6 100644
--- a/crypto/src/pqc/crypto/sike/SIDH.cs
+++ b/crypto/src/pqc/crypto/sike/SIDH.cs
@@ -1,16 +1,16 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIDH
+internal sealed class Sidh
 {
-    private SIKEEngine engine;
+    private readonly SikeEngine engine;
 
-    public SIDH(SIKEEngine engine)
+    internal Sidh(SikeEngine engine)
     {
         this.engine = engine;
     }
 
     // Initialization of basis points
-    protected void init_basis(ulong[] gen, ulong[][] XP, ulong[][] XQ, ulong[][] XR)
+    internal void init_basis(ulong[] gen, ulong[][] XP, ulong[][] XQ, ulong[][] XR)
     {
        engine.fpx.fpcopy(gen, 0, XP[0]);
        engine.fpx.fpcopy(gen, engine.param.NWORDS_FIELD, XP[1]);
@@ -23,7 +23,7 @@ public class SIDH
     // Bob's ephemeral public key generation
     // Input:  a private key PrivateKeyB in the range [0, 2^Floor(Log(2,oB)) - 1].
     // Output: the public key PublicKeyB consisting of 3 elements in GF(p^2) which are encoded by removing leading 0 bytes.
-    protected internal void EphemeralKeyGeneration_B(byte[] sk, byte[] pk)
+    internal void EphemeralKeyGeneration_B(byte[] sk, byte[] pk)
     {
         PointProj R = new PointProj(engine.param.NWORDS_FIELD),
                 phiP = new PointProj(engine.param.NWORDS_FIELD),
@@ -32,14 +32,14 @@ public class SIDH
 
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_BOB];
 
-        ulong[][] XPB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XQB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XRB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24minus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] XPB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XQB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XRB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24minus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
-        ulong[][][] coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);
         uint i, row, m, index = 0, npts = 0, ii = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_BOB];
         ulong[] SecretKeyB = new ulong[engine.param.NWORDS_ORDER];
@@ -71,29 +71,29 @@ public class SIDH
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Bob[ii++];
-                engine.isogeny.xTPLe(R, R, A24minus, A24plus, m);
+                engine.isogeny.XTplE(R, R, A24minus, A24plus, m);
                 index += m;
             }
-            engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+            engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
 
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_3_isog(pts[i], coeff);
+                engine.isogeny.Eval3Isog(pts[i], coeff);
             }
-            engine.isogeny.eval_3_isog(phiP, coeff);
-            engine.isogeny.eval_3_isog(phiQ, coeff);
-            engine.isogeny.eval_3_isog(phiR, coeff);
+            engine.isogeny.Eval3Isog(phiP, coeff);
+            engine.isogeny.Eval3Isog(phiQ, coeff);
+            engine.isogeny.Eval3Isog(phiR, coeff);
 
             engine.fpx.fp2copy(pts[npts - 1].X, R.X);
             engine.fpx.fp2copy(pts[npts - 1].Z, R.Z);
             index = pts_index[npts - 1];
             npts -= 1;
         }
-        engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
-        engine.isogeny.eval_3_isog(phiP, coeff);
-        engine.isogeny.eval_3_isog(phiQ, coeff);
-        engine.isogeny.eval_3_isog(phiR, coeff);
-        engine.isogeny.inv_3_way(phiP.Z, phiQ.Z, phiR.Z);
+        engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
+        engine.isogeny.Eval3Isog(phiP, coeff);
+        engine.isogeny.Eval3Isog(phiQ, coeff);
+        engine.isogeny.Eval3Isog(phiR, coeff);
+        engine.isogeny.Inv3Way(phiP.Z, phiQ.Z, phiR.Z);
 
         engine.fpx.fp2mul_mont(phiP.X, phiP.Z, phiP.X);
         engine.fpx.fp2mul_mont(phiQ.X, phiQ.Z, phiQ.X);
@@ -108,7 +108,7 @@ public class SIDH
     // Alice's ephemeral public key generation
     // Input:  a private key PrivateKeyA in the range [0, 2^eA - 1].
     // Output: the public key PublicKeyA consisting of 3 elements in GF(p^2) which are encoded by removing leading 0 bytes.
-    protected internal void EphemeralKeyGeneration_A(byte[] ephemeralsk, byte[] ct)
+    internal void EphemeralKeyGeneration_A(byte[] ephemeralsk, byte[] ct)
     {
         PointProj R = new PointProj(engine.param.NWORDS_FIELD),
                 phiP = new PointProj(engine.param.NWORDS_FIELD),
@@ -116,14 +116,14 @@ public class SIDH
                 phiR = new PointProj(engine.param.NWORDS_FIELD);
 
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_ALICE];
-        ulong[][] XPA = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XQA = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XRA = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            C24 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-
-        ulong[][][] coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);
+        ulong[][] XPA = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XQA = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XRA = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            C24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
+
+        ulong[][][] coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);
         uint index = 0, npts = 0, ii = 0, m, i, row;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_ALICE];
         ulong[] SecretKeyA = new ulong[engine.param.NWORDS_ORDER];
@@ -149,12 +149,12 @@ public class SIDH
         if (engine.param.OALICE_BITS % 2 == 1)
         {
             PointProj S = new PointProj(engine.param.NWORDS_FIELD);
-            engine.isogeny.xDBLe(R, S, A24plus, C24, (engine.param.OALICE_BITS - 1));
-            engine.isogeny.get_2_isog(S, A24plus, C24);
-            engine.isogeny.eval_2_isog(phiP, S);
-            engine.isogeny.eval_2_isog(phiQ, S);
-            engine.isogeny.eval_2_isog(phiR, S);
-            engine.isogeny.eval_2_isog(R, S);
+            engine.isogeny.XDblE(R, S, A24plus, C24, (engine.param.OALICE_BITS - 1));
+            engine.isogeny.Get2Isog(S, A24plus, C24);
+            engine.isogeny.Eval2Isog(phiP, S);
+            engine.isogeny.Eval2Isog(phiQ, S);
+            engine.isogeny.Eval2Isog(phiR, S);
+            engine.isogeny.Eval2Isog(R, S);
         }
 
         // Traverse tree
@@ -168,18 +168,18 @@ public class SIDH
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Alice[ii++];
-                engine.isogeny.xDBLe(R, R, A24plus, C24, 2*m);
+                engine.isogeny.XDblE(R, R, A24plus, C24, 2*m);
                 index += m;
             }
-            engine.isogeny.get_4_isog(R, A24plus, C24, coeff);
+            engine.isogeny.Get4Isog(R, A24plus, C24, coeff);
 
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_4_isog(pts[i], coeff);
+                engine.isogeny.Eval4Isog(pts[i], coeff);
             }
-            engine.isogeny.eval_4_isog(phiP, coeff);
-            engine.isogeny.eval_4_isog(phiQ, coeff);
-            engine.isogeny.eval_4_isog(phiR, coeff);
+            engine.isogeny.Eval4Isog(phiP, coeff);
+            engine.isogeny.Eval4Isog(phiQ, coeff);
+            engine.isogeny.Eval4Isog(phiR, coeff);
 
             engine.fpx.fp2copy(pts[npts-1].X, R.X);
             engine.fpx.fp2copy(pts[npts-1].Z, R.Z);
@@ -187,12 +187,12 @@ public class SIDH
             npts -= 1;
         }
 
-        engine.isogeny.get_4_isog(R, A24plus, C24, coeff);
-        engine.isogeny.eval_4_isog(phiP, coeff);
-        engine.isogeny.eval_4_isog(phiQ, coeff);
-        engine.isogeny.eval_4_isog(phiR, coeff);
+        engine.isogeny.Get4Isog(R, A24plus, C24, coeff);
+        engine.isogeny.Eval4Isog(phiP, coeff);
+        engine.isogeny.Eval4Isog(phiQ, coeff);
+        engine.isogeny.Eval4Isog(phiR, coeff);
 
-        engine.isogeny.inv_3_way(phiP.Z, phiQ.Z, phiR.Z);
+        engine.isogeny.Inv3Way(phiP.Z, phiQ.Z, phiR.Z);
         engine.fpx.fp2mul_mont(phiP.X, phiP.Z, phiP.X);
         engine.fpx.fp2mul_mont(phiQ.X, phiQ.Z, phiQ.X);
         engine.fpx.fp2mul_mont(phiR.X, phiR.Z, phiR.X);
@@ -209,16 +209,16 @@ public class SIDH
     // Inputs: Alice's PrivateKeyA is an integer in the range [0, oA-1].
     //         Bob's PublicKeyB consists of 3 elements in GF(p^2) encoded by removing leading 0 bytes.
     // Output: a shared secret SharedSecretA that consists of one element in GF(p^2) encoded by removing leading 0 bytes.
-    protected internal void EphemeralSecretAgreement_A(byte[] ephemeralsk, byte[] pk, byte[] jinvariant)
+    internal void EphemeralSecretAgreement_A(byte[] ephemeralsk, byte[] pk, byte[] jinvariant)
     {
         PointProj R = new PointProj(engine.param.NWORDS_FIELD);
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_ALICE];
-        ulong[][][] PKB = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD),
-            coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);
-        ulong[][] jinv = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            C24 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] PKB = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD),
+            coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);
+        ulong[][] jinv = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            C24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         uint i = 0, row = 0, m = 0, index = 0, npts = 0, ii = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_ALICE];
@@ -230,7 +230,7 @@ public class SIDH
         engine.fpx.fp2_decode(pk, PKB[2], 2*engine.param.FP2_ENCODED_BYTES);
 
         // Initialize constants: A24plus = A+2C, C24 = 4C, where C=1
-        engine.isogeny.get_A(PKB[0], PKB[1], PKB[2], A);
+        engine.isogeny.GetA(PKB[0], PKB[1], PKB[2], A);
         engine.fpx.mp_add(engine.param.Montgomery_one, engine.param.Montgomery_one, C24[0], engine.param.NWORDS_FIELD);
         engine.fpx.mp2_add(A, C24, A24plus);
         engine.fpx.mp_add(C24[0], C24[0], C24[0], engine.param.NWORDS_FIELD);
@@ -243,9 +243,9 @@ public class SIDH
         {
             PointProj S = new PointProj(engine.param.NWORDS_FIELD);
 
-            engine.isogeny.xDBLe(R, S, A24plus, C24, engine.param.OALICE_BITS - 1);
-            engine.isogeny.get_2_isog(S, A24plus, C24);
-            engine.isogeny.eval_2_isog(R, S);
+            engine.isogeny.XDblE(R, S, A24plus, C24, engine.param.OALICE_BITS - 1);
+            engine.isogeny.Get2Isog(S, A24plus, C24);
+            engine.isogeny.Eval2Isog(R, S);
         }
 
         // Traverse tree
@@ -259,14 +259,14 @@ public class SIDH
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Alice[ii++];
-                engine.isogeny.xDBLe(R, R, A24plus, C24, 2*m);
+                engine.isogeny.XDblE(R, R, A24plus, C24, 2*m);
                 index += m;
             }
-            engine.isogeny.get_4_isog(R, A24plus, C24, coeff);
+            engine.isogeny.Get4Isog(R, A24plus, C24, coeff);
 
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_4_isog(pts[i], coeff);
+                engine.isogeny.Eval4Isog(pts[i], coeff);
             }
 
             engine.fpx.fp2copy(pts[npts-1].X, R.X);
@@ -275,11 +275,11 @@ public class SIDH
             npts -= 1;
         }
 
-        engine.isogeny.get_4_isog(R, A24plus, C24, coeff);
+        engine.isogeny.Get4Isog(R, A24plus, C24, coeff);
         engine.fpx.mp2_add(A24plus, A24plus, A24plus);
         engine.fpx.fp2sub(A24plus, C24, A24plus);
         engine.fpx.fp2add(A24plus, A24plus, A24plus);
-        engine.isogeny.j_inv(A24plus, C24, jinv);
+        engine.isogeny.JInv(A24plus, C24, jinv);
         engine.fpx.fp2_encode(jinv, jinvariant, 0);    // Format shared secret
     }
 
@@ -288,17 +288,17 @@ public class SIDH
     // Inputs: Bob's PrivateKeyB is an integer in the range [0, 2^Floor(Log(2,oB)) - 1].
     //         Alice's PublicKeyA consists of 3 elements in GF(p^2) encoded by removing leading 0 bytes.
     // Output: a shared secret SharedSecretB that consists of one element in GF(p^2) encoded by removing leading 0 bytes.
-    protected internal void EphemeralSecretAgreement_B(byte[] sk, byte[] ct, byte[] jinvariant_)
+    internal void EphemeralSecretAgreement_B(byte[] sk, byte[] ct, byte[] jinvariant_)
     {
         PointProj R = new PointProj(engine.param.NWORDS_FIELD);
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_BOB];
-        ulong[][][] coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD),
-            PKB = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD),
+            PKB = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);
 
-        ulong[][] jinv = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24minus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] jinv = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24minus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         uint i, row, m, index = 0, npts = 0, ii = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_BOB];
         ulong[] SecretKeyB = new ulong[engine.param.NWORDS_ORDER];
@@ -309,7 +309,7 @@ public class SIDH
         engine.fpx.fp2_decode(ct, PKB[2], 2*engine.param.FP2_ENCODED_BYTES);
 
         // Initialize constants: A24plus = A+2C, A24minus = A-2C, where C=1
-        engine.isogeny.get_A(PKB[0], PKB[1], PKB[2], A);
+        engine.isogeny.GetA(PKB[0], PKB[1], PKB[2], A);
         engine.fpx.mp_add(engine.param.Montgomery_one, engine.param.Montgomery_one, A24minus[0], engine.param.NWORDS_FIELD);
         engine.fpx.mp2_add(A, A24minus, A24plus);
         engine.fpx.mp2_sub_p2(A, A24minus, A24minus);
@@ -329,13 +329,13 @@ public class SIDH
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Bob[ii++];
-                engine.isogeny.xTPLe(R, R, A24minus, A24plus, m);
+                engine.isogeny.XTplE(R, R, A24minus, A24plus, m);
                 index += m;
             }
-            engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+            engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
 
             for (i = 0; i < npts; i++) {
-                engine.isogeny.eval_3_isog(pts[i], coeff);
+                engine.isogeny.Eval3Isog(pts[i], coeff);
             }
 
             engine.fpx.fp2copy(pts[npts-1].X, R.X);
@@ -344,14 +344,12 @@ public class SIDH
             npts -= 1;
         }
 
-        engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+        engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
         engine.fpx.fp2add(A24plus, A24minus, A);
         engine.fpx.fp2add(A, A, A);
         engine.fpx.fp2sub(A24plus, A24minus, A24plus);
-        engine.isogeny.j_inv(A, A24plus, jinv);
+        engine.isogeny.JInv(A, A24plus, jinv);
         engine.fpx.fp2_encode(jinv, jinvariant_, 0);    // Format shared secret
     }
-
 }
-
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sike/SIDH_Compressed.cs b/crypto/src/pqc/crypto/sike/SIDH_Compressed.cs
index f701aea60..cf69d0f82 100644
--- a/crypto/src/pqc/crypto/sike/SIDH_Compressed.cs
+++ b/crypto/src/pqc/crypto/sike/SIDH_Compressed.cs
@@ -1,18 +1,20 @@
 using System;
+
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIDH_Compressed
+internal sealed class SidhCompressed
 {
-    private SIKEEngine engine;
+    private readonly SikeEngine engine;
 
-    public SIDH_Compressed(SIKEEngine engine)
+    internal SidhCompressed(SikeEngine engine)
     {
         this.engine = engine;
     }
-    protected void init_basis(ulong[] gen, ulong[][] XP, ulong[][] XQ, ulong[][] XR)
+
+    internal void init_basis(ulong[] gen, ulong[][] XP, ulong[][] XQ, ulong[][] XR)
     { // Initialization of basis points
 
         engine.fpx.fpcopy(gen, 0, XP[0]);
@@ -23,8 +25,7 @@ public class SIDH_Compressed
         engine.fpx.fpcopy(gen, 5*engine.param.NWORDS_FIELD, XR[1]);
     }
 
-
-    protected internal void FormatPrivKey_B(byte[] skB)
+    internal void FormatPrivKey_B(byte[] skB)
     {
         skB[engine.param.SECRETKEY_B_BYTES-2] &= (byte)engine.param.MASK3_BOB;
         skB[engine.param.SECRETKEY_B_BYTES-1] &= (byte)engine.param.MASK2_BOB;    // Clear necessary bits so that 3*ephemeralsk is still less than Bob_order
@@ -33,7 +34,7 @@ public class SIDH_Compressed
 
     // Generation of Alice's secret key
     // Outputs random value in [0, 2^eA - 1]
-    protected void random_mod_order_A(byte[] random_digits, SecureRandom random)
+    internal void random_mod_order_A(byte[] random_digits, SecureRandom random)
     {
         byte[] temp = new byte[engine.param.SECRETKEY_A_BYTES];
         random.NextBytes(temp);
@@ -44,7 +45,7 @@ public class SIDH_Compressed
 
     // Generation of Bob's secret key
     // Outputs random value in [0, 2^Floor(Log(2, oB)) - 1]
-    protected void random_mod_order_B(byte[] random_digits, SecureRandom random)
+    internal void random_mod_order_B(byte[] random_digits, SecureRandom random)
     {
         byte[] temp = new byte[engine.param.SECRETKEY_B_BYTES];
         random.NextBytes(temp);
@@ -53,7 +54,7 @@ public class SIDH_Compressed
     }
 
     // Project 3-pouint ladder
-    protected void Ladder3pt_dual(PointProj[] Rs, ulong[] m, uint AliceOrBob, PointProj R, ulong[][] A24)
+    internal void Ladder3pt_dual(PointProj[] Rs, ulong[] m, uint AliceOrBob, PointProj R, ulong[][] A24)
     {
         PointProj R0 = new PointProj(engine.param.NWORDS_FIELD),
                   R2 = new PointProj(engine.param.NWORDS_FIELD);
@@ -83,18 +84,16 @@ public class SIDH_Compressed
             prevbit = bit;
             mask = 0 - (ulong)swap;
 
-            engine.isogeny.swap_points(R, R2, mask);
-            engine.isogeny.xDBLADD(R0, R2, R.X, A24);
+            engine.isogeny.SwapPoints(R, R2, mask);
+            engine.isogeny.XDblAdd(R0, R2, R.X, A24);
             engine.fpx.fp2mul_mont(R2.X, R.Z, R2.X);
         }
         swap = 0 ^ prevbit;
         mask = 0 - (ulong)swap;
-        engine.isogeny.swap_points(R, R2, mask);
+        engine.isogeny.SwapPoints(R, R2, mask);
     }
 
-
-
-    protected void Elligator2(ulong[][] a24, uint[] r, uint rIndex, ulong[][] x, byte[] bit, uint bitOffset, uint COMPorDEC)
+    internal void Elligator2(ulong[][] a24, uint[] r, uint rIndex, ulong[][] x, byte[] bit, uint bitOffset, uint COMPorDEC)
     { // Generate an x-coordinate of a pouint on curve with (affine) coefficient a24 
         // Use the counter r
         uint i;
@@ -104,8 +103,8 @@ public class SIDH_Compressed
                 N = new ulong[engine.param.NWORDS_FIELD],
                 temp0 = new ulong[engine.param.NWORDS_FIELD],
                 temp1 = new ulong[engine.param.NWORDS_FIELD];
-        ulong[][] A = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            y2 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            y2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         uint t_ptr = 0;
 
 //        System.out.print("a24: ");
@@ -187,8 +186,7 @@ public class SIDH_Compressed
 //        {System.out.printf("%016x ", x[di][dj] );}System.out.Println();}
     }
 
-
-    protected void make_positive(ulong[][] x)
+    internal void make_positive(ulong[][] x)
     {
         uint nbytes = engine.param.NWORDS_FIELD;
         ulong[] zero = new ulong[engine.param.NWORDS_FIELD];
@@ -211,15 +209,14 @@ public class SIDH_Compressed
         engine.fpx.to_fp2mont(x, x);
     }
 
-
-    protected void BiQuad_affine(ulong[][] a24, ulong[][] x0, ulong[][] x1, PointProj R)
+    internal void BiQuad_affine(ulong[][] a24, ulong[][] x0, ulong[][] x1, PointProj R)
     {
-        ulong[][] Ap2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            aa = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            bb = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            cc = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] Ap2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            aa = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            bb = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            cc = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2add(a24, a24, Ap2);
         engine.fpx.fp2add(Ap2, Ap2, Ap2);    // Ap2 = a+2 = 4*a24
@@ -257,8 +254,7 @@ public class SIDH_Compressed
         engine.fpx.fp2add(aa, aa, R.Z);
     }
 
-
-    protected void get_4_isog_dual(PointProj P, ulong[][] A24, ulong[][] C24, ulong[][][] coeff)
+    internal void get_4_isog_dual(PointProj P, ulong[][] A24, ulong[][] C24, ulong[][][] coeff)
     {
         engine.fpx.fp2sub(P.X, P.Z, coeff[1]);
         engine.fpx.fp2add(P.X, P.Z, coeff[2]);
@@ -273,9 +269,9 @@ public class SIDH_Compressed
 
 //    if (engine.param.OALICE_BITS % 2 == 1)
 
-    protected void eval_dual_2_isog(ulong[][] X2, ulong[][] Z2, PointProj P)
+    internal void eval_dual_2_isog(ulong[][] X2, ulong[][] Z2, PointProj P)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2add(P.X, P.Z, t0);
         engine.fpx.fp2sub(P.X, P.Z, P.Z);
@@ -286,10 +282,10 @@ public class SIDH_Compressed
         engine.fpx.fp2mul_mont(Z2, t0, P.X);
     }
 
-    protected void eval_final_dual_2_isog(PointProj P)
+    internal void eval_final_dual_2_isog(PointProj P)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         ulong[] t2 = new ulong[engine.param.NWORDS_FIELD];
 
         engine.fpx.fp2add(P.X, P.Z, t0);
@@ -304,7 +300,7 @@ public class SIDH_Compressed
     }
 
 
-    protected void eval_dual_4_isog_shared(ulong[][] X4pZ4, ulong[][] X42, ulong[][] Z42, ulong[][][] coeff, uint coeffOffset)
+    internal void eval_dual_4_isog_shared(ulong[][] X4pZ4, ulong[][] X42, ulong[][] Z42, ulong[][][] coeff, uint coeffOffset)
     {
 //        System.out.print("coeff0: ");
 //        for (uint di = 0; di < 2; di++){
@@ -338,13 +334,12 @@ public class SIDH_Compressed
 //                System.out.printf("%016x ", coeff[2 + coeffOffset][di][dj]);}System.out.Println();}
     }
 
-
-    protected void eval_dual_4_isog(ulong[][] A24, ulong[][] C24, ulong[][][] coeff, uint coeffOffset, PointProj P)
+    internal void eval_dual_4_isog(ulong[][] A24, ulong[][] C24, ulong[][][] coeff, uint coeffOffset, PointProj P)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2add(P.X, P.Z, t0);
         engine.fpx.fp2sub(P.X, P.Z, t1);
@@ -363,8 +358,7 @@ public class SIDH_Compressed
         engine.fpx.fp2mul_mont(coeff[2 + coeffOffset], P.Z, P.Z);
     }
 
-
-    protected void eval_full_dual_4_isog(ulong[][][][] As, PointProj P)
+    internal void eval_full_dual_4_isog(ulong[][][][] As, PointProj P)
     {
         //todo : check
         // First all 4-isogenies
@@ -382,8 +376,7 @@ public class SIDH_Compressed
         eval_final_dual_2_isog(P);    // to A = 0
     }
 
-
-    protected void TripleAndParabola_proj(PointProjFull R, ulong[][] l1x, ulong[][] l1z)
+    internal void TripleAndParabola_proj(PointProjFull R, ulong[][] l1x, ulong[][] l1z)
     {
         engine.fpx.fp2sqr_mont(R.X, l1z);
         engine.fpx.fp2add(l1z, l1z, l1x);
@@ -392,11 +385,10 @@ public class SIDH_Compressed
         engine.fpx.fp2add(R.Y, R.Y, l1z);
     }
 
-
-    protected void Tate3_proj(PointProjFull P, PointProjFull Q, ulong[][] gX, ulong[][] gZ)
+    internal void Tate3_proj(PointProjFull P, PointProjFull Q, ulong[][] gX, ulong[][] gZ)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            l1x = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            l1x = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         TripleAndParabola_proj(P, l1x, gZ);
         engine.fpx.fp2sub(Q.X, P.X, gX);
@@ -406,12 +398,11 @@ public class SIDH_Compressed
         engine.fpx.fp2add(gX, t0, gX);
     }
 
-
-    protected void FinalExpo3(ulong[][] gX, ulong[][] gZ)
+    internal void FinalExpo3(ulong[][] gX, ulong[][] gZ)
     {
         uint i;
 
-        ulong[][] f_ = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] f_ = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2copy(gZ, f_);
         engine.fpx.fpnegPRIME(f_[1]);
@@ -430,13 +421,12 @@ public class SIDH_Compressed
         }
     }
 
-
-    protected void FinalExpo3_2way(ulong[][][] gX, ulong[][][] gZ)
+    internal void FinalExpo3_2way(ulong[][][] gX, ulong[][][] gZ)
     {
         uint i, j;
 
-        ulong[][][] f_ = Utils.InitArray(2, 2, engine.param.NWORDS_FIELD),
-            finv = Utils.InitArray(2, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] f_ = SikeUtilities.InitArray(2, 2, engine.param.NWORDS_FIELD),
+            finv = SikeUtilities.InitArray(2, 2, engine.param.NWORDS_FIELD);
 
         for(i = 0; i < 2; i++)
         {
@@ -461,14 +451,13 @@ public class SIDH_Compressed
         }
     }
 
-
     private bool FirstPoint_dual(PointProj P, PointProjFull R, byte[] ind)
     {
         PointProjFull R3 = new PointProjFull(engine.param.NWORDS_FIELD),
                       S3 = new PointProjFull(engine.param.NWORDS_FIELD);
 
-        ulong[][][] gX = Utils.InitArray(2, 2, engine.param.NWORDS_FIELD),
-            gZ = Utils.InitArray(2, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] gX = SikeUtilities.InitArray(2, 2, engine.param.NWORDS_FIELD),
+            gZ = SikeUtilities.InitArray(2, 2, engine.param.NWORDS_FIELD);
         ulong[] zero = new ulong[engine.param.NWORDS_FIELD];
         uint nbytes = engine.param.NWORDS_FIELD;// (((engine.param.NBITS_FIELD)+7)/8);
         uint alpha,beta;
@@ -578,13 +567,12 @@ public class SIDH_Compressed
         return true;
     }
 
-
     private bool SecondPoint_dual(PointProj P, PointProjFull R, byte[] ind)
     {
         PointProjFull RS3 = new PointProjFull(engine.param.NWORDS_FIELD);
 
-        ulong[][] gX = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            gZ = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] gX = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            gZ = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         ulong[] zero = new ulong[engine.param.NWORDS_FIELD];
         uint nbytes = engine.param.NWORDS_FIELD;
@@ -610,8 +598,7 @@ public class SIDH_Compressed
         }
     }
 
-
-    protected void FirstPoint3n(ulong[][] a24, ulong[][][][] As, ulong[][] x, PointProjFull R, uint[] r, byte[] ind, byte[] bitEll)
+    internal void FirstPoint3n(ulong[][] a24, ulong[][][][] As, ulong[][] x, PointProjFull R, uint[] r, byte[] ind, byte[] bitEll)
     {
         bool b = false;
         PointProj P = new PointProj(engine.param.NWORDS_FIELD);
@@ -666,8 +653,7 @@ public class SIDH_Compressed
         }
     }
 
-
-    protected void SecondPoint3n(ulong[][] a24, ulong[][][][] As, ulong[][] x, PointProjFull R, uint[] r, byte[] ind, byte[] bitEll)
+    internal void SecondPoint3n(ulong[][] a24, ulong[][][][] As, ulong[][] x, PointProjFull R, uint[] r, byte[] ind, byte[] bitEll)
     {
         bool b = false;
         PointProj P = new PointProj(engine.param.NWORDS_FIELD);
@@ -722,12 +708,11 @@ public class SIDH_Compressed
         }
     }
 
-
-    protected void makeDiff(PointProjFull R, PointProjFull S, PointProj D)
+    internal void makeDiff(PointProjFull R, PointProjFull S, PointProj D)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         uint nbytes = engine.param.NWORDS_FIELD;
 
         engine.fpx.fp2sub(R.X, S.X, t0);
@@ -747,12 +732,11 @@ public class SIDH_Compressed
         }
     }
 
-
-    protected void BuildOrdinary3nBasis_dual(ulong[][] a24, ulong[][][][] As, PointProjFull[] R, uint[] r, uint[] bitsEll, uint bitsEllOffset)
+    internal void BuildOrdinary3nBasis_dual(ulong[][] a24, ulong[][][][] As, PointProjFull[] R, uint[] r, uint[] bitsEll, uint bitsEllOffset)
     {
         PointProj D = new PointProj(engine.param.NWORDS_FIELD);
 
-        ulong[][][] xs = Utils.InitArray(2, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] xs = SikeUtilities.InitArray(2, 2, engine.param.NWORDS_FIELD);
         byte[] ind = new byte[1],
                bit = new byte[1];
 
@@ -807,22 +791,21 @@ public class SIDH_Compressed
         makeDiff(R[0], R[1], D);
     }
 
-
-    protected void FullIsogeny_A_dual(byte[] PrivateKeyA, ulong[][][][] As, ulong[][] a24, uint sike)
+    internal void FullIsogeny_A_dual(byte[] PrivateKeyA, ulong[][][][] As, ulong[][] a24, uint sike)
     {
         // Input:  a private key PrivateKeyA in the range [0, 2^eA - 1]. 
         // Output: the public key PublicKeyA consisting of 3 elements in GF(p^2) which are encoded by removing leading 0 bytes.
         PointProj R = new PointProj(engine.param.NWORDS_FIELD);
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_ALICE];
 
-        ulong[][] XPA = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XQA = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XRA = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            C24 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] XPA = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XQA = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XRA = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            C24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
-        ulong[][][] coeff = Utils.InitArray(5, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(5, 2, engine.param.NWORDS_FIELD);
 
         uint i, row, m, index = 0, npts = 0, ii = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_ALICE];
@@ -923,9 +906,9 @@ public class SIDH_Compressed
         {
             PointProj S = new PointProj(engine.param.NWORDS_FIELD);
 
-            engine.isogeny.xDBLe(R, S, A24, C24, engine.param.OALICE_BITS - 1);
-            engine.isogeny.get_2_isog(S, A24, C24);
-            engine.isogeny.eval_2_isog(R, S);
+            engine.isogeny.XDblE(R, S, A24, C24, engine.param.OALICE_BITS - 1);
+            engine.isogeny.Get2Isog(S, A24, C24);
+            engine.isogeny.Eval2Isog(R, S);
             engine.fpx.fp2copy(S.X, As[engine.param.MAX_Alice][2]);
             engine.fpx.fp2copy(S.Z, As[engine.param.MAX_Alice][3]);
         }
@@ -943,7 +926,7 @@ public class SIDH_Compressed
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Alice[ii++];
-                engine.isogeny.xDBLe(R, R, A24, C24, 2*m);
+                engine.isogeny.XDblE(R, R, A24, C24, 2*m);
                 index += m;
             }
 
@@ -974,7 +957,7 @@ public class SIDH_Compressed
             get_4_isog_dual(R, A24, C24, coeff);
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_4_isog(pts[i], coeff);
+                engine.isogeny.Eval4Isog(pts[i], coeff);
 
 //                System.out.print(i + "ptsX: ");
 //                for (uint di = 0; di < 2; di++){
@@ -1041,8 +1024,7 @@ public class SIDH_Compressed
 //                System.out.printf("%016x ", a24[di][dj]);}System.out.Println();}
     }
 
-
-    protected void Dlogs3_dual(ulong[][][] f, int[] D, ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1)
+    internal void Dlogs3_dual(ulong[][][] f, int[] D, ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1)
     {
         solve_dlog(f[0], D, d0, 3);
         solve_dlog(f[2], D, c0, 3);
@@ -1052,8 +1034,7 @@ public class SIDH_Compressed
         engine.fpx.mp_sub(engine.param.Bob_order, c1, c1, engine.param.NWORDS_ORDER);
     }
 
-
-    protected void BuildOrdinary3nBasis_Decomp_dual(ulong[][] A24, PointProj[] Rs, uint[] r, uint[] bitsEll, uint bitsEllIndex)
+    internal void BuildOrdinary3nBasis_Decomp_dual(ulong[][] A24, PointProj[] Rs, uint[] r, uint[] bitsEll, uint bitsEllIndex)
     {
         byte[] bitEll = new byte[2];
 
@@ -1069,12 +1050,11 @@ public class SIDH_Compressed
         BiQuad_affine(A24, Rs[0].X, Rs[1].X, Rs[2]);
     }
 
-
-    protected void PKADecompression_dual(byte[] SecretKeyB, byte[] CompressedPKA, PointProj R, ulong[][] A)
+    internal void PKADecompression_dual(byte[] SecretKeyB, byte[] CompressedPKA, PointProj R, ulong[][] A)
     {
         byte bit;
         uint[] rs = new uint[3];
-        ulong[][] A24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         PointProj[] Rs = new PointProj[3];
         Rs[0] = new PointProj(engine.param.NWORDS_FIELD);
         Rs[1] = new PointProj(engine.param.NWORDS_FIELD);
@@ -1118,7 +1098,7 @@ public class SIDH_Compressed
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, Rs[0].Z[0]);
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, Rs[1].Z[0]);
 
-        engine.isogeny.swap_points(Rs[0], Rs[1], ((ulong) -bit));//todo check
+        engine.isogeny.SwapPoints(Rs[0], Rs[1], ((ulong) -bit));//todo check
         engine.fpx.decode_to_digits(SecretKeyB, 0, SKin, engine.param.SECRETKEY_B_BYTES, engine.param.NWORDS_ORDER);
         engine.fpx.to_Montgomery_mod_order(SKin, t1, engine.param.Bob_order, engine.param.Montgomery_RB2, engine.param.Montgomery_RB1);    // Converting to Montgomery representation
         engine.fpx.decode_to_digits(CompressedPKA, 0, temp, engine.param.ORDER_B_ENCODED_BYTES, engine.param.NWORDS_ORDER);
@@ -1153,13 +1133,12 @@ public class SIDH_Compressed
         engine.isogeny.Double(R, R, A24, engine.param.OALICE_BITS);    // x, z := Double(A24, x, 1, eA);
     }
 
-
-    protected void Compress_PKA_dual(ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1, ulong[][] a24, uint[] rs, byte[] CompressedPKA)
+    internal void Compress_PKA_dual(ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1, ulong[][] a24, uint[] rs, byte[] CompressedPKA)
     {
         uint bit;
         ulong[] temp = new ulong[engine.param.NWORDS_ORDER],
                inv = new ulong[engine.param.NWORDS_ORDER];
-        ulong[][] A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2add(a24,a24,A);
         engine.fpx.fp2add(A,A,A);
@@ -1229,14 +1208,14 @@ public class SIDH_Compressed
 
     // Alice's ephemeral public key generation using compression -- SIKE protocol
     // Output: PrivateKeyA[MSG_BYTES + engine.param.SECRETKEY_A_BYTES] <- x(K_A) where K_A = PA + sk_A*Q_A
-    protected internal uint EphemeralKeyGeneration_A_extended(byte[] PrivateKeyA, byte[] CompressedPKA)
+    internal uint EphemeralKeyGeneration_A_extended(byte[] PrivateKeyA, byte[] CompressedPKA)
     {
         uint[] rs = new uint[3];
         int[] D = new int[engine.param.DLEN_3];
-        ulong[][] a24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] a24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         
-        ulong[][][][] As = Utils.InitArray(engine.param.MAX_Alice + 1, 5, 2, engine.param.NWORDS_FIELD);
-        ulong[][][] f = Utils.InitArray(4, 2, engine.param.NWORDS_FIELD);
+        ulong[][][][] As = SikeUtilities.InitArray(engine.param.MAX_Alice + 1, 5, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] f = SikeUtilities.InitArray(4, 2, engine.param.NWORDS_FIELD);
         ulong[] c0 = new ulong[engine.param.NWORDS_ORDER],
                d0 = new ulong[engine.param.NWORDS_ORDER],
                c1 = new ulong[engine.param.NWORDS_ORDER],
@@ -1309,9 +1288,9 @@ public class SIDH_Compressed
                d0 = new ulong[engine.param.NWORDS_ORDER],
                c1 = new ulong[engine.param.NWORDS_ORDER],
                d1 = new ulong[engine.param.NWORDS_ORDER];
-        ulong[][] a24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-        ulong[][][] f = Utils.InitArray(4, 2, engine.param.NWORDS_FIELD);
-        ulong[][][][] As = Utils.InitArray(engine.param.MAX_Alice + 1, 5, 2, engine.param.NWORDS_FIELD);
+        ulong[][] a24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] f = SikeUtilities.InitArray(4, 2, engine.param.NWORDS_FIELD);
+        ulong[][][][] As = SikeUtilities.InitArray(engine.param.MAX_Alice + 1, 5, 2, engine.param.NWORDS_FIELD);
         PointProjFull[] Rs = new PointProjFull[2];
 
         FullIsogeny_A_dual(PrivateKeyA, As, a24, 0);
@@ -1331,14 +1310,14 @@ public class SIDH_Compressed
     {
         uint i, ii = 0, row, m, index = 0, npts = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_BOB];
-        ulong[][] A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24minus = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24minus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         PointProj R = new PointProj(engine.param.NWORDS_FIELD);
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_BOB];
-        ulong[][] jinv = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-        ulong[][][] coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);
-        ulong[][] param_A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] jinv = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);
+        ulong[][] param_A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         PKADecompression_dual(PrivateKeyB, PKA, R, param_A);
         engine.fpx.fp2copy(param_A, A);
@@ -1361,14 +1340,14 @@ public class SIDH_Compressed
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Bob[ii++];
-                engine.isogeny.xTPLe(R, R, A24minus, A24plus, m);
+                engine.isogeny.XTplE(R, R, A24minus, A24plus, m);
                 index += m;
             }
-            engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+            engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
 
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_3_isog(pts[i], coeff);
+                engine.isogeny.Eval3Isog(pts[i], coeff);
             }
 
             engine.fpx.fp2copy(pts[npts-1].X, R.X);
@@ -1377,23 +1356,22 @@ public class SIDH_Compressed
             npts -= 1;
         }
 
-        engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+        engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
         engine.fpx.fp2add(A24plus, A24minus, A);
         engine.fpx.fp2add(A, A, A);
         engine.fpx.fp2sub(A24plus, A24minus, A24plus);
-        engine.isogeny.j_inv(A, A24plus, jinv);
+        engine.isogeny.JInv(A, A24plus, jinv);
         engine.fpx.fp2_encode(jinv, SharedSecretB, 0);    // Format shared secret
 
         return 0;
     }
 
-
-    protected void BuildEntangledXonly(ulong[][] A, PointProj[] R, byte[] qnr, byte[] ind)
+    internal void BuildEntangledXonly(ulong[][] A, PointProj[] R, byte[] qnr, byte[] ind)
     {
         ulong[] s = new ulong[engine.param.NWORDS_FIELD];
         ulong[][] t_ptr,
-            r = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            r = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         uint t_ptrOffset = 0;
 
         // Select the correct table
@@ -1454,14 +1432,13 @@ public class SIDH_Compressed
         engine.fpx.fp2mul_mont(t, r, R[2].X);
     }
 
-
-    protected void RecoverY(ulong[][] A, PointProj[] xs, PointProjFull[] Rs)
+    internal void RecoverY(ulong[][] A, PointProj[] xs, PointProjFull[] Rs)
     {
-        ulong[][] t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t4 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t4 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         engine.fpx.fp2mul_mont(xs[2].X, xs[1].Z, t0);
         engine.fpx.fp2mul_mont(xs[1].X, xs[2].Z, t1);
@@ -1495,12 +1472,11 @@ public class SIDH_Compressed
         engine.fpx.fp2mul_mont(Rs[1].Y, Rs[1].Z, Rs[1].Y);
     }
 
-
-    protected void BuildOrdinary2nBasis_dual(ulong[][] A, ulong[][][][] Ds, PointProjFull[] Rs, byte[] qnr, byte[] ind)
+    internal void BuildOrdinary2nBasis_dual(ulong[][] A, ulong[][][][] Ds, PointProjFull[] Rs, byte[] qnr, byte[] ind)
     {
         uint i;
         ulong[] t0 = new ulong[engine.param.NWORDS_FIELD];
-        ulong[][] A6 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A6 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         PointProj[] xs = new PointProj[3];
         xs[0] = new PointProj(engine.param.NWORDS_FIELD);
         xs[1] = new PointProj(engine.param.NWORDS_FIELD);
@@ -1543,9 +1519,9 @@ public class SIDH_Compressed
         // Move them back to A = 6 
         for(i = 0; i < engine.param.MAX_Bob; i++)
         {
-            engine.isogeny.eval_3_isog(xs[0], Ds[engine.param.MAX_Bob-1-i]);
-            engine.isogeny.eval_3_isog(xs[1], Ds[engine.param.MAX_Bob-1-i]);
-            engine.isogeny.eval_3_isog(xs[2], Ds[engine.param.MAX_Bob-1-i]);
+            engine.isogeny.Eval3Isog(xs[0], Ds[engine.param.MAX_Bob-1-i]);
+            engine.isogeny.Eval3Isog(xs[1], Ds[engine.param.MAX_Bob-1-i]);
+            engine.isogeny.Eval3Isog(xs[2], Ds[engine.param.MAX_Bob-1-i]);
         }
 
         // Recover y-coordinates with a single sqrt on A = 6
@@ -1561,17 +1537,17 @@ public class SIDH_Compressed
     // Bob's ephemeral public key generation
     // Input:  a private key PrivateKeyB in the range [0, 2^Floor(Log(2,oB)) - 1].
     // Output: the public key PublicKeyB consisting of 3 elements in GF(p^2) which are encoded by removing leading 0 bytes.
-    protected void FullIsogeny_B_dual(byte[] PrivateKeyB, ulong[][][][] Ds, ulong[][] A)
+    internal void FullIsogeny_B_dual(byte[] PrivateKeyB, ulong[][][][] Ds, ulong[][] A)
     {
         PointProj R = new PointProj(engine.param.NWORDS_FIELD),
         Q3 = new PointProj(engine.param.NWORDS_FIELD);
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_BOB];
-        ulong[][] XPB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XQB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XRB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24minus = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-        ulong[][][] coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);
+        ulong[][] XPB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XQB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XRB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24minus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);
         uint i, row, m, index = 0, npts = 0, ii = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_BOB];
         ulong[] SecretKeyB = new ulong[engine.param.NWORDS_ORDER];
@@ -1619,15 +1595,15 @@ public class SIDH_Compressed
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Bob[ii++];
-                engine.isogeny.xTPLe(R, R, A24minus, A24plus, m);
+                engine.isogeny.XTplE(R, R, A24minus, A24plus, m);
                 index += m;
             }
-            engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+            engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_3_isog(pts[i], coeff);
+                engine.isogeny.Eval3Isog(pts[i], coeff);
             }
-            engine.isogeny.eval_3_isog(Q3, coeff);    // Kernel of dual 
+            engine.isogeny.Eval3Isog(Q3, coeff);    // Kernel of dual 
             engine.fpx.fp2sub(Q3.X,Q3.Z,Ds[row-1][0]);
             engine.fpx.fp2add(Q3.X,Q3.Z,Ds[row-1][1]);
 
@@ -1636,8 +1612,8 @@ public class SIDH_Compressed
             index = pts_index[npts-1];
             npts -= 1;
         }
-        engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
-        engine.isogeny.eval_3_isog(Q3, coeff);    // Kernel of dual 
+        engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
+        engine.isogeny.Eval3Isog(Q3, coeff);    // Kernel of dual 
         engine.fpx.fp2sub(Q3.X, Q3.Z, Ds[engine.param.MAX_Bob-1][0]);
         engine.fpx.fp2add(Q3.X, Q3.Z, Ds[engine.param.MAX_Bob-1][1]);
 
@@ -1648,8 +1624,7 @@ public class SIDH_Compressed
         engine.fpx.fp2add(A, A, A);    // A = 2*(A24plus+A24mins)/(A24plus-A24minus) 
     }
 
-
-    protected void Dlogs2_dual(ulong[][][] f, int[] D, ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1)
+    internal void Dlogs2_dual(ulong[][][] f, int[] D, ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1)
     {
         solve_dlog(f[0], D, d0, 2);
         solve_dlog(f[2], D, c0, 2);
@@ -1659,12 +1634,11 @@ public class SIDH_Compressed
         engine.fpx.mp_sub(engine.param.Alice_order, c1, c1, engine.param.NWORDS_ORDER);
     }
 
-
-    protected void BuildEntangledXonly_Decomp(ulong[][] A, PointProj[] R, uint qnr, uint ind)
+    internal void BuildEntangledXonly_Decomp(ulong[][] A, PointProj[] R, uint qnr, uint ind)
     {
         ulong[][] t_ptr,
-            r = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            r = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         // Select the correct table
         if ( qnr == 1 )
@@ -1712,12 +1686,12 @@ public class SIDH_Compressed
     }
 
     // Bob's PK decompression -- SIKE protocol
-    protected void PKBDecompression_extended(byte[] SecretKeyA, uint SecretKeyAOffset, byte[] CompressedPKB, PointProj R, ulong[][] A, byte[] tphiBKA_t, uint tphiBKA_tOffset)
+    internal void PKBDecompression_extended(byte[] SecretKeyA, uint SecretKeyAOffset, byte[] CompressedPKB, PointProj R, ulong[][] A, byte[] tphiBKA_t, uint tphiBKA_tOffset)
     { 
         ulong mask = unchecked((ulong) -1L);
         uint qnr, ind;
-        ulong[][] A24 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            Adiv2 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            Adiv2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         
         ulong[] tmp1 = new ulong[2*engine.param.NWORDS_ORDER],
                 tmp2 = new ulong[2*engine.param.NWORDS_ORDER],
@@ -1805,12 +1779,12 @@ public class SIDH_Compressed
             engine.fpx.inv_mod_orderA(tmp2, inv);
             engine.fpx.multiply(inv, tmp1, scal, engine.param.NWORDS_ORDER);
             scal[engine.param.NWORDS_ORDER-1] &= (ulong)mask;
-            engine.isogeny.swap_points(Rs[0], Rs[1], unchecked((ulong)-1L));//check
+            engine.isogeny.SwapPoints(Rs[0], Rs[1], unchecked((ulong)-1L));//check
             Ladder3pt_dual(Rs, scal, engine.param.ALICE, R, A24);
         }
 
         engine.fpx.fp2div2(A,Adiv2);
-        engine.isogeny.xTPLe_fast(R, R, Adiv2, engine.param.OBOB_EXPON);
+        engine.isogeny.XTplEFast(R, R, Adiv2, engine.param.OBOB_EXPON);
 
         engine.fpx.fp2_encode(R.X, tphiBKA_t, tphiBKA_tOffset);
         engine.fpx.fp2_encode(R.Z, tphiBKA_t, tphiBKA_tOffset + engine.param.FP2_ENCODED_BYTES);
@@ -1818,7 +1792,7 @@ public class SIDH_Compressed
     }
 
     // Bob's PK compression -- SIKE protocol
-    protected void Compress_PKB_dual_extended(ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1, ulong[][] A, byte[] qnr, byte[] ind, byte[] CompressedPKB)
+    internal void Compress_PKB_dual_extended(ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1, ulong[][] A, byte[] qnr, byte[] ind, byte[] CompressedPKB)
     {
         ulong mask = unchecked((ulong) -1L);
         ulong[] tmp = new ulong[2*engine.param.NWORDS_ORDER],
@@ -1874,12 +1848,12 @@ public class SIDH_Compressed
     }
 
     // Bob's PK decompression -- SIDH protocol
-    protected void PKBDecompression(byte[] SecretKeyA, uint SecretKeyAOffset, byte[] CompressedPKB, PointProj R, ulong[][] A)
+    internal void PKBDecompression(byte[] SecretKeyA, uint SecretKeyAOffset, byte[] CompressedPKB, PointProj R, ulong[][] A)
     {
         ulong mask = unchecked((ulong) -1L);
         uint bit,qnr,ind;
 
-        ulong[][] A24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         ulong[] tmp1 = new ulong[2*engine.param.NWORDS_ORDER],
                tmp2 = new ulong[2*engine.param.NWORDS_ORDER],
                vone = new ulong[2*engine.param.NWORDS_ORDER],
@@ -1907,7 +1881,7 @@ public class SIDH_Compressed
         engine.fpx.fp2div2(A24, A24);
 
         engine.fpx.decode_to_digits(SecretKeyA, SecretKeyAOffset, SKin, engine.param.SECRETKEY_A_BYTES, engine.param.NWORDS_ORDER);
-        engine.isogeny.swap_points(Rs[0], Rs[1], 0-(ulong)bit);
+        engine.isogeny.SwapPoints(Rs[0], Rs[1], 0-(ulong)bit);
         if (bit == 0)
         {
             engine.fpx.decode_to_digits(CompressedPKB, engine.param.ORDER_A_ENCODED_BYTES, comp_temp, engine.param.ORDER_A_ENCODED_BYTES, engine.param.NWORDS_ORDER);
@@ -1939,11 +1913,11 @@ public class SIDH_Compressed
             Ladder3pt_dual(Rs, vone, engine.param.ALICE, R, A24);
         }
         engine.fpx.fp2div2(A, A24);
-        engine.isogeny.xTPLe_fast(R, R, A24, engine.param.OBOB_EXPON);
+        engine.isogeny.XTplEFast(R, R, A24, engine.param.OBOB_EXPON);
     }
 
     // Bob's PK compression -- SIDH protocol
-    protected void Compress_PKB_dual(ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1, ulong[][] A, byte[] qnr, byte[] ind, byte[] CompressedPKB)
+    internal void Compress_PKB_dual(ulong[] d0, ulong[] c0, ulong[] d1, ulong[] c1, ulong[][] A, byte[] qnr, byte[] ind, byte[] CompressedPKB)
     {
         
         ulong[] tmp = new ulong[2*engine.param.NWORDS_ORDER],
@@ -1988,7 +1962,7 @@ public class SIDH_Compressed
     }
 
     // Bob's ephemeral public key generation using compression -- SIKE protocol
-    protected internal uint EphemeralKeyGeneration_B_extended(byte[] PrivateKeyB, byte[] CompressedPKB, uint sike)
+    internal uint EphemeralKeyGeneration_B_extended(byte[] PrivateKeyB, byte[] CompressedPKB, uint sike)
     {
         byte[] qnr = new byte[1], ind = new byte[1];
         int[] D = new int[engine.param.DLEN_2];
@@ -1996,9 +1970,9 @@ public class SIDH_Compressed
                d0 = new ulong[engine.param.NWORDS_ORDER],
                c1 = new ulong[engine.param.NWORDS_ORDER],
                d1 = new ulong[engine.param.NWORDS_ORDER];
-        ulong[][][][] Ds = Utils.InitArray(engine.param.MAX_Bob, 2, 2, engine.param.NWORDS_FIELD);
-        ulong[][][] f = Utils.InitArray(4, 2, engine.param.NWORDS_FIELD);
-        ulong[][] A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][][] Ds = SikeUtilities.InitArray(engine.param.MAX_Bob, 2, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] f = SikeUtilities.InitArray(4, 2, engine.param.NWORDS_FIELD);
+        ulong[][] A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         PointProjFull[] Rs = new PointProjFull[2];
         Rs[0] = new PointProjFull(engine.param.NWORDS_FIELD);
@@ -2112,25 +2086,25 @@ public class SIDH_Compressed
     }
 
     // Bob's ephemeral public key generation using compression -- SIDH protocol
-    protected uint EphemeralKeyGeneration_B(byte[] PrivateKeyB, byte[] CompressedPKB)
+    internal uint EphemeralKeyGeneration_B(byte[] PrivateKeyB, byte[] CompressedPKB)
     {
         return EphemeralKeyGeneration_B_extended(PrivateKeyB, CompressedPKB, 0);
     }
 
     // Alice's ephemeral shared secret computation using compression -- SIKE protocol
-    protected internal uint EphemeralSecretAgreement_A_extended(byte[] PrivateKeyA, uint PrivateKeyAOffset, byte[] PKB, byte[] SharedSecretA, uint sike)
+    internal uint EphemeralSecretAgreement_A_extended(byte[] PrivateKeyA, uint PrivateKeyAOffset, byte[] PKB, byte[] SharedSecretA, uint sike)
     {
         uint i, ii = 0, row, m, index = 0, npts = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_ALICE];
-        ulong[][] A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            C24 = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            C24 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         
         PointProj R = new PointProj(engine.param.NWORDS_FIELD);
         PointProj[] pts = new PointProj[engine.param.MAX_INT_POINTS_ALICE];
-        ulong[][] jinv = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            param_A = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-        ulong[][][] coeff = Utils.InitArray(5, 2, engine.param.NWORDS_FIELD);
+        ulong[][] jinv = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            param_A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(5, 2, engine.param.NWORDS_FIELD);
 
 
         if (sike == 1)
@@ -2151,9 +2125,9 @@ public class SIDH_Compressed
         {
             PointProj S = new PointProj(engine.param.NWORDS_FIELD);
 
-            engine.isogeny.xDBLe(R, S, A24plus, C24, (engine.param.OALICE_BITS - 1));
-            engine.isogeny.get_2_isog(S, A24plus, C24);
-            engine.isogeny.eval_2_isog(R, S);
+            engine.isogeny.XDblE(R, S, A24plus, C24, (engine.param.OALICE_BITS - 1));
+            engine.isogeny.Get2Isog(S, A24plus, C24);
+            engine.isogeny.Eval2Isog(R, S);
         }
 
         // Traverse tree
@@ -2167,14 +2141,14 @@ public class SIDH_Compressed
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Alice[ii++];
-                engine.isogeny.xDBLe(R, R, A24plus, C24, 2*m);
+                engine.isogeny.XDblE(R, R, A24plus, C24, 2*m);
                 index += m;
             }
-            engine.isogeny.get_4_isog(R, A24plus, C24, coeff);
+            engine.isogeny.Get4Isog(R, A24plus, C24, coeff);
 
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_4_isog(pts[i], coeff);
+                engine.isogeny.Eval4Isog(pts[i], coeff);
             }
 
             engine.fpx.fp2copy(pts[npts-1].X, R.X);
@@ -2183,11 +2157,11 @@ public class SIDH_Compressed
             npts -= 1;
         }
 
-        engine.isogeny.get_4_isog(R, A24plus, C24, coeff);
+        engine.isogeny.Get4Isog(R, A24plus, C24, coeff);
         engine.fpx.fp2add(A24plus, A24plus, A24plus);
         engine.fpx.fp2sub(A24plus, C24, A24plus);
         engine.fpx.fp2add(A24plus, A24plus, A24plus);
-        engine.isogeny.j_inv(A24plus, C24, jinv);
+        engine.isogeny.JInv(A24plus, C24, jinv);
         engine.fpx.fp2_encode(jinv, SharedSecretA, 0);    // Format shared secret
 
         return 0;
@@ -2198,13 +2172,12 @@ public class SIDH_Compressed
     // Inputs: Alice's PrivateKeyA is an even integer in the range [2, oA-2], where oA = 2^engine.param.OALICE_BITS.
     //         Bob's decompressed data consists of point_R in (X:Z) coordinates and the curve parameter param_A in GF(p^2).
     // Output: a shared secret SharedSecretA that consists of one element in GF(p^2).
-    uint EphemeralSecretAgreement_A(byte[] PrivateKeyA, uint PrivateKeyAOffset, byte[] PKB, byte[] SharedSecretA)
+    private uint EphemeralSecretAgreement_A(byte[] PrivateKeyA, uint PrivateKeyAOffset, byte[] PKB, byte[] SharedSecretA)
     {
         return EphemeralSecretAgreement_A_extended(PrivateKeyA, PrivateKeyAOffset, PKB, SharedSecretA, 0);
     }
 
-
-    protected internal byte validate_ciphertext(byte[] ephemeralsk_, byte[] CompressedPKB, byte[] xKA, uint xKAOffset, byte[] tphiBKA_t, uint tphiBKA_tOffset)
+    internal byte validate_ciphertext(byte[] ephemeralsk_, byte[] CompressedPKB, byte[] xKA, uint xKAOffset, byte[] tphiBKA_t, uint tphiBKA_tOffset)
     { // If ct validation passes returns 0, otherwise returns -1.
         PointProj[] phis = new PointProj[3],
                     pts = new PointProj[engine.param.MAX_INT_POINTS_BOB];
@@ -2216,16 +2189,16 @@ public class SIDH_Compressed
         PointProj R = new PointProj(engine.param.NWORDS_FIELD),
                   S = new PointProj(engine.param.NWORDS_FIELD);
 
-        ulong[][] XPB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XQB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            XRB = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24plus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A24minus = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            A = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            comp1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            comp2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            one = Utils.InitArray(2, engine.param.NWORDS_FIELD);
-        ulong[][][] coeff = Utils.InitArray(3, 2, engine.param.NWORDS_FIELD);;
+        ulong[][] XPB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XQB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            XRB = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24plus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A24minus = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            A = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            comp1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            comp2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            one = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] coeff = SikeUtilities.InitArray(3, 2, engine.param.NWORDS_FIELD);;
 
         uint i, row, m, index = 0, npts = 0, ii = 0;
         uint[] pts_index = new uint[engine.param.MAX_INT_POINTS_BOB];
@@ -2262,23 +2235,23 @@ public class SIDH_Compressed
                 engine.fpx.fp2copy(R.Z, pts[npts].Z);
                 pts_index[npts++] = index;
                 m = engine.param.strat_Bob[ii++];
-                engine.isogeny.xTPLe(R, R, A24minus, A24plus, m);
+                engine.isogeny.XTplE(R, R, A24minus, A24plus, m);
                 index += m;
             }
-            engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
+            engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
             for (i = 0; i < npts; i++)
             {
-                engine.isogeny.eval_3_isog(pts[i], coeff);
+                engine.isogeny.Eval3Isog(pts[i], coeff);
             }
-            engine.isogeny.eval_3_isog(phis[0], coeff);
+            engine.isogeny.Eval3Isog(phis[0], coeff);
 
             engine.fpx.fp2copy(pts[npts-1].X, R.X);
             engine.fpx.fp2copy(pts[npts-1].Z, R.Z);
             index = pts_index[npts-1];
             npts -= 1;
         }
-        engine.isogeny.get_3_isog(R, A24minus, A24plus, coeff);
-        engine.isogeny.eval_3_isog(phis[0], coeff);  // phis[0] <- phiB(PA + skA*QA)
+        engine.isogeny.Get3Isog(R, A24minus, A24plus, coeff);
+        engine.isogeny.Eval3Isog(phis[0], coeff);  // phis[0] <- phiB(PA + skA*QA)
 
         engine.fpx.fp2_decode(CompressedPKB, A, 4*engine.param.ORDER_A_ENCODED_BYTES);
 
@@ -2294,13 +2267,11 @@ public class SIDH_Compressed
         return (engine.fpx.cmp_f2elm(comp1, comp2));
     }
 
-
-
     /// DLOG
 
     // Computes the discrete log of input r = g^d where g = e(P,Q)^ell^e, and P,Q are torsion generators in the initial curve
     // Return the integer d
-    void solve_dlog(ulong[][] r, int[] D, ulong[] d, uint ell)
+    internal void solve_dlog(ulong[][] r, int[] D, ulong[] d, uint ell)
     {
         if (ell == 2)
         {
@@ -2447,16 +2418,14 @@ public class SIDH_Compressed
         }
     }
 
-
-
     // Traverse a Pohlig-Hellman optimal strategy to solve a discrete log in a group of order ell^e
     // Leaves are used to recover the digits which are numbers from 0 to ell^w-1 except by the last leaf that gives a digit between 0 and ell^(e mod w)
     // Assume w does not divide the exponent e
-    void Traverse_w_notdiv_e_fullsigned(ulong[][] r, uint j, uint k, uint z, uint[] P, ulong[] CT1, ulong[] CT2,
+    internal void Traverse_w_notdiv_e_fullsigned(ulong[][] r, uint j, uint k, uint z, uint[] P, ulong[] CT1, ulong[] CT2,
                                         int[] D, uint Dlen, uint ell, uint ellw, uint ell_emodw, uint w, uint e)
     {
-        ulong[][] rp = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            alpha = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] rp = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            alpha = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
 
         if (z > 1)
@@ -2626,15 +2595,13 @@ public class SIDH_Compressed
         }
     }
 
-
-
     // Traverse a Pohlig-Hellman optimal strategy to solve a discrete log in a group of order ell^e
     // The leaves of the tree will be used to recover the signed digits which are numbers from +/-{0,1... Ceil((ell^w-1)/2)}
     // Assume the integer w divides the exponent e
-    void Traverse_w_div_e_fullsigned(ulong[][] r, uint j, uint k, uint z, uint[] P, ulong[] CT, int[] D, uint Dlen, uint ellw, uint w)
+    internal void Traverse_w_div_e_fullsigned(ulong[][] r, uint j, uint k, uint z, uint[] P, ulong[] CT, int[] D, uint Dlen, uint ellw, uint w)
     {
-        ulong[][] rp = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            alpha = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] rp = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            alpha = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         if (z > 1)
         {
@@ -2729,18 +2696,18 @@ public class SIDH_Compressed
                 x23 = new ulong[engine.param.NWORDS_FIELD],
                 x2p3 = new ulong[engine.param.NWORDS_FIELD];
 
-        ulong[][][] xQ2s = Utils.InitArray(t_points, 2, engine.param.NWORDS_FIELD),
-            finv = Utils.InitArray(2*t_points, 2, engine.param.NWORDS_FIELD);
-        ulong[][] one = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t2 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t3 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t4 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t5 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            g = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            h = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            tf = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][][] xQ2s = SikeUtilities.InitArray(t_points, 2, engine.param.NWORDS_FIELD),
+            finv = SikeUtilities.InitArray(2*t_points, 2, engine.param.NWORDS_FIELD);
+        ulong[][] one = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t2 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t3 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t4 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t5 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            g = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            h = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            tf = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
 
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, one[0]);
@@ -2919,7 +2886,7 @@ public class SIDH_Compressed
     private void final_exponentiation_3_torsion(ulong[][] f, ulong[][] finv, ulong[][] fout)
     {
         ulong[] one = new ulong[engine.param.NWORDS_FIELD];
-        ulong[][] temp = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] temp = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         uint i;
 
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, one);
@@ -2938,16 +2905,16 @@ public class SIDH_Compressed
     private void Tate2_pairings(PointProj P, PointProj Q, PointProjFull[] Qj, ulong[][][] f)
     {
         ulong[] x, y, x_, y_, l1;
-        ulong[][][] finv = Utils.InitArray(2 * t_points, 2, engine.param.NWORDS_FIELD);
+        ulong[][][] finv = SikeUtilities.InitArray(2 * t_points, 2, engine.param.NWORDS_FIELD);
 
         ulong[][] x_first,
             y_first,
-            one = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            l1_first = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t0 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            t1 = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            g = Utils.InitArray(2, engine.param.NWORDS_FIELD),
-            h = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+            one = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            l1_first = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t0 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            t1 = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            g = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD),
+            h = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
 
         uint x_Offset, y_Offset, l1Offset, xOffset, yOffset;
 
@@ -3141,7 +3108,7 @@ public class SIDH_Compressed
     private void final_exponentiation_2_torsion(ulong[][] f, ulong[][] finv, ulong[][] fout)
     {
         ulong[] one = new ulong[engine.param.NWORDS_FIELD];
-        ulong[][] temp = Utils.InitArray(2, engine.param.NWORDS_FIELD);
+        ulong[][] temp = SikeUtilities.InitArray(2, engine.param.NWORDS_FIELD);
         uint i;
 
         engine.fpx.fpcopy(engine.param.Montgomery_one, 0, one);
@@ -3156,9 +3123,5 @@ public class SIDH_Compressed
         }
         engine.fpx.fp2copy(temp, fout);
     }
-
-
-
 }
-
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sike/SIKEEngine.cs b/crypto/src/pqc/crypto/sike/SIKEEngine.cs
index 58c04b459..12f468340 100644
--- a/crypto/src/pqc/crypto/sike/SIKEEngine.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEEngine.cs
@@ -4,44 +4,42 @@ using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-    
-public class SIKEEngine
-{
-    private SecureRandom random;
-    
-    protected internal Internal param;
-    protected internal Isogeny isogeny;
-    protected internal Fpx fpx;
-    private SIDH sidh;
-    private SIDH_Compressed sidhCompressed;
-    private bool isCompressed;
-
-    public uint GetDefaultSessionKeySize()
+    internal sealed class SikeEngine
     {
-        return param.MSG_BYTES * 8;
-    }
+        internal Internal param;
+        internal Isogeny isogeny;
+        internal Fpx fpx;
+        private Sidh sidh;
+        private SidhCompressed sidhCompressed;
+        private bool isCompressed;
+
+        internal uint GetDefaultSessionKeySize()
+        {
+            return param.MSG_BYTES * 8;
+        }
 
-    public uint GetCipherTextSize()
-    {
-        return param.CRYPTO_CIPHERTEXTBYTES;
-    }
+        internal int GetCipherTextSize()
+        {
+            return param.CRYPTO_CIPHERTEXTBYTES;
+        }
 
-    public uint GetPrivateKeySize()
-    {
-        return param.CRYPTO_SECRETKEYBYTES;
-    }
+        internal uint GetPrivateKeySize()
+        {
+            return param.CRYPTO_SECRETKEYBYTES;
+        }
 
-    public uint GetPublicKeySize()
-    {
-        return param.CRYPTO_PUBLICKEYBYTES;
-    }
-    public SIKEEngine(int ver, bool isCompressed, SecureRandom random)
-    {
-        this.random = random;
-        this.isCompressed = isCompressed;
-        //todo switch for different parameters
-        switch(ver)
+        internal uint GetPublicKeySize()
         {
+            return param.CRYPTO_PUBLICKEYBYTES;
+        }
+
+        internal SikeEngine(int ver, bool isCompressed, SecureRandom random)
+        {
+            //this.random = random;
+            this.isCompressed = isCompressed;
+            //todo switch for different parameters
+            switch(ver)
+            {
             case 434:
                 param = new P434(isCompressed);
                 break;
@@ -56,239 +54,236 @@ public class SIKEEngine
                 break;
             default:
                 break;
-                
+            }
+            fpx = new Fpx(this);
+            isogeny = new Isogeny(this);
+            if(isCompressed)
+            {
+                sidhCompressed = new SidhCompressed(this);
+            }
+            sidh = new Sidh(this);
         }
-        fpx = new Fpx(this);
-        isogeny = new Isogeny(this);
-        if(isCompressed)
+
+        // SIKE's key generation
+        // Outputs: secret key sk (CRYPTO_SECRETKEYBYTES = MSG_BYTES + SECRETKEY_B_BYTES + CRYPTO_PUBLICKEYBYTES bytes)
+        //          public key pk (CRYPTO_PUBLICKEYBYTES bytes)
+        internal int crypto_kem_keypair(byte[] pk, byte[] sk, SecureRandom random)
         {
-            sidhCompressed = new SIDH_Compressed(this);
-        }
-        sidh = new SIDH(this);
-    }
+            random.NextBytes(sk, 0, (int)param.MSG_BYTES);
 
-    // SIKE's key generation
-    // Outputs: secret key sk (CRYPTO_SECRETKEYBYTES = MSG_BYTES + SECRETKEY_B_BYTES + CRYPTO_PUBLICKEYBYTES bytes)
-    //          public key pk (CRYPTO_PUBLICKEYBYTES bytes)
-    public int crypto_kem_keypair(byte[] pk, byte[] sk, SecureRandom random)
-    {
-        random.NextBytes(sk, 0, (int)param.MSG_BYTES);
+            if (isCompressed)
+            {
+                // Generation of Alice's secret key
+                // Outputs random value in [0, 2^eA - 1]
 
-        if (isCompressed)
-        {
-            // Generation of Alice's secret key
-            // Outputs random value in [0, 2^eA - 1]
+                random.NextBytes(sk, (int)param.MSG_BYTES, (int)param.SECRETKEY_A_BYTES);
+                sk[param.MSG_BYTES] &= 0xFE;                                                    // Make private scalar even
+                sk[param.MSG_BYTES + param.SECRETKEY_A_BYTES - 1] &= (byte)param.MASK_ALICE;    // Masking last
 
-            random.NextBytes(sk, (int)param.MSG_BYTES, (int)param.SECRETKEY_A_BYTES);
-            sk[param.MSG_BYTES] &= 0xFE;                                                    // Make private scalar even
-            sk[param.MSG_BYTES + param.SECRETKEY_A_BYTES - 1] &= (byte)param.MASK_ALICE;    // Masking last
+                sidhCompressed.EphemeralKeyGeneration_A_extended(sk, pk);
 
-            sidhCompressed.EphemeralKeyGeneration_A_extended(sk, pk);
+                // Append public key pk to secret key sk
+                System.Array.Copy(pk, 0, sk, param.MSG_BYTES + param.SECRETKEY_A_BYTES, param.CRYPTO_PUBLICKEYBYTES);
+            }
+            else
+            {
+                // Generation of Bob's secret key
+                // Outputs random value in [0, 2^Floor(Log(2, oB)) - 1]
+                // todo/org: SIDH.random_mod_order_B(sk, random);
 
-            // Append public key pk to secret key sk
-            System.Array.Copy(pk, 0, sk, param.MSG_BYTES + param.SECRETKEY_A_BYTES, param.CRYPTO_PUBLICKEYBYTES);
-        }
-        else
-        {
-            // Generation of Bob's secret key
-            // Outputs random value in [0, 2^Floor(Log(2, oB)) - 1]
-            // todo/org: SIDH.random_mod_order_B(sk, random);
+                random.NextBytes(sk, (int)param.MSG_BYTES, (int)param.SECRETKEY_B_BYTES);
+                sk[param.MSG_BYTES + param.SECRETKEY_B_BYTES - 1] &= (byte)param.MASK_BOB;
 
-            random.NextBytes(sk, (int)param.MSG_BYTES, (int)param.SECRETKEY_B_BYTES);
-            sk[param.MSG_BYTES + param.SECRETKEY_B_BYTES - 1] &= (byte)param.MASK_BOB;
+                sidh.EphemeralKeyGeneration_B(sk, pk);
 
-            sidh.EphemeralKeyGeneration_B(sk, pk);
+                // Append public key pk to secret key sk
+                System.Array.Copy(pk, 0, sk, param.MSG_BYTES + param.SECRETKEY_B_BYTES, param.CRYPTO_PUBLICKEYBYTES);
 
-            // Append public key pk to secret key sk
-            System.Array.Copy(pk, 0, sk, param.MSG_BYTES + param.SECRETKEY_B_BYTES, param.CRYPTO_PUBLICKEYBYTES);
+            }
 
+            return 0;
         }
 
-        return 0;
-    }
-
-    // SIKE's encapsulation
-    // Input:   public key pk         (CRYPTO_PUBLICKEYBYTES bytes)
-    // Outputs: shared secret ss      (CRYPTO_BYTES bytes)
-    //          ciphertext message ct (CRYPTO_CIPHERTEXTBYTES = CRYPTO_PUBLICKEYBYTES + MSG_BYTES bytes)
-    public int crypto_kem_enc(byte[] ct, byte[] ss, byte[] pk, SecureRandom random)
-    {
-        if(isCompressed)
+        // SIKE's encapsulation
+        // Input:   public key pk         (CRYPTO_PUBLICKEYBYTES bytes)
+        // Outputs: shared secret ss      (CRYPTO_BYTES bytes)
+        //          ciphertext message ct (CRYPTO_CIPHERTEXTBYTES = CRYPTO_PUBLICKEYBYTES + MSG_BYTES bytes)
+        internal int crypto_kem_enc(byte[] ct, byte[] ss, byte[] pk, SecureRandom random)
         {
-            byte[] ephemeralsk = new byte[param.SECRETKEY_B_BYTES];
-            byte[] jinvariant = new byte[param.FP2_ENCODED_BYTES];
-            byte[] h = new byte[param.MSG_BYTES];
-            byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
+            if(isCompressed)
+            {
+                byte[] ephemeralsk = new byte[param.SECRETKEY_B_BYTES];
+                byte[] jinvariant = new byte[param.FP2_ENCODED_BYTES];
+                byte[] h = new byte[param.MSG_BYTES];
+                byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
 
-            // Generate ephemeralsk <- G(m||pk) mod oB
-            random.NextBytes(temp, 0, (int)param.MSG_BYTES);
-            System.Array.Copy(pk, 0, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
+                // Generate ephemeralsk <- G(m||pk) mod oB
+                random.NextBytes(temp, 0, (int)param.MSG_BYTES);
+                System.Array.Copy(pk, 0, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
 
-            IXof digest = new ShakeDigest(256);
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
-            digest.DoFinal(ephemeralsk, 0, (int) param.SECRETKEY_B_BYTES);
+                IXof digest = new ShakeDigest(256);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ephemeralsk, 0, (int) param.SECRETKEY_B_BYTES);
 
-            sidhCompressed.FormatPrivKey_B(ephemeralsk);
+                sidhCompressed.FormatPrivKey_B(ephemeralsk);
 
-//            System.out.println("ephemeralsk: " + Hex.toHexstring(ephemeralsk));
+    //            System.out.println("ephemeralsk: " + Hex.toHexstring(ephemeralsk));
 
 
-            // Encrypt
-            sidhCompressed.EphemeralKeyGeneration_B_extended(ephemeralsk, ct, 1);
+                // Encrypt
+                sidhCompressed.EphemeralKeyGeneration_B_extended(ephemeralsk, ct, 1);
 
-//            System.out.println("ct: " + Hex.toHexstring(ct));
-//            System.out.println("pk: " + Hex.toHexstring(pk));
+    //            System.out.println("ct: " + Hex.toHexstring(ct));
+    //            System.out.println("pk: " + Hex.toHexstring(pk));
 
-            sidhCompressed.EphemeralSecretAgreement_B(ephemeralsk, pk, jinvariant);
+                sidhCompressed.EphemeralSecretAgreement_B(ephemeralsk, pk, jinvariant);
 
-//            System.out.println("jinv: " + Hex.toHexstring(jinvariant));
+    //            System.out.println("jinv: " + Hex.toHexstring(jinvariant));
 
-            digest.BlockUpdate(jinvariant, 0, (int) param.FP2_ENCODED_BYTES);
-            digest.DoFinal(h, 0, (int) param.MSG_BYTES);
+                digest.BlockUpdate(jinvariant, 0, (int) param.FP2_ENCODED_BYTES);
+                digest.OutputFinal(h, 0, (int) param.MSG_BYTES);
 
-//            System.out.println("h: " + Hex.toHexstring(h));
-//            System.out.println("temp: " + Hex.toHexstring(temp));
+    //            System.out.println("h: " + Hex.toHexstring(h));
+    //            System.out.println("temp: " + Hex.toHexstring(temp));
 
-            for (int i = 0; i < param.MSG_BYTES; i++)
-            {
-                ct[i + param.PARTIALLY_COMPRESSED_CHUNK_CT] = (byte) (temp[i] ^ h[i]);
-            }
+                for (int i = 0; i < param.MSG_BYTES; i++)
+                {
+                    ct[i + param.PARTIALLY_COMPRESSED_CHUNK_CT] = (byte) (temp[i] ^ h[i]);
+                }
 
-            // Generate shared secret ss <- H(m||ct)
-            System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
+                // Generate shared secret ss <- H(m||ct)
+                System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
 
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
-            digest.DoFinal(ss, 0, (int) param.CRYPTO_BYTES);
-            return 0;
-        }
-        else
-        {
-            byte[] ephemeralsk = new byte[param.SECRETKEY_A_BYTES];
-            byte[] jinvariant = new byte[param.FP2_ENCODED_BYTES];
-            byte[] h = new byte[param.MSG_BYTES];
-            byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ss, 0, (int) param.CRYPTO_BYTES);
+                return 0;
+            }
+            else
+            {
+                byte[] ephemeralsk = new byte[param.SECRETKEY_A_BYTES];
+                byte[] jinvariant = new byte[param.FP2_ENCODED_BYTES];
+                byte[] h = new byte[param.MSG_BYTES];
+                byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
 
-            // Generate ephemeralsk <- G(m||pk) mod oA
-            random.NextBytes(temp, 0, (int)param.MSG_BYTES);
-            System.Array.Copy(pk, 0, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
+                // Generate ephemeralsk <- G(m||pk) mod oA
+                random.NextBytes(temp, 0, (int)param.MSG_BYTES);
+                System.Array.Copy(pk, 0, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
 
-            IXof digest = new ShakeDigest(256);
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
-            digest.DoFinal(ephemeralsk, 0, (int) param.SECRETKEY_A_BYTES);
-            ephemeralsk[param.SECRETKEY_A_BYTES - 1] &= (byte) param.MASK_ALICE;
+                IXof digest = new ShakeDigest(256);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ephemeralsk, 0, (int) param.SECRETKEY_A_BYTES);
+                ephemeralsk[param.SECRETKEY_A_BYTES - 1] &= (byte) param.MASK_ALICE;
 
-            // Encrypt
-            sidh.EphemeralKeyGeneration_A(ephemeralsk, ct);
-            sidh.EphemeralSecretAgreement_A(ephemeralsk, pk, jinvariant);
+                // Encrypt
+                sidh.EphemeralKeyGeneration_A(ephemeralsk, ct);
+                sidh.EphemeralSecretAgreement_A(ephemeralsk, pk, jinvariant);
 
-            digest.BlockUpdate(jinvariant, 0, (int) param.FP2_ENCODED_BYTES);
-            digest.DoFinal(h, 0, (int) param.MSG_BYTES);
+                digest.BlockUpdate(jinvariant, 0, (int) param.FP2_ENCODED_BYTES);
+                digest.OutputFinal(h, 0, (int) param.MSG_BYTES);
 
-            for (int i = 0; i < param.MSG_BYTES; i++)
-            {
-                ct[i + param.CRYPTO_PUBLICKEYBYTES] = (byte) (temp[i] ^ h[i]);
-            }
+                for (int i = 0; i < param.MSG_BYTES; i++)
+                {
+                    ct[i + param.CRYPTO_PUBLICKEYBYTES] = (byte) (temp[i] ^ h[i]);
+                }
 
-            // Generate shared secret ss <- H(m||ct)
-            System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
+                // Generate shared secret ss <- H(m||ct)
+                System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
 
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
-            digest.DoFinal(ss, 0, (int) param.CRYPTO_BYTES);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ss, 0, (int) param.CRYPTO_BYTES);
 
-            return 0;
+                return 0;
+            }
         }
-    }
 
-    // SIKE's decapsulation
-    // Input:   secret key sk         (CRYPTO_SECRETKEYBYTES = MSG_BYTES + SECRETKEY_B_BYTES + CRYPTO_PUBLICKEYBYTES bytes)
-    //          ciphertext message ct (CRYPTO_CIPHERTEXTBYTES = CRYPTO_PUBLICKEYBYTES + MSG_BYTES bytes)
-    // Outputs: shared secret ss      (CRYPTO_BYTES bytes)
-    public int crypto_kem_dec(byte[] ss, byte[] ct, byte[] sk)
-    {
-        if (isCompressed)
+        // SIKE's decapsulation
+        // Input:   secret key sk         (CRYPTO_SECRETKEYBYTES = MSG_BYTES + SECRETKEY_B_BYTES + CRYPTO_PUBLICKEYBYTES bytes)
+        //          ciphertext message ct (CRYPTO_CIPHERTEXTBYTES = CRYPTO_PUBLICKEYBYTES + MSG_BYTES bytes)
+        // Outputs: shared secret ss      (CRYPTO_BYTES bytes)
+        internal int crypto_kem_dec(byte[] ss, byte[] ct, byte[] sk)
         {
-            byte[] ephemeralsk_ = new byte[param.SECRETKEY_B_BYTES];
-            byte[] jinvariant_ = new byte[param.FP2_ENCODED_BYTES + 2*param.FP2_ENCODED_BYTES + param.SECRETKEY_A_BYTES],
-                   h_ = new byte[param.MSG_BYTES];
-            byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
-            byte[] tphiBKA_t = jinvariant_;//jinvariant_[param.FP2_ENCODED_BYTES];
+            if (isCompressed)
+            {
+                byte[] ephemeralsk_ = new byte[param.SECRETKEY_B_BYTES];
+                byte[] jinvariant_ = new byte[param.FP2_ENCODED_BYTES + 2*param.FP2_ENCODED_BYTES + param.SECRETKEY_A_BYTES],
+                       h_ = new byte[param.MSG_BYTES];
+                byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
+                byte[] tphiBKA_t = jinvariant_;//jinvariant_[param.FP2_ENCODED_BYTES];
 
-            // Decrypt
-            sidhCompressed.EphemeralSecretAgreement_A_extended(sk, param.MSG_BYTES, ct, jinvariant_, 1);
+                // Decrypt
+                sidhCompressed.EphemeralSecretAgreement_A_extended(sk, param.MSG_BYTES, ct, jinvariant_, 1);
 
-            IXof digest = new ShakeDigest(256);
-            digest.BlockUpdate(jinvariant_, 0, (int) param.FP2_ENCODED_BYTES);
-            digest.DoFinal(h_, 0, (int) param.MSG_BYTES);
+                IXof digest = new ShakeDigest(256);
+                digest.BlockUpdate(jinvariant_, 0, (int) param.FP2_ENCODED_BYTES);
+                digest.OutputFinal(h_, 0, (int) param.MSG_BYTES);
 
-//            System.out.println("h_: " + Hex.toHexstring(h_));
+    //            System.out.println("h_: " + Hex.toHexstring(h_));
 
-            for (int i = 0; i < param.MSG_BYTES; i++)
-            {
-                temp[i] = (byte) (ct[i + param.PARTIALLY_COMPRESSED_CHUNK_CT] ^ h_[i]);
-            }
+                for (int i = 0; i < param.MSG_BYTES; i++)
+                {
+                    temp[i] = (byte) (ct[i + param.PARTIALLY_COMPRESSED_CHUNK_CT] ^ h_[i]);
+                }
 
-            // Generate ephemeralsk_ <- G(m||pk) mod oB
-            System.Array.Copy(sk, param.MSG_BYTES + param.SECRETKEY_A_BYTES, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
+                // Generate ephemeralsk_ <- G(m||pk) mod oB
+                System.Array.Copy(sk, param.MSG_BYTES + param.SECRETKEY_A_BYTES, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
 
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
-            digest.DoFinal(ephemeralsk_, 0, (int) param.SECRETKEY_B_BYTES);
-            sidhCompressed.FormatPrivKey_B(ephemeralsk_);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ephemeralsk_, 0, (int) param.SECRETKEY_B_BYTES);
+                sidhCompressed.FormatPrivKey_B(ephemeralsk_);
 
-            // Generate shared secret ss <- H(m||ct), or output ss <- H(s||ct) in case of ct verification failure
-            // No need to recompress, just check if x(phi(P) + t*phi(Q)) == x((a0 + t*a1)*R1 + (b0 + t*b1)*R2)
-            byte selector = sidhCompressed.validate_ciphertext(ephemeralsk_, ct, sk, param.MSG_BYTES + param.SECRETKEY_A_BYTES + param.CRYPTO_PUBLICKEYBYTES, tphiBKA_t, param.FP2_ENCODED_BYTES);
-            // If ct validation passes (selector = 0) then do ss = H(m||ct), otherwise (selector = -1) load s to do ss = H(s||ct)
-            fpx.ct_cmov(temp, sk, param.MSG_BYTES, selector);
+                // Generate shared secret ss <- H(m||ct), or output ss <- H(s||ct) in case of ct verification failure
+                // No need to recompress, just check if x(phi(P) + t*phi(Q)) == x((a0 + t*a1)*R1 + (b0 + t*b1)*R2)
+                byte selector = sidhCompressed.validate_ciphertext(ephemeralsk_, ct, sk, param.MSG_BYTES + param.SECRETKEY_A_BYTES + param.CRYPTO_PUBLICKEYBYTES, tphiBKA_t, param.FP2_ENCODED_BYTES);
+                // If ct validation passes (selector = 0) then do ss = H(m||ct), otherwise (selector = -1) load s to do ss = H(s||ct)
+                fpx.ct_cmov(temp, sk, param.MSG_BYTES, selector);
 
-            System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
-            digest.DoFinal(ss, 0, (int) param.CRYPTO_BYTES);
+                System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ss, 0, (int) param.CRYPTO_BYTES);
 
-            return 0;
-        }
-        else
-        {
-            byte[] ephemeralsk_ = new byte[param.SECRETKEY_A_BYTES];
-            byte[] jinvariant_ = new byte[param.FP2_ENCODED_BYTES];
-            byte[] h_ = new byte[param.MSG_BYTES];
-            byte[] c0_ = new byte[param.CRYPTO_PUBLICKEYBYTES];
-            byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
-
-            // Decrypt
-            // int EphemeralSecretAgreement_B(PrivateKeyB, PublicKeyA, SharedSecretB)
-            sidh.EphemeralSecretAgreement_B(sk, ct, jinvariant_);
-
-            IXof digest = new ShakeDigest(256);
-            digest.BlockUpdate(jinvariant_, 0, (int) param.FP2_ENCODED_BYTES);
-            digest.DoFinal(h_, 0, (int) param.MSG_BYTES);
-            for (int i = 0; i < param.MSG_BYTES; i++)
-            {
-                temp[i] = (byte) (ct[i + param.CRYPTO_PUBLICKEYBYTES] ^ h_[i]);
+                return 0;
             }
+            else
+            {
+                byte[] ephemeralsk_ = new byte[param.SECRETKEY_A_BYTES];
+                byte[] jinvariant_ = new byte[param.FP2_ENCODED_BYTES];
+                byte[] h_ = new byte[param.MSG_BYTES];
+                byte[] c0_ = new byte[param.CRYPTO_PUBLICKEYBYTES];
+                byte[] temp = new byte[param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES];
 
-            // Generate ephemeralsk_ <- G(m||pk) mod oA
-            System.Array.Copy(sk, param.MSG_BYTES + param.SECRETKEY_B_BYTES, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
+                // Decrypt
+                // int EphemeralSecretAgreement_B(PrivateKeyB, PublicKeyA, SharedSecretB)
+                sidh.EphemeralSecretAgreement_B(sk, ct, jinvariant_);
 
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
-            digest.DoFinal(ephemeralsk_, 0, (int) param.SECRETKEY_A_BYTES);
-            ephemeralsk_[param.SECRETKEY_A_BYTES - 1] &= (byte) param.MASK_ALICE;
+                IXof digest = new ShakeDigest(256);
+                digest.BlockUpdate(jinvariant_, 0, (int) param.FP2_ENCODED_BYTES);
+                digest.OutputFinal(h_, 0, (int) param.MSG_BYTES);
+                for (int i = 0; i < param.MSG_BYTES; i++)
+                {
+                    temp[i] = (byte) (ct[i + param.CRYPTO_PUBLICKEYBYTES] ^ h_[i]);
+                }
 
+                // Generate ephemeralsk_ <- G(m||pk) mod oA
+                System.Array.Copy(sk, param.MSG_BYTES + param.SECRETKEY_B_BYTES, temp, param.MSG_BYTES, param.CRYPTO_PUBLICKEYBYTES);
 
-            // Generate shared secret ss <- H(m||ct), or output ss <- H(s||ct) in case of ct verification failure
-            sidh.EphemeralKeyGeneration_A(ephemeralsk_, c0_);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_PUBLICKEYBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ephemeralsk_, 0, (int) param.SECRETKEY_A_BYTES);
+                ephemeralsk_[param.SECRETKEY_A_BYTES - 1] &= (byte) param.MASK_ALICE;
 
-            // If selector = 0 then do ss = H(m||ct), else if selector = -1 load s to do ss = H(s||ct)
-            byte selector = fpx.ct_compare(c0_, ct, param.CRYPTO_PUBLICKEYBYTES);
-            fpx.ct_cmov(temp, sk, param.MSG_BYTES, selector);
 
-            System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
-            digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
-            digest.DoFinal(ss, 0, (int) param.CRYPTO_BYTES);
+                // Generate shared secret ss <- H(m||ct), or output ss <- H(s||ct) in case of ct verification failure
+                sidh.EphemeralKeyGeneration_A(ephemeralsk_, c0_);
 
-            return 0;
+                // If selector = 0 then do ss = H(m||ct), else if selector = -1 load s to do ss = H(s||ct)
+                byte selector = fpx.ct_compare(c0_, ct, param.CRYPTO_PUBLICKEYBYTES);
+                fpx.ct_cmov(temp, sk, param.MSG_BYTES, selector);
+
+                System.Array.Copy(ct, 0, temp, param.MSG_BYTES, param.CRYPTO_CIPHERTEXTBYTES);
+                digest.BlockUpdate(temp, 0, (int) (param.CRYPTO_CIPHERTEXTBYTES + param.MSG_BYTES));
+                digest.OutputFinal(ss, 0, (int) param.CRYPTO_BYTES);
+
+                return 0;
+            }
         }
     }
-
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/SIKEKEMExtractor.cs b/crypto/src/pqc/crypto/sike/SIKEKEMExtractor.cs
index 1d2bab871..879f1d8ef 100644
--- a/crypto/src/pqc/crypto/sike/SIKEKEMExtractor.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEKEMExtractor.cs
@@ -1,44 +1,43 @@
+using System;
+
 using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-    public class SIKEKEMExtractor
+    [Obsolete("Will be removed")]
+    public sealed class SikeKemExtractor
         : IEncapsulatedSecretExtractor
     {
-    private SIKEEngine engine;
-
-    private SIKEKeyParameters key;
-
-    public SIKEKEMExtractor(SIKEPrivateKeyParameters privParams)
-    {
-        this.key = privParams;
-        InitCipher(key.GetParameters());
+        private readonly SikeKeyParameters key;
+
+        private SikeEngine engine;
+
+        public SikeKemExtractor(SikePrivateKeyParameters privParams)
+        {
+            this.key = privParams;
+            InitCipher(key.Parameters);
+        }
+
+        private void InitCipher(SikeParameters param)
+        {
+            engine = param.Engine;
+            SikePrivateKeyParameters privateParams = (SikePrivateKeyParameters)key;
+            //todo: add compression check
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            return ExtractSecret(encapsulation, (int)engine.GetDefaultSessionKeySize());
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation, int sessionKeySizeInBits)
+        {
+            Console.Error.WriteLine("WARNING: the SIKE algorithm is only for research purposes, insecure");
+            byte[] session_key = new byte[sessionKeySizeInBits / 8];
+            engine.crypto_kem_dec(session_key, encapsulation, ((SikePrivateKeyParameters)key).GetPrivateKey());
+            return session_key;
+        }
+
+        public int EncapsulationLength => (int)engine.GetCipherTextSize();
     }
-
-    private void InitCipher(SIKEParameters param)
-    {
-        engine = param.GetEngine();
-        SIKEPrivateKeyParameters privateParams = (SIKEPrivateKeyParameters)key;
-        //todo: add compression check
-    }
-
-    public byte[] ExtractSecret(byte[] encapsulation)
-    {
-        return ExtractSecret(encapsulation, engine.GetDefaultSessionKeySize());
-    }
-
-    public byte[] ExtractSecret(byte[] encapsulation, uint sessionKeySizeInBits)
-    {
-        byte[] session_key = new byte[sessionKeySizeInBits / 8];
-        engine.crypto_kem_dec(session_key, encapsulation, ((SIKEPrivateKeyParameters)key).GetPrivateKey());
-        return session_key;
-    }
-
-//    public int GetInputSize()
-//    {
-//        return engine.GetCipherTextSize();
-//    }
-
-    }
-
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sike/SIKEKEMGenerator.cs b/crypto/src/pqc/crypto/sike/SIKEKEMGenerator.cs
index 83c08ef56..5e4bd41eb 100644
--- a/crypto/src/pqc/crypto/sike/SIKEKEMGenerator.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEKEMGenerator.cs
@@ -1,38 +1,41 @@
+using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Pqc.Crypto.Utilities;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIKEKEMGenerator
-    : IEncapsulatedSecretGenerator
-{
-    // the source of randomness
-    private SecureRandom sr;
-
-
-    public SIKEKEMGenerator(SecureRandom random)
+    [Obsolete("Will be removed")]
+    public sealed class SikeKemGenerator
+        : IEncapsulatedSecretGenerator
     {
-        this.sr = random;
-    }
+        // the source of randomness
+        private readonly SecureRandom sr;
 
-    public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
-    {
-        SIKEPublicKeyParameters key = (SIKEPublicKeyParameters)recipientKey;
-        SIKEEngine engine = key.GetParameters().GetEngine();
+        public SikeKemGenerator(SecureRandom random)
+        {
+            this.sr = CryptoServicesRegistrar.GetSecureRandom(random);
+        }
 
-        return GenerateEncapsulated(recipientKey, engine.GetDefaultSessionKeySize());
-    }
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            SikePublicKeyParameters key = (SikePublicKeyParameters)recipientKey;
+            SikeEngine engine = key.Parameters.Engine;
 
-    public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey, uint sessionKeySizeInBits)
-    {
-        SIKEPublicKeyParameters key = (SIKEPublicKeyParameters)recipientKey;
-        SIKEEngine engine = key.GetParameters().GetEngine();
-        byte[] cipher_text = new byte[engine.GetCipherTextSize()];
-        byte[] sessionKey = new byte[sessionKeySizeInBits / 8];
-        engine.crypto_kem_enc(cipher_text, sessionKey, key.GetPublicKey(), sr);
-        return new SecretWithEncapsulationImpl(sessionKey, cipher_text);
+            return GenerateEncapsulated(recipientKey, (int)engine.GetDefaultSessionKeySize());
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey,
+            int sessionKeySizeInBits)
+        {
+            Console.Error.WriteLine("WARNING: the SIKE algorithm is only for research purposes, insecure");
+            SikePublicKeyParameters key = (SikePublicKeyParameters)recipientKey;
+            SikeEngine engine = key.Parameters.Engine;
+            byte[] cipher_text = new byte[engine.GetCipherTextSize()];
+            byte[] sessionKey = new byte[sessionKeySizeInBits / 8];
+            engine.crypto_kem_enc(cipher_text, sessionKey, key.GetPublicKey(), sr);
+            return new SecretWithEncapsulationImpl(sessionKey, cipher_text);
+        }
     }
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/SIKEKeyGenerationParameters.cs b/crypto/src/pqc/crypto/sike/SIKEKeyGenerationParameters.cs
index 669a417b2..f595032eb 100644
--- a/crypto/src/pqc/crypto/sike/SIKEKeyGenerationParameters.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEKeyGenerationParameters.cs
@@ -1,25 +1,22 @@
+using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIKEKeyGenerationParameters
-    : KeyGenerationParameters
-{
-    private SIKEParameters param;
-
-    public SIKEKeyGenerationParameters(
-            SecureRandom random,
-            SIKEParameters sikeParameters
-    )
-    	: base(random, 256)
-    {
-        this.param = sikeParameters;
-    }
-    public SIKEParameters GetParameters()
+    [Obsolete("Will be removed")]
+    public sealed class SikeKeyGenerationParameters
+        : KeyGenerationParameters
     {
-        return param;
+        private readonly SikeParameters m_parameters;
+
+        public SikeKeyGenerationParameters(SecureRandom random, SikeParameters sikeParameters)
+            : base(random, 256)
+        {
+            m_parameters = sikeParameters;
+        }
+
+        public SikeParameters Parameters => m_parameters;
     }
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/SIKEKeyPairGenerator.cs b/crypto/src/pqc/crypto/sike/SIKEKeyPairGenerator.cs
index 3945fe8c8..20def8a32 100644
--- a/crypto/src/pqc/crypto/sike/SIKEKeyPairGenerator.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEKeyPairGenerator.cs
@@ -1,32 +1,34 @@
+using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-    public class SIKEKeyPairGenerator
+    [Obsolete("Will be removed")]
+    public sealed class SikeKeyPairGenerator
         : IAsymmetricCipherKeyPairGenerator
     {
-        private SIKEKeyGenerationParameters sikeParams;
+        private SikeKeyGenerationParameters sikeParams;
 
         private SecureRandom random;
 
         private void Initialize(KeyGenerationParameters param)
         {
-            this.sikeParams = (SIKEKeyGenerationParameters) param;
+            this.sikeParams = (SikeKeyGenerationParameters) param;
             this.random = param.Random;
         }
 
         private AsymmetricCipherKeyPair GenKeyPair()
         {
-            SIKEEngine engine = sikeParams.GetParameters().GetEngine();
+            SikeEngine engine = sikeParams.Parameters.Engine;
             byte[] sk = new byte[engine.GetPrivateKeySize()];
             byte[] pk = new byte[engine.GetPublicKeySize()];
 
             engine.crypto_kem_keypair(pk, sk, random);
 
-
-            SIKEPublicKeyParameters pubKey = new SIKEPublicKeyParameters(sikeParams.GetParameters(), pk);
-            SIKEPrivateKeyParameters privKey = new SIKEPrivateKeyParameters(sikeParams.GetParameters(), sk);
+            SikePublicKeyParameters pubKey = new SikePublicKeyParameters(sikeParams.Parameters, pk);
+            SikePrivateKeyParameters privKey = new SikePrivateKeyParameters(sikeParams.Parameters, sk);
             return new AsymmetricCipherKeyPair(pubKey, privKey);
         }
 
@@ -40,5 +42,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             return GenKeyPair();
         }
     }
-
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sike/SIKEKeyParameters.cs b/crypto/src/pqc/crypto/sike/SIKEKeyParameters.cs
index 29ef6114d..759c8dd5d 100644
--- a/crypto/src/pqc/crypto/sike/SIKEKeyParameters.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEKeyParameters.cs
@@ -1,25 +1,21 @@
+using System;
+
 using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIKEKeyParameters
-    : AsymmetricKeyParameter
-{
-    private SIKEParameters param;
-
-    public SIKEKeyParameters(
-            bool isPrivate,
-            SIKEParameters param
-    )
-    	:base(isPrivate)
+    [Obsolete("Will be removed")]
+    public abstract class SikeKeyParameters
+        : AsymmetricKeyParameter
     {
-        this.param = param;
-    }
+        private readonly SikeParameters m_parameters;
 
-    public SIKEParameters GetParameters()
-    {
-        return param;
+        public SikeKeyParameters(bool isPrivate, SikeParameters param)
+            : base(isPrivate)
+        {
+            this.m_parameters = param;
+        }
+
+        public SikeParameters Parameters => m_parameters;
     }
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/SIKEParameters.cs b/crypto/src/pqc/crypto/sike/SIKEParameters.cs
index 5c1b162f8..d18797067 100644
--- a/crypto/src/pqc/crypto/sike/SIKEParameters.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEParameters.cs
@@ -1,32 +1,33 @@
+using System;
+
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIKEParameters
-{
+    [Obsolete("Will be removed")]
+    public sealed class SikeParameters
+    {
+        public static readonly SikeParameters sikep434 = new SikeParameters(434, false, "sikep434");
+        public static readonly SikeParameters sikep503 = new SikeParameters(503, false, "sikep503");
+        public static readonly SikeParameters sikep610 = new SikeParameters(610, false, "sikep610");
+        public static readonly SikeParameters sikep751 = new SikeParameters(751, false, "sikep751");
 
-    public static SIKEParameters sikep434 = new SIKEParameters(434, false,"sikep434");
-    public static SIKEParameters sikep503 = new SIKEParameters(503, false,"sikep503");
-    public static SIKEParameters sikep610 = new SIKEParameters(610, false,"sikep610");
-    public static SIKEParameters sikep751 = new SIKEParameters(751, false,"sikep751");
+        public static readonly SikeParameters sikep434_compressed = new SikeParameters(434, true, "sikep434_compressed");
+        public static readonly SikeParameters sikep503_compressed = new SikeParameters(503, true, "sikep503_compressed");
+        public static readonly SikeParameters sikep610_compressed = new SikeParameters(610, true, "sikep610_compressed");
+        public static readonly SikeParameters sikep751_compressed = new SikeParameters(751, true, "sikep751_compressed");
 
-    public static SIKEParameters sikep434_compressed = new SIKEParameters(434, true,"sikep434_compressed");
-    public static SIKEParameters sikep503_compressed = new SIKEParameters(503, true,"sikep503_compressed");
-    public static SIKEParameters sikep610_compressed = new SIKEParameters(610, true,"sikep610_compressed");
-    public static SIKEParameters sikep751_compressed = new SIKEParameters(751, true,"sikep751_compressed");
+        private readonly string name;
+        private readonly SikeEngine engine;
 
-    private string name;
-    private SIKEEngine engine;
-    public SIKEParameters(int ver, bool isCompressed, string name)
-    {
-        this.name = name;
-        this.engine = new SIKEEngine(ver, isCompressed, null);
-    }
+        public SikeParameters(int ver, bool isCompressed, string name)
+        {
+            this.name = name;
+            this.engine = new SikeEngine(ver, isCompressed, null);
+        }
 
-    public SIKEEngine GetEngine()
-    {
-        return engine;
-    }
+        internal SikeEngine Engine => engine;
 
+        public string Name => name;
 
+        public int DefaultKeySize => (int)this.engine.GetDefaultSessionKeySize();
+    }
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/SIKEPrivateKeyParameters.cs b/crypto/src/pqc/crypto/sike/SIKEPrivateKeyParameters.cs
index e3e791e71..ee7116b68 100644
--- a/crypto/src/pqc/crypto/sike/SIKEPrivateKeyParameters.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEPrivateKeyParameters.cs
@@ -1,27 +1,29 @@
+using System;
+
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIKEPrivateKeyParameters
-    : SIKEKeyParameters
-{
-    private byte[] privateKey;
-
-    public byte[] GetPrivateKey()
+    [Obsolete("Will be removed")]
+    public sealed class SikePrivateKeyParameters
+        : SikeKeyParameters
     {
-        return Arrays.Clone(privateKey);
-    }
+        private readonly byte[] privateKey;
 
-    public SIKEPrivateKeyParameters(SIKEParameters param, byte[] privateKey)
-    	:base(true, param)
-    {
-        this.privateKey = Arrays.Clone(privateKey);
-    }
+        public SikePrivateKeyParameters(SikeParameters param, byte[] privateKey)
+            : base(true, param)
+        {
+            this.privateKey = Arrays.Clone(privateKey);
+        }
 
-    public byte[] GetEncoded()
-    {
-        return Arrays.Clone(privateKey);
+        public byte[] GetEncoded()
+        {
+            return Arrays.Clone(privateKey);
+        }
+
+        public byte[] GetPrivateKey()
+        {
+            return Arrays.Clone(privateKey);
+        }
     }
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/SIKEPublicKeyParameters.cs b/crypto/src/pqc/crypto/sike/SIKEPublicKeyParameters.cs
index a777d7896..3300ed438 100644
--- a/crypto/src/pqc/crypto/sike/SIKEPublicKeyParameters.cs
+++ b/crypto/src/pqc/crypto/sike/SIKEPublicKeyParameters.cs
@@ -1,27 +1,29 @@
+using System;
+
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-public class SIKEPublicKeyParameters
-    : SIKEKeyParameters
-{
-    public byte[] publicKey;
-
-    public byte[] GetPublicKey()
+    [Obsolete("Will be removed")]
+    public sealed class SikePublicKeyParameters
+        : SikeKeyParameters
     {
-        return Arrays.Clone(publicKey);
-    }
+        public readonly byte[] publicKey;
 
-    public byte[] GetEncoded()
-    {
-        return GetPublicKey();
-    }
+        public SikePublicKeyParameters(SikeParameters param, byte[] publicKey)
+            : base(false, param)
+        {
+            this.publicKey = Arrays.Clone(publicKey);
+        }
 
-    public SIKEPublicKeyParameters(SIKEParameters param, byte[] publicKey)
-    	: base(false, param)
-    {
-        this.publicKey = Arrays.Clone(publicKey);
+        public byte[] GetEncoded()
+        {
+            return Arrays.Clone(publicKey);
+        }
+
+        public byte[] GetPublicKey()
+        {
+            return Arrays.Clone(publicKey);
+        }
     }
 }
-
-}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/sike/Utils.cs b/crypto/src/pqc/crypto/sike/SikeUtilities.cs
index 33f1f8696..ca5ee67a0 100644
--- a/crypto/src/pqc/crypto/sike/Utils.cs
+++ b/crypto/src/pqc/crypto/sike/SikeUtilities.cs
@@ -1,8 +1,8 @@
 namespace Org.BouncyCastle.Pqc.Crypto.Sike
 {
-    public class Utils
+    internal static class SikeUtilities
     {
-        public static ulong[][] InitArray(uint size1, uint size2)
+        internal static ulong[][] InitArray(uint size1, uint size2)
         {
             ulong[][] res = new ulong[size1][];
             for (int i = 0; i < size1; i++)
@@ -12,8 +12,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
             return res;
         }
-        
-        public static ulong[][][] InitArray(uint size1, uint size2, uint size3)
+
+        internal static ulong[][][] InitArray(uint size1, uint size2, uint size3)
         {
             ulong[][][] res = new ulong[size1][][];
             for (int i = 0; i < size1; i++)
@@ -27,8 +27,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
 
             return res;
         }
-        
-        public static ulong[][][][] InitArray(uint size1, uint size2, uint size3, uint size4)
+
+        internal static ulong[][][][] InitArray(uint size1, uint size2, uint size3, uint size4)
         {
             ulong[][][][] res = new ulong[size1][][][];
             for (int i = 0; i < size1; i++)
@@ -47,4 +47,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.Sike
             return res;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/Adrs.cs b/crypto/src/pqc/crypto/sphincsplus/Adrs.cs
index a6ec753fd..39b0fa16d 100644
--- a/crypto/src/pqc/crypto/sphincsplus/Adrs.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/Adrs.cs
@@ -1,31 +1,30 @@
-
 using System;
+
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-
     internal class Adrs
     {
-        public static uint WOTS_HASH = 0;
-        public static uint WOTS_PK = 1;
-        public static uint TREE = 2;
-        public static uint FORS_TREE = 3;
-        public static uint FORS_PK = 4;
-        public static uint WOTS_PRF = 5;
-        public static uint FORS_PRF = 6;
+        internal static uint WOTS_HASH = 0;
+        internal static uint WOTS_PK = 1;
+        internal static uint TREE = 2;
+        internal static uint FORS_TREE = 3;
+        internal static uint FORS_PK = 4;
+        internal static uint WOTS_PRF = 5;
+        internal static uint FORS_PRF = 6;
 
         internal static int OFFSET_LAYER = 0;
         internal static int OFFSET_TREE = 4;
-        static int OFFSET_TREE_HGT = 24;
-        static int OFFSET_TREE_INDEX = 28;
+        internal static int OFFSET_TREE_HGT = 24;
+        internal static int OFFSET_TREE_INDEX = 28;
         internal static int OFFSET_TYPE = 16;
-        static int OFFSET_KP_ADDR = 20;
-        static int OFFSET_CHAIN_ADDR = 24;
-        static int OFFSET_HASH_ADDR = 28;
+        internal static int OFFSET_KP_ADDR = 20;
+        internal static int OFFSET_CHAIN_ADDR = 24;
+        internal static int OFFSET_HASH_ADDR = 28;
 
-        internal byte[] value = new byte[32];
+        internal readonly byte[] value = new byte[32];
 
         internal Adrs()
         {
@@ -33,79 +32,80 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         internal Adrs(Adrs adrs)
         {
-            Array.Copy(adrs.value, 0, this.value, 0, adrs.value.Length);
+            Array.Copy(adrs.value, 0, value, 0, adrs.value.Length);
         }
 
-        public void SetLayerAddress(uint layer)
+        internal void SetLayerAddress(uint layer)
         {
             Pack.UInt32_To_BE(layer, value, OFFSET_LAYER);
         }
 
-        public uint GetLayerAddress()
+        internal uint GetLayerAddress()
         {
             return Pack.BE_To_UInt32(value, OFFSET_LAYER);
         }
 
-        public void SetTreeAddress(ulong tree)
+        internal void SetTreeAddress(ulong tree)
         {
             // tree address is 12 bytes
             Pack.UInt64_To_BE(tree, value, OFFSET_TREE + 4);
         }
 
-        public ulong GetTreeAddress()
+        internal ulong GetTreeAddress()
         {
+            // tree address is 12 bytes
             return Pack.BE_To_UInt64(value, OFFSET_TREE + 4);
         }
 
-        public void SetTreeHeight(uint height)
+        internal void SetTreeHeight(uint height)
         {
             Pack.UInt32_To_BE(height, value, OFFSET_TREE_HGT);
         }
 
-        public uint GetTreeHeight()
+        internal uint GetTreeHeight()
         {
             return Pack.BE_To_UInt32(value, OFFSET_TREE_HGT);
         }
 
-        public void SetTreeIndex(uint index)
+        internal void SetTreeIndex(uint index)
         {
             Pack.UInt32_To_BE(index, value, OFFSET_TREE_INDEX);
         }
 
-        public uint GetTreeIndex()
+        internal uint GetTreeIndex()
         {
             return Pack.BE_To_UInt32(value, OFFSET_TREE_INDEX);
         }
 
         // resets part of value to zero in line with 2.7.3
-        public void SetType(uint type)
+        internal void SetAdrsType(uint adrsType)
         {
-            Pack.UInt32_To_BE(type, value, OFFSET_TYPE);
+            Pack.UInt32_To_BE(adrsType, value, OFFSET_TYPE);
 
-            Arrays.Fill(value, 20, value.Length, (byte) 0);
+            Arrays.Fill(value, OFFSET_TYPE + 4, value.Length, 0x00);
         }
-        
-        public void ChangeType(uint type)
+
+        internal void ChangeAdrsType(uint adrsType)
         {
-            Pack.UInt32_To_BE(type, value, OFFSET_TYPE);
+            Pack.UInt32_To_BE(adrsType, value, OFFSET_TYPE);
         }
 
-        public new uint GetType()
+        internal uint GetAdrsType()
         {
             return Pack.BE_To_UInt32(value, OFFSET_TYPE);
         }
 
-        public void SetKeyPairAddress(uint keyPairAddr)
+        internal void SetKeyPairAddress(uint keyPairAddr)
         {
             Pack.UInt32_To_BE(keyPairAddr, value, OFFSET_KP_ADDR);
         }
 
-        public uint GetKeyPairAddress()
+        internal uint GetKeyPairAddress()
         {
             return Pack.BE_To_UInt32(value, OFFSET_KP_ADDR);
         }
 
-        public void SetHashAddress(uint hashAddr)
+        internal void SetHashAddress(uint hashAddr)
         {
             Pack.UInt32_To_BE(hashAddr, value, OFFSET_HASH_ADDR);
         }
@@ -115,4 +115,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             Pack.UInt32_To_BE(chainAddr, value, OFFSET_CHAIN_ADDR);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/Fors.cs b/crypto/src/pqc/crypto/sphincsplus/Fors.cs
index af86eec10..7c83c2017 100644
--- a/crypto/src/pqc/crypto/sphincsplus/Fors.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/Fors.cs
@@ -1,94 +1,93 @@
-using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class Fors
+    internal class Fors
     {
-        SPHINCSPlusEngine engine;
+        private readonly SphincsPlusEngine engine;
 
-        public Fors(SPHINCSPlusEngine engine)
+        internal Fors(SphincsPlusEngine engine)
         {
             this.engine = engine;
         }
 
         // Input: Secret seed SK.seed, start index s, target node height z, public seed PK.seed, address Adrs
         // Output: n-byte root node - top node on Stack
-        byte[] TreeHash(byte[] skSeed, uint s, int z, byte[] pkSeed, Adrs adrsParam)
+        internal byte[] TreeHash(byte[] skSeed, uint s, int z, byte[] pkSeed, Adrs adrsParam)
         {
-            var stack = new List<NodeEntry>();
-
             if (s % (1 << z) != 0)
-            {
                 return null;
-            }
 
+            var stack = new Stack<NodeEntry>();
             Adrs adrs = new Adrs(adrsParam);
+            byte[] sk = new byte[engine.N];
 
             for (uint idx = 0; idx < (1 << z); idx++)
             {
-                adrs.SetType(Adrs.FORS_PRF);
+                adrs.SetAdrsType(Adrs.FORS_PRF);
                 adrs.SetKeyPairAddress(adrsParam.GetKeyPairAddress());
                 adrs.SetTreeHeight(0);
                 adrs.SetTreeIndex(s + idx);
 
-                byte[] sk = engine.PRF(pkSeed, skSeed, adrs);
+                engine.PRF(pkSeed, skSeed, adrs, sk, 0);
                 
-                adrs.ChangeType(Adrs.FORS_TREE);
+                adrs.ChangeAdrsType(Adrs.FORS_TREE);
 
                 byte[] node = engine.F(pkSeed, adrs, sk);
-                
+
                 adrs.SetTreeHeight(1);
 
+                uint adrsTreeHeight = 1;
+                uint adrsTreeIndex = s + idx;
+
                 // while ( Top node on Stack has same height as node )
-                while (stack.Count != 0
-                       && ((NodeEntry) stack[0]).nodeHeight == adrs.GetTreeHeight())
+                while (stack.Count > 0 && stack.Peek().nodeHeight == adrsTreeHeight)
                 {
-                    adrs.SetTreeIndex((adrs.GetTreeIndex() - 1) / 2);
-                    NodeEntry current = (NodeEntry) stack[0];
-                    stack.RemoveAt(0);
+                    adrsTreeIndex = (adrsTreeIndex - 1) / 2;
+                    adrs.SetTreeIndex(adrsTreeIndex);
+
+                    engine.H(pkSeed, adrs, stack.Pop().nodeValue, node, node);
 
-                    node = engine.H(pkSeed, adrs, current.nodeValue, node);
                     //topmost node is now one layer higher
-                    adrs.SetTreeHeight(adrs.GetTreeHeight() + 1);
+                    adrs.SetTreeHeight(++adrsTreeHeight);
                 }
 
-                stack.Insert(0, new NodeEntry(node, adrs.GetTreeHeight()));
+                stack.Push(new NodeEntry(node, adrsTreeHeight));
             }
 
-            return ((NodeEntry) stack[0]).nodeValue;
+            return stack.Peek().nodeValue;
         }
 
-        public SIG_FORS[] Sign(byte[] md, byte[] skSeed, byte[] pkSeed, Adrs paramAdrs)
+        internal SIG_FORS[] Sign(byte[] md, byte[] skSeed, byte[] pkSeed, Adrs paramAdrs)
         {
             Adrs adrs = new Adrs(paramAdrs);
-            uint[] idxs = MessageToIdxs(md, engine.K, engine.A);
             SIG_FORS[] sig_fors = new SIG_FORS[engine.K];
             // compute signature elements
             uint t = engine.T;
             for (uint i = 0; i < engine.K; i++)
             {
                 // get next index
-                uint idx = idxs[i];
+                uint idx = GetMessageIdx(md, (int)i, engine.A);
+
                 // pick private key element
-                
-                adrs.SetType(Adrs.FORS_PRF);
+                adrs.SetAdrsType(Adrs.FORS_PRF);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
                 adrs.SetTreeHeight(0);
                 adrs.SetTreeIndex((uint) (i * t + idx));
-                
-                byte[] sk = engine.PRF(pkSeed, skSeed, adrs);
-                
-                adrs.ChangeType(Adrs.FORS_TREE);
-                
+
+                byte[] sk = new byte[engine.N];
+                engine.PRF(pkSeed, skSeed, adrs, sk, 0);
+
+                adrs.ChangeAdrsType(Adrs.FORS_TREE);
+
                 byte[][] authPath = new byte[engine.A][];
                 // compute auth path
                 for (int j = 0; j < engine.A; j++)
                 {
-                    uint s = (uint) (idx / (1 << j)) ^ 1;
-                    authPath[j] = TreeHash(skSeed, (uint) (i * t + s * (1 << j)), j, pkSeed, adrs);
+                    uint s = (idx >> j) ^ 1U;
+                    authPath[j] = TreeHash(skSeed, (uint) (i * t + (s << j)), j, pkSeed, adrs);
                 }
 
                 sig_fors[i] = new SIG_FORS(sk, authPath);
@@ -97,51 +96,53 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             return sig_fors;
         }
 
-        public byte[] PKFromSig(SIG_FORS[] sig_fors, byte[] message, byte[] pkSeed, Adrs adrs)
+        internal byte[] PKFromSig(SIG_FORS[] sig_fors, byte[] message, byte[] pkSeed, Adrs adrs)
         {
-            byte[][] node = new byte[2][];
             byte[][] root = new byte[engine.K][];
             uint t = engine.T;
 
-            uint[] idxs = MessageToIdxs(message, engine.K, engine.A);
             // compute roots
             for (uint i = 0; i < engine.K; i++)
             {
                 // get next index
-                uint idx = idxs[i];
+                uint idx = GetMessageIdx(message, (int)i, engine.A);
+
                 // compute leaf
                 byte[] sk = sig_fors[i].SK;
                 adrs.SetTreeHeight(0);
                 adrs.SetTreeIndex(i * t + idx);
-                node[0] = engine.F(pkSeed, adrs, sk);
+                byte[] node = engine.F(pkSeed, adrs, sk);
+
                 // compute root from leaf and AUTH
                 byte[][] authPath = sig_fors[i].AuthPath;
-
-                adrs.SetTreeIndex(i * t + idx);
+                uint adrsTreeIndex = i * t + idx;
                 for (int j = 0; j < engine.A; j++)
                 {
                     adrs.SetTreeHeight((uint)j + 1);
-                    if (((idx / (1 << j)) % 2) == 0)
+                    if (((idx >> j) % 2) == 0U)
                     {
-                        adrs.SetTreeIndex(adrs.GetTreeIndex() / 2);
-                        node[1] = engine.H(pkSeed, adrs, node[0], authPath[j]);
+                        adrsTreeIndex = adrsTreeIndex / 2;
+                        adrs.SetTreeIndex(adrsTreeIndex);
+                        engine.H(pkSeed, adrs, node, authPath[j], node);
                     }
                     else
                     {
-                        adrs.SetTreeIndex((adrs.GetTreeIndex() - 1) / 2);
-                        node[1] = engine.H(pkSeed, adrs, authPath[j], node[0]);
+                        adrsTreeIndex = (adrsTreeIndex - 1) / 2;
+                        adrs.SetTreeIndex(adrsTreeIndex);
+                        engine.H(pkSeed, adrs, authPath[j], node, node);
                     }
-
-                    node[0] = node[1];
                 }
 
-                root[i] = node[0];
+                root[i] = node;
             }
 
             Adrs forspkAdrs = new Adrs(adrs); // copy address to create FTS public key address
-            forspkAdrs.SetType(Adrs.FORS_PK);
+            forspkAdrs.SetAdrsType(Adrs.FORS_PK);
             forspkAdrs.SetKeyPairAddress(adrs.GetKeyPairAddress());
-            return engine.T_l(pkSeed, forspkAdrs, Arrays.ConcatenateAll(root));
+
+            byte[] result = new byte[engine.N];
+            engine.T_l(pkSeed, forspkAdrs, Arrays.ConcatenateAll(root), result);
+            return result;
         }
 
         /**
@@ -149,21 +150,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
          * Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits.
          * Assumes indices has space for SPX_FORS_TREES integers.
          */
-        static uint[] MessageToIdxs(byte[] msg, int fors_trees, int fors_height)
+        private static uint GetMessageIdx(byte[] msg, int fors_tree, int fors_height)
         {
-            uint offset = 0;
-            uint[] idxs = new uint[fors_trees];
-            for (int i = 0; i < fors_trees; i++)
+            int offset = fors_tree * fors_height;
+            uint idx = 0;
+            for (int bit = 0; bit < fors_height; bit++)
             {
-                idxs[i] = 0;
-                for (int j = 0; j < fors_height; j++)
-                {
-                    idxs[i] ^= (uint) (((msg[offset >> 3] >> (int)(offset & 0x7)) & 0x1) << j);
-                    offset++;
-                }
+                idx ^= (((uint)msg[offset >> 3] >> (offset & 0x7)) & 1U) << bit;
+                offset++;
             }
-
-            return idxs;
+            return idx;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/HT.cs b/crypto/src/pqc/crypto/sphincsplus/HT.cs
index 2cd149f0d..15893fc46 100644
--- a/crypto/src/pqc/crypto/sphincsplus/HT.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/HT.cs
@@ -1,20 +1,19 @@
-using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class HT
+    internal class HT
     {
         private byte[] skSeed;
         private byte[] pkSeed;
-        SPHINCSPlusEngine engine;
+        SphincsPlusEngine engine;
         WotsPlus wots;
 
         internal byte[] HTPubKey;
 
-        public HT(SPHINCSPlusEngine engine, byte[] skSeed, byte[] pkSeed)
+        internal HT(SphincsPlusEngine engine, byte[] skSeed, byte[] pkSeed)
         {
             this.skSeed = skSeed;
             this.pkSeed = pkSeed;
@@ -75,28 +74,28 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             return Arrays.ConcatenateAll(totSigs);
         }
 
-        byte[] xmss_PKgen(byte[] skSeed, byte[] pkSeed, Adrs adrs)
+        private byte[] xmss_PKgen(byte[] skSeed, byte[] pkSeed, Adrs adrs)
         {
             return TreeHash(skSeed, 0, engine.H_PRIME, pkSeed, adrs);
         }
 
         // Input: index idx, XMSS signature SIG_XMSS = (sig || AUTH), n-byte message M, public seed PK.seed, address Adrs
         // Output: n-byte root value node[0]
-        byte[] xmss_pkFromSig(uint idx, SIG_XMSS sig_xmss, byte[] M, byte[] pkSeed, Adrs paramAdrs)
+        private byte[] xmss_pkFromSig(uint idx, SIG_XMSS sig_xmss, byte[] M, byte[] pkSeed, Adrs paramAdrs)
         {
             Adrs adrs = new Adrs(paramAdrs);
 
             // compute WOTS+ pk from WOTS+ sig
-            adrs.SetType(Adrs.WOTS_HASH);
+            adrs.SetAdrsType(Adrs.WOTS_HASH);
             adrs.SetKeyPairAddress(idx);
-            byte[] sig = sig_xmss.GetWOTSSig();
-            byte[][] AUTH = sig_xmss.GetXMSSAUTH();
+            byte[] sig = sig_xmss.WotsSig;
+            byte[][] AUTH = sig_xmss.XmssAuth;
 
-            byte[] node0 = wots.PKFromSig(sig, M, pkSeed, adrs);
-            byte[] node1 = null;
+            byte[] node = new byte[engine.N];
+            wots.PKFromSig(sig, M, pkSeed, adrs, node);
 
             // compute root from WOTS+ pk and AUTH
-            adrs.SetType(Adrs.TREE);
+            adrs.SetAdrsType(Adrs.TREE);
             adrs.SetTreeIndex(idx);
             for (uint k = 0; k < engine.H_PRIME; k++)
             {
@@ -104,30 +103,28 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 if (((idx / (1 << (int)k)) % 2) == 0)
                 {
                     adrs.SetTreeIndex(adrs.GetTreeIndex() / 2);
-                    node1 = engine.H(pkSeed, adrs, node0, AUTH[k]);
+                    engine.H(pkSeed, adrs, node, AUTH[k], node);
                 }
                 else
                 {
                     adrs.SetTreeIndex((adrs.GetTreeIndex() - 1) / 2);
-                    node1 = engine.H(pkSeed, adrs, AUTH[k], node0);
+                    engine.H(pkSeed, adrs, AUTH[k], node, node);
                 }
-
-                node0 = node1;
             }
 
-            return node0;
+            return node;
         }
 
         //    # Input: n-byte message M, secret seed SK.seed, index idx, public seed PK.seed,
         //    address Adrs
         //    # Output: XMSS signature SIG_XMSS = (sig || AUTH)
-        SIG_XMSS xmss_sign(byte[] M, byte[] skSeed, uint idx, byte[] pkSeed, Adrs paramAdrs)
+        private SIG_XMSS xmss_sign(byte[] M, byte[] skSeed, uint idx, byte[] pkSeed, Adrs paramAdrs)
         {
             byte[][] AUTH = new byte[engine.H_PRIME][];
             
             Adrs adrs = new Adrs(paramAdrs);
 
-            adrs.SetType(Adrs.TREE);
+            adrs.SetAdrsType(Adrs.TREE);
             adrs.SetLayerAddress(paramAdrs.GetLayerAddress());
             adrs.SetTreeAddress(paramAdrs.GetTreeAddress());
 
@@ -140,7 +137,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             }
 
             adrs = new Adrs(paramAdrs);
-            adrs.SetType(Adrs.WOTS_PK);
+            adrs.SetAdrsType(Adrs.WOTS_PK);
             adrs.SetKeyPairAddress(idx);
 
             byte[] sig = wots.Sign(M, skSeed, pkSeed, adrs);
@@ -151,49 +148,51 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         // Input: Secret seed SK.seed, start index s, target node height z, public seed
         //PK.seed, address Adrs
         // Output: n-byte root node - top node on Stack
-        byte[] TreeHash(byte[] skSeed, uint s, uint z, byte[] pkSeed, Adrs adrsParam)
+        private byte[] TreeHash(byte[] skSeed, uint s, uint z, byte[] pkSeed, Adrs adrsParam)
         {
-            Adrs adrs = new Adrs(adrsParam);
-
-            var stack = new List<NodeEntry>();
-
             if (s % (1 << (int)z) != 0)
-            {
                 return null;
-            }
+
+            var stack = new Stack<NodeEntry>();
+            Adrs adrs = new Adrs(adrsParam);
 
             for (uint idx = 0; idx < (1 << (int)z); idx++)
             {
-                adrs.SetType(Adrs.WOTS_HASH);
+                adrs.SetAdrsType(Adrs.WOTS_HASH);
                 adrs.SetKeyPairAddress(s + idx);
-                byte[] node = wots.PKGen(skSeed, pkSeed, adrs);
 
-                adrs.SetType(Adrs.TREE);
+                byte[] node = new byte[engine.N];
+                wots.PKGen(skSeed, pkSeed, adrs, node);
+
+                adrs.SetAdrsType(Adrs.TREE);
                 adrs.SetTreeHeight(1);
                 adrs.SetTreeIndex(s + idx);
 
+                uint adrsTreeHeight = 1;
+                uint adrsTreeIndex = s + idx;
+
                 // while ( Top node on Stack has same height as node )
-                while (stack.Count != 0
-                       && ((NodeEntry) stack[0]).nodeHeight == adrs.GetTreeHeight())
+                while (stack.Count > 0 && stack.Peek().nodeHeight == adrsTreeHeight)
                 {
-                    adrs.SetTreeIndex((adrs.GetTreeIndex() - 1) / 2);
-                    NodeEntry current = ((NodeEntry) stack[0]);
-                    stack.RemoveAt(0);
-                    node = engine.H(pkSeed, adrs, current.nodeValue, node);
+                    adrsTreeIndex = (adrsTreeIndex - 1) / 2;
+                    adrs.SetTreeIndex(adrsTreeIndex);
+
+                    engine.H(pkSeed, adrs, stack.Pop().nodeValue, node, node);
+
                     //topmost node is now one layer higher
-                    adrs.SetTreeHeight(adrs.GetTreeHeight() + 1);
+                    adrs.SetTreeHeight(++adrsTreeHeight);
                 }
 
-                stack.Insert(0, new NodeEntry(node, adrs.GetTreeHeight()));
+                stack.Push(new NodeEntry(node, adrsTreeHeight));
             }
 
-            return ((NodeEntry) stack[0]).nodeValue;
+            return stack.Peek().nodeValue;
         }
 
         //    # Input: Message M, signature SIG_HT, public seed PK.seed, tree index idx_tree,
         //    leaf index idx_leaf, HT public key PK_HT.
         //    # Output: bool
-        public bool Verify(byte[] M, SIG_XMSS[] sig_ht, byte[] pkSeed, ulong idx_tree, uint idx_leaf, byte[] PK_HT)
+        internal bool Verify(byte[] M, SIG_XMSS[] sig_ht, byte[] pkSeed, ulong idx_tree, uint idx_leaf, byte[] PK_HT)
         {
             // init
             Adrs adrs = new Adrs();
@@ -215,4 +214,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             return Arrays.AreEqual(PK_HT, node);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaS256Digest.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaS256Digest.cs
new file mode 100644
index 000000000..90a5a0dbb
--- /dev/null
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaS256Digest.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
+{
+    internal sealed class HarakaS256Digest
+        : HarakaSBase
+    {
+        public HarakaS256Digest(HarakaSXof harakaSXof)
+        {
+            haraka256_rc = harakaSXof.haraka256_rc;
+        }
+
+        public string AlgorithmName => "HarakaS-256";
+
+        public int GetDigestSize()
+        {
+            return 32;
+        }
+
+        public void Update(byte input)
+        {
+            if (off > 32 - 1)
+                throw new ArgumentException("total input cannot be more than 32 bytes");
+
+            buffer[off++] = input;
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int len)
+        {
+            if (off > 32 - len)
+                throw new ArgumentException("total input cannot be more than 32 bytes");
+
+            Array.Copy(input, inOff, buffer, off, len);
+            off += len;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (off > 32 - input.Length)
+                throw new ArgumentException("total input cannot be more than 32 bytes");
+
+            input.CopyTo(buffer.AsSpan(off));
+            off += input.Length;
+        }
+#endif
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+            // TODO Check received all 32 bytes of input?
+
+            byte[] s = new byte[32];
+            Haraka256Perm(s);
+            Xor(s, 0, buffer, 0, output, outOff, 32);
+
+            Reset();
+
+            return 32;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            // TODO Check received all 32 bytes of input?
+
+            Span<byte> s = stackalloc byte[32];
+            Haraka256Perm(s);
+            Xor(s, buffer, output[..32]);
+
+            Reset();
+
+            return 32;
+        }
+#endif
+    }
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaS512Digest.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaS512Digest.cs
new file mode 100644
index 000000000..d96509f62
--- /dev/null
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaS512Digest.cs
@@ -0,0 +1,86 @@
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
+{
+    /**
+    * Haraka-512 v2, https://eprint.iacr.org/2016/098.pdf
+    * <p>
+    * Haraka512-256 with reference to Python Reference Impl from: https://github.com/sphincs/sphincsplus
+    * </p>
+    */
+    internal sealed class HarakaS512Digest
+        : HarakaSBase
+    {
+        public HarakaS512Digest(HarakaSBase harakaSBase)
+        {
+            haraka512_rc = harakaSBase.haraka512_rc;
+        }
+
+        public string AlgorithmName => "HarakaS-512";
+
+        public int GetDigestSize()
+        {
+            return 32;
+        }
+
+        public void Update(byte input)
+        {
+            if (off > 64 - 1)
+                throw new ArgumentException("total input cannot be more than 64 bytes");
+
+            buffer[off++] = input;
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int len)
+        {
+            if (off > 64 - len)
+                throw new ArgumentException("total input cannot be more than 64 bytes");
+
+            Array.Copy(input, inOff, buffer, off, len);
+            off += len;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (off > 64 - input.Length)
+                throw new ArgumentException("total input cannot be more than 64 bytes");
+
+            input.CopyTo(buffer.AsSpan(off));
+            off += input.Length;
+        }
+#endif
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+            // TODO Check received all 64 bytes of input?
+
+            byte[] s = new byte[64];
+            Haraka512Perm(s);
+            Xor(s,  8, buffer,  8, output, outOff     ,  8);
+            Xor(s, 24, buffer, 24, output, outOff +  8, 16);
+            Xor(s, 48, buffer, 48, output, outOff + 24,  8);
+
+            Reset();
+
+            return 32;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            // TODO Check received all 64 bytes of input?
+
+            Span<byte> s = stackalloc byte[64];
+            Haraka512Perm(s);
+            Xor(s[8..], buffer.AsSpan(8), output[..8]);
+            Xor(s[24..], buffer.AsSpan(24), output[8..24]);
+            Xor(s[48..], buffer.AsSpan(48), output[24..32]);
+
+            Reset();
+
+            return 32;
+        }
+#endif
+    }
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaSBase.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaSBase.cs
new file mode 100644
index 000000000..2134c5c73
--- /dev/null
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaSBase.cs
@@ -0,0 +1,778 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math.Raw;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
+{
+    /**
+     * Haraka-512 v2, https://eprint.iacr.org/2016/098.pdf
+     * <p>
+     * Haraka512-256 with reference to Python Reference Impl from: https://github.com/sphincs/sphincsplus
+     * </p>
+     */
+    internal abstract class HarakaSBase
+    {
+        private static readonly byte[] RC0  = Hex.DecodeStrict("0684704ce620c00ab2c5fef075817b9d");
+        private static readonly byte[] RC1  = Hex.DecodeStrict("8b66b4e188f3a06b640f6ba42f08f717");
+        private static readonly byte[] RC2  = Hex.DecodeStrict("3402de2d53f28498cf029d609f029114");
+        private static readonly byte[] RC3  = Hex.DecodeStrict("0ed6eae62e7b4f08bbf3bcaffd5b4f79");
+        private static readonly byte[] RC4  = Hex.DecodeStrict("cbcfb0cb4872448b79eecd1cbe397044");
+        private static readonly byte[] RC5  = Hex.DecodeStrict("7eeacdee6e9032b78d5335ed2b8a057b");
+        private static readonly byte[] RC6  = Hex.DecodeStrict("67c28f435e2e7cd0e2412761da4fef1b");
+        private static readonly byte[] RC7  = Hex.DecodeStrict("2924d9b0afcacc07675ffde21fc70b3b");
+        private static readonly byte[] RC8  = Hex.DecodeStrict("ab4d63f1e6867fe9ecdb8fcab9d465ee");
+        private static readonly byte[] RC9  = Hex.DecodeStrict("1c30bf84d4b7cd645b2a404fad037e33");
+        private static readonly byte[] RC10 = Hex.DecodeStrict("b2cc0bb9941723bf69028b2e8df69800");
+        private static readonly byte[] RC11 = Hex.DecodeStrict("fa0478a6de6f55724aaa9ec85c9d2d8a");
+        private static readonly byte[] RC12 = Hex.DecodeStrict("dfb49f2b6b772a120efa4f2e29129fd4");
+        private static readonly byte[] RC13 = Hex.DecodeStrict("1ea10344f449a23632d611aebb6a12ee");
+        private static readonly byte[] RC14 = Hex.DecodeStrict("af0449884b0500845f9600c99ca8eca6");
+        private static readonly byte[] RC15 = Hex.DecodeStrict("21025ed89d199c4f78a2c7e327e593ec");
+        private static readonly byte[] RC16 = Hex.DecodeStrict("bf3aaaf8a759c9b7b9282ecd82d40173");
+        private static readonly byte[] RC17 = Hex.DecodeStrict("6260700d6186b01737f2efd910307d6b");
+        private static readonly byte[] RC18 = Hex.DecodeStrict("5aca45c22130044381c29153f6fc9ac6");
+        private static readonly byte[] RC19 = Hex.DecodeStrict("9223973c226b68bb2caf92e836d1943a");
+        private static readonly byte[] RC20 = Hex.DecodeStrict("d3bf9238225886eb6cbab958e51071b4");
+        private static readonly byte[] RC21 = Hex.DecodeStrict("db863ce5aef0c677933dfddd24e1128d");
+        private static readonly byte[] RC22 = Hex.DecodeStrict("bb606268ffeba09c83e48de3cb2212b1");
+        private static readonly byte[] RC23 = Hex.DecodeStrict("734bd3dce2e4d19c2db91a4ec72bf77d");
+        private static readonly byte[] RC24 = Hex.DecodeStrict("43bb47c361301b434b1415c42cb3924e");
+        private static readonly byte[] RC25 = Hex.DecodeStrict("dba775a8e707eff603b231dd16eb6899");
+        private static readonly byte[] RC26 = Hex.DecodeStrict("6df3614b3c7559778e5e23027eca472c");
+        private static readonly byte[] RC27 = Hex.DecodeStrict("cda75a17d6de7d776d1be5b9b88617f9");
+        private static readonly byte[] RC28 = Hex.DecodeStrict("ec6b43f06ba8e9aa9d6c069da946ee5d");
+        private static readonly byte[] RC29 = Hex.DecodeStrict("cb1e6950f957332ba25311593bf327c1");
+        private static readonly byte[] RC30 = Hex.DecodeStrict("2cee0c7500da619ce4ed0353600ed0d9");
+        private static readonly byte[] RC31 = Hex.DecodeStrict("f0b1a5a196e90cab80bbbabc63a4a350");
+        private static readonly byte[] RC32 = Hex.DecodeStrict("ae3db1025e962988ab0dde30938dca39");
+        private static readonly byte[] RC33 = Hex.DecodeStrict("17bb8f38d554a40b8814f3a82e75b442");
+        private static readonly byte[] RC34 = Hex.DecodeStrict("34bb8a5b5f427fd7aeb6b779360a16f6");
+        private static readonly byte[] RC35 = Hex.DecodeStrict("26f65241cbe5543843ce5918ffbaafde");
+        private static readonly byte[] RC36 = Hex.DecodeStrict("4ce99a54b9f3026aa2ca9cf7839ec978");
+        private static readonly byte[] RC37 = Hex.DecodeStrict("ae51a51a1bdff7be40c06e2822901235");
+        private static readonly byte[] RC38 = Hex.DecodeStrict("a0c1613cba7ed22bc173bc0f48a659cf");
+        private static readonly byte[] RC39 = Hex.DecodeStrict("756acc03022882884ad6bdfde9c59da1");
+
+        private static readonly byte[][] RoundConstants =
+        {
+            RC0 , RC1 , RC2 , RC3 ,
+            RC4 , RC5 , RC6 , RC7 ,
+            RC8 , RC9 , RC10, RC11,
+            RC12, RC13, RC14, RC15,
+            RC16, RC17, RC18, RC19,
+            RC20, RC21, RC22, RC23,
+            RC24, RC25, RC26, RC27,
+            RC28, RC29, RC30, RC31,
+            RC32, RC33, RC34, RC35,
+            RC36, RC37, RC38, RC39,
+        };
+
+        internal ulong[][] haraka512_rc = new ulong[][]{
+            new ulong[]{0x24cf0ab9086f628bL, 0xbdd6eeecc83b8382L, 0xd96fb0306cdad0a7L, 0xaace082ac8f95f89L, 0x449d8e8870d7041fL, 0x49bb2f80b2b3e2f8L, 0x0569ae98d93bb258L, 0x23dc9691e7d6a4b1L},
+            new ulong[]{0xd8ba10ede0fe5b6eL, 0x7ecf7dbe424c7b8eL, 0x6ea9949c6df62a31L, 0xbf3f3c97ec9c313eL, 0x241d03a196a1861eL, 0xead3a51116e5a2eaL, 0x77d479fcad9574e3L, 0x18657a1af894b7a0L},
+            new ulong[]{0x10671e1a7f595522L, 0xd9a00ff675d28c7bL, 0x2f1edf0d2b9ba661L, 0xb8ff58b8e3de45f9L, 0xee29261da9865c02L, 0xd1532aa4b50bdf43L, 0x8bf858159b231bb1L, 0xdf17439d22d4f599L},
+            new ulong[]{0xdd4b2f0870b918c0L, 0x757a81f3b39b1bb6L, 0x7a5c556898952e3fL, 0x7dd70a16d915d87aL, 0x3ae61971982b8301L, 0xc3ab319e030412beL, 0x17c0033ac094a8cbL, 0x5a0630fc1a8dc4efL},
+            new ulong[]{0x17708988c1632f73L, 0xf92ddae090b44f4fL, 0x11ac0285c43aa314L, 0x509059941936b8baL, 0xd03e152fa2ce9b69L, 0x3fbcbcb63a32998bL, 0x6204696d692254f7L, 0x915542ed93ec59b4L},
+            new ulong[]{0xf4ed94aa8879236eL, 0xff6cb41cd38e03c0L, 0x069b38602368aeabL, 0x669495b820f0ddbaL, 0xf42013b1b8bf9e3dL, 0xcf935efe6439734dL, 0xbc1dcf42ca29e3f8L, 0x7e6d3ed29f78ad67L},
+            new ulong[]{0xf3b0f6837ffcddaaL, 0x3a76faef934ddf41L, 0xcec7ae583a9c8e35L, 0xe4dd18c68f0260afL, 0x2c0e5df1ad398eaaL, 0x478df5236ae22e8cL, 0xfb944c46fe865f39L, 0xaa48f82f028132baL},
+            new ulong[]{0x231b9ae2b76aca77L, 0x292a76a712db0b40L, 0x5850625dc8134491L, 0x73137dd469810fb5L, 0x8a12a6a202a474fdL, 0xd36fd9daa78bdb80L, 0xb34c5e733505706fL, 0xbaf1cdca818d9d96L},
+            new ulong[]{0x2e99781335e8c641L, 0xbddfe5cce47d560eL, 0xf74e9bf32e5e040cL, 0x1d7a709d65996be9L, 0x670df36a9cf66cddL, 0xd05ef84a176a2875L, 0x0f888e828cb1c44eL, 0x1a79e9c9727b052cL},
+            new ulong[]{0x83497348628d84deL, 0x2e9387d51f22a754L, 0xb000068da2f852d6L, 0x378c9e1190fd6fe5L, 0x870027c316de7293L, 0xe51a9d4462e047bbL, 0x90ecf7f8c6251195L, 0x655953bfbed90a9cL},
+        };
+
+        internal uint[][] haraka256_rc = new uint[10][];
+
+        protected readonly byte[] buffer;
+        protected int off;
+
+        protected HarakaSBase()
+        {
+            this.buffer = new byte[64];
+            off = 0;
+
+            byte[] buf = new byte[640];
+            byte[] tmp = new byte[16];
+            for (int rc = 0; rc < 40; ++rc)
+            {
+                Arrays.Reverse(RoundConstants[rc]).CopyTo(buf, rc << 4);
+            }
+            for (int round = 0; round < 10; ++round)
+            {
+                InterleaveConstant(haraka512_rc[round], buf, round << 6);
+                //for (int j = 0; j < 8; ++j)
+                //{
+                //    Console.Write(haraka512_rc[round][j].ToString("X") + ", ");
+                //}
+                //Console.WriteLine();
+            }
+            //Console.WriteLine("-----");
+        }
+
+        protected void Reset()
+        {
+            off = 0;
+            Arrays.Clear(buffer);
+        }
+
+        protected static void InterleaveConstant(ulong[] output, byte[] input, int startPos)
+        {
+            uint[] tmp_32_constant = new uint[16];
+            Pack.LE_To_UInt32(input, startPos, tmp_32_constant);
+            for (int i = 0; i < 4; ++i)
+            {
+                BrAesCt64InterleaveIn(output, i, tmp_32_constant, i << 2);
+            }
+            BrAesCt64Ortho(output);
+        }
+
+        protected static void InterleaveConstant32(uint[] output, byte[] input, int startPos)
+        {
+            for (int i = 0; i < 4; ++i)
+            {
+                output[i << 1] = Pack.LE_To_UInt32(input, startPos + (i << 2));
+                output[(i << 1) + 1] = Pack.LE_To_UInt32(input, startPos + (i << 2) + 16);
+            }
+            BrAesCtOrtho(output);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void Haraka512Perm(Span<byte> output)
+#else
+        internal void Haraka512Perm(byte[] output)
+#endif
+        {
+            uint[] w = new uint[16];
+            ulong[] q = new ulong[8];
+            Pack.LE_To_UInt32(buffer, 0, w);
+            for (int i = 0; i < 4; ++i)
+            {
+                BrAesCt64InterleaveIn(q, i, w, i << 2);
+            }
+            BrAesCt64Ortho(q);
+            for (int i = 0; i < 5; ++i)
+            {
+                for (int j = 0; j < 2; ++j)
+                {
+                    BrAesCt64BitsliceSbox(q);
+                    ShiftRows(q);
+                    MixColumns(q);
+                    AddRoundKey(q, haraka512_rc[(i << 1) + j]);
+                }
+                for (int j = 0; j < 8; j++)
+                {
+                    ulong t = q[j];
+                    q[j] = (t & 0x0001_0001_0001_0001L) <<  5 |
+                           (t & 0x0002_0002_0002_0002L) << 12 |
+                           (t & 0x0004_0004_0004_0004L) >>  1 |
+                           (t & 0x0008_0008_0008_0008L) <<  6 |
+                           (t & 0x0020_0020_0020_0020L) <<  9 |
+                           (t & 0x0040_0040_0040_0040L) >>  4 |
+                           (t & 0x0080_0080_0080_0080L) <<  3 |
+                           (t & 0x2100_2100_2100_2100L) >>  5 |
+                           (t & 0x0210_0210_0210_0210L) <<  2 |
+                           (t & 0x0800_0800_0800_0800L) <<  4 |
+                           (t & 0x1000_1000_1000_1000L) >> 12 |
+                           (t & 0x4000_4000_4000_4000L) >> 10 |
+                           (t & 0x8400_8400_8400_8400L) >>  3;
+                }
+            }
+            BrAesCt64Ortho(q);
+            for (int i = 0; i < 4; i++)
+            {
+                BrAesCt64InterleaveOut(w, q, i);
+            }
+            for (int i = 0; i < 16; ++i)
+            {
+                for (int j = 0; j < 4; ++j)
+                {
+                    output[(i << 2) + j] = (byte)(w[i] >> (j << 3));
+                }
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void Haraka256Perm(Span<byte> output)
+#else
+        internal void Haraka256Perm(byte[] output)
+#endif
+        {
+            uint[] q = new uint[8];
+            InterleaveConstant32(q, buffer, 0);
+            for (int i = 0; i < 5; ++i)
+            {
+                for (int j = 0; j < 2; ++j)
+                {
+                    BrAesCtBitsliceSbox(q);
+                    ShiftRows32(q);
+                    MixColumns32(q);
+                    AddRoundKey32(q, haraka256_rc[(i << 1) + j]);
+                }
+                for (int j = 0; j < 8; j++)
+                {
+                    uint t = Bits.BitPermuteStep(q[j], 0x0C_0C_0C_0CU, 2);
+                    q[j]   = Bits.BitPermuteStep(t   , 0x22_22_22_22U, 1);
+                }
+            }
+            BrAesCtOrtho(q);
+            for (int i = 0; i < 4; i++)
+            {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Pack.UInt32_To_LE(q[i << 1], output[(i << 2)..]);
+                Pack.UInt32_To_LE(q[(i << 1) + 1], output[((i << 2) + 16)..]);
+#else
+                Pack.UInt32_To_LE(q[i << 1], output, i << 2);
+                Pack.UInt32_To_LE(q[(i << 1) + 1], output, (i << 2) + 16);
+#endif
+            }
+        }
+
+        private static void BrAesCt64InterleaveIn(ulong[] q, int qPos, uint[] w, int startPos)
+        {
+            ulong x0, x1, x2, x3;
+            x0 = (ulong)w[startPos] & 0x00000000FFFFFFFFL;
+            x1 = (ulong)w[startPos + 1] & 0x00000000FFFFFFFFL;
+            x2 = (ulong)w[startPos + 2] & 0x00000000FFFFFFFFL;
+            x3 = (ulong)w[startPos + 3] & 0x00000000FFFFFFFFL;
+            x0 |= x0 << 16;
+            x1 |= x1 << 16;
+            x2 |= x2 << 16;
+            x3 |= x3 << 16;
+            x0 &= 0x0000FFFF0000FFFFL;
+            x1 &= 0x0000FFFF0000FFFFL;
+            x2 &= 0x0000FFFF0000FFFFL;
+            x3 &= 0x0000FFFF0000FFFFL;
+            x0 |= x0 << 8;
+            x1 |= x1 << 8;
+            x2 |= x2 << 8;
+            x3 |= x3 << 8;
+            x0 &= 0x00FF00FF00FF00FFL;
+            x1 &= 0x00FF00FF00FF00FFL;
+            x2 &= 0x00FF00FF00FF00FFL;
+            x3 &= 0x00FF00FF00FF00FFL;
+            q[qPos] = x0 | (x2 << 8);
+            q[qPos + 4] = x1 | (x3 << 8);
+        }
+
+        private static void BrAesCtBitsliceSbox(uint[] q)
+        {
+            /*
+             * This S-box implementation is a straightforward translation of
+             * the circuit described by Boyar and Peralta in "A new
+             * combinational logic minimization technique with applications
+             * to cryptology" (https://eprint.iacr.org/2009/191.pdf).
+             *
+             * Note that variables x* (input) and s* (output) are numbered
+             * in "reverse" order (x0 is the high bit, x7 is the low bit).
+             */
+
+            uint x0, x1, x2, x3, x4, x5, x6, x7;
+            uint y1, y2, y3, y4, y5, y6, y7, y8, y9;
+            uint y10, y11, y12, y13, y14, y15, y16, y17, y18, y19;
+            uint y20, y21;
+            uint z0, z1, z2, z3, z4, z5, z6, z7, z8, z9;
+            uint z10, z11, z12, z13, z14, z15, z16, z17;
+            uint t0, t1, t2, t3, t4, t5, t6, t7, t8, t9;
+            uint t10, t11, t12, t13, t14, t15, t16, t17, t18, t19;
+            uint t20, t21, t22, t23, t24, t25, t26, t27, t28, t29;
+            uint t30, t31, t32, t33, t34, t35, t36, t37, t38, t39;
+            uint t40, t41, t42, t43, t44, t45, t46, t47, t48, t49;
+            uint t50, t51, t52, t53, t54, t55, t56, t57, t58, t59;
+            uint t60, t61, t62, t63, t64, t65, t66, t67;
+            uint s0, s1, s2, s3, s4, s5, s6, s7;
+
+            x0 = q[7];
+            x1 = q[6];
+            x2 = q[5];
+            x3 = q[4];
+            x4 = q[3];
+            x5 = q[2];
+            x6 = q[1];
+            x7 = q[0];
+
+            /*
+             * Top linear transformation.
+             */
+            y14 = x3 ^ x5;
+            y13 = x0 ^ x6;
+            y9 = x0 ^ x3;
+            y8 = x0 ^ x5;
+            t0 = x1 ^ x2;
+            y1 = t0 ^ x7;
+            y4 = y1 ^ x3;
+            y12 = y13 ^ y14;
+            y2 = y1 ^ x0;
+            y5 = y1 ^ x6;
+            y3 = y5 ^ y8;
+            t1 = x4 ^ y12;
+            y15 = t1 ^ x5;
+            y20 = t1 ^ x1;
+            y6 = y15 ^ x7;
+            y10 = y15 ^ t0;
+            y11 = y20 ^ y9;
+            y7 = x7 ^ y11;
+            y17 = y10 ^ y11;
+            y19 = y10 ^ y8;
+            y16 = t0 ^ y11;
+            y21 = y13 ^ y16;
+            y18 = x0 ^ y16;
+
+            /*
+             * Non-linear section.
+             */
+            t2 = y12 & y15;
+            t3 = y3 & y6;
+            t4 = t3 ^ t2;
+            t5 = y4 & x7;
+            t6 = t5 ^ t2;
+            t7 = y13 & y16;
+            t8 = y5 & y1;
+            t9 = t8 ^ t7;
+            t10 = y2 & y7;
+            t11 = t10 ^ t7;
+            t12 = y9 & y11;
+            t13 = y14 & y17;
+            t14 = t13 ^ t12;
+            t15 = y8 & y10;
+            t16 = t15 ^ t12;
+            t17 = t4 ^ t14;
+            t18 = t6 ^ t16;
+            t19 = t9 ^ t14;
+            t20 = t11 ^ t16;
+            t21 = t17 ^ y20;
+            t22 = t18 ^ y19;
+            t23 = t19 ^ y21;
+            t24 = t20 ^ y18;
+
+            t25 = t21 ^ t22;
+            t26 = t21 & t23;
+            t27 = t24 ^ t26;
+            t28 = t25 & t27;
+            t29 = t28 ^ t22;
+            t30 = t23 ^ t24;
+            t31 = t22 ^ t26;
+            t32 = t31 & t30;
+            t33 = t32 ^ t24;
+            t34 = t23 ^ t33;
+            t35 = t27 ^ t33;
+            t36 = t24 & t35;
+            t37 = t36 ^ t34;
+            t38 = t27 ^ t36;
+            t39 = t29 & t38;
+            t40 = t25 ^ t39;
+
+            t41 = t40 ^ t37;
+            t42 = t29 ^ t33;
+            t43 = t29 ^ t40;
+            t44 = t33 ^ t37;
+            t45 = t42 ^ t41;
+            z0 = t44 & y15;
+            z1 = t37 & y6;
+            z2 = t33 & x7;
+            z3 = t43 & y16;
+            z4 = t40 & y1;
+            z5 = t29 & y7;
+            z6 = t42 & y11;
+            z7 = t45 & y17;
+            z8 = t41 & y10;
+            z9 = t44 & y12;
+            z10 = t37 & y3;
+            z11 = t33 & y4;
+            z12 = t43 & y13;
+            z13 = t40 & y5;
+            z14 = t29 & y2;
+            z15 = t42 & y9;
+            z16 = t45 & y14;
+            z17 = t41 & y8;
+
+            /*
+             * Bottom linear transformation.
+             */
+            t46 = z15 ^ z16;
+            t47 = z10 ^ z11;
+            t48 = z5 ^ z13;
+            t49 = z9 ^ z10;
+            t50 = z2 ^ z12;
+            t51 = z2 ^ z5;
+            t52 = z7 ^ z8;
+            t53 = z0 ^ z3;
+            t54 = z6 ^ z7;
+            t55 = z16 ^ z17;
+            t56 = z12 ^ t48;
+            t57 = t50 ^ t53;
+            t58 = z4 ^ t46;
+            t59 = z3 ^ t54;
+            t60 = t46 ^ t57;
+            t61 = z14 ^ t57;
+            t62 = t52 ^ t58;
+            t63 = t49 ^ t58;
+            t64 = z4 ^ t59;
+            t65 = t61 ^ t62;
+            t66 = z1 ^ t63;
+            s0 = t59 ^ t63;
+            s6 = t56 ^ ~t62;
+            s7 = t48 ^ ~t60;
+            t67 = t64 ^ t65;
+            s3 = t53 ^ t66;
+            s4 = t51 ^ t66;
+            s5 = t47 ^ t65;
+            s1 = t64 ^ ~s3;
+            s2 = t55 ^ ~t67;
+
+            q[7] = s0;
+            q[6] = s1;
+            q[5] = s2;
+            q[4] = s3;
+            q[3] = s4;
+            q[2] = s5;
+            q[1] = s6;
+            q[0] = s7;
+        }
+
+        private static void ShiftRows32(uint[] q)
+        {
+            for (int i = 0; i < 8; i++)
+            {
+                uint t = Bits.BitPermuteStep(q[i], 0x0C_0F_03_00U, 4);
+                q[i]   = Bits.BitPermuteStep(t   , 0x33_00_33_00U, 2);
+            }
+        }
+
+        private static void MixColumns32(uint[] q)
+        {
+            uint q0 = q[0], r0 = Integers.RotateRight(q0, 8), s0 = q0 ^ r0;
+            uint q1 = q[1], r1 = Integers.RotateRight(q1, 8), s1 = q1 ^ r1;
+            uint q2 = q[2], r2 = Integers.RotateRight(q2, 8), s2 = q2 ^ r2;
+            uint q3 = q[3], r3 = Integers.RotateRight(q3, 8), s3 = q3 ^ r3;
+            uint q4 = q[4], r4 = Integers.RotateRight(q4, 8), s4 = q4 ^ r4;
+            uint q5 = q[5], r5 = Integers.RotateRight(q5, 8), s5 = q5 ^ r5;
+            uint q6 = q[6], r6 = Integers.RotateRight(q6, 8), s6 = q6 ^ r6;
+            uint q7 = q[7], r7 = Integers.RotateRight(q7, 8), s7 = q7 ^ r7;
+
+            q[0] = r0       ^ s7 ^ Integers.RotateRight(s0, 16); 
+            q[1] = r1 ^ s0  ^ s7 ^ Integers.RotateRight(s1, 16); 
+            q[2] = r2 ^ s1       ^ Integers.RotateRight(s2, 16);
+            q[3] = r3 ^ s2  ^ s7 ^ Integers.RotateRight(s3, 16);
+            q[4] = r4 ^ s3  ^ s7 ^ Integers.RotateRight(s4, 16);
+            q[5] = r5 ^ s4       ^ Integers.RotateRight(s5, 16);
+            q[6] = r6 ^ s5       ^ Integers.RotateRight(s6, 16);
+            q[7] = r7 ^ s6       ^ Integers.RotateRight(s7, 16);
+        }
+
+        private static void AddRoundKey32(uint[] q, uint[] sk)
+        {
+            q[0] ^= sk[0];
+            q[1] ^= sk[1];
+            q[2] ^= sk[2];
+            q[3] ^= sk[3];
+            q[4] ^= sk[4];
+            q[5] ^= sk[5];
+            q[6] ^= sk[6];
+            q[7] ^= sk[7];
+        }
+
+        private static void BrAesCt64Ortho(ulong[] q)
+        {
+            ulong q0 = q[0], q1 = q[1], q2 = q[2], q3 = q[3], q4 = q[4], q5 = q[5], q6 = q[6], q7 = q[7];
+
+            Bits.BitPermuteStep2(ref q1, ref q0, 0x5555555555555555UL, 1);
+            Bits.BitPermuteStep2(ref q3, ref q2, 0x5555555555555555UL, 1);
+            Bits.BitPermuteStep2(ref q5, ref q4, 0x5555555555555555UL, 1);
+            Bits.BitPermuteStep2(ref q7, ref q6, 0x5555555555555555UL, 1);
+
+            Bits.BitPermuteStep2(ref q2, ref q0, 0x3333333333333333UL, 2);
+            Bits.BitPermuteStep2(ref q3, ref q1, 0x3333333333333333UL, 2);
+            Bits.BitPermuteStep2(ref q6, ref q4, 0x3333333333333333UL, 2);
+            Bits.BitPermuteStep2(ref q7, ref q5, 0x3333333333333333UL, 2);
+
+            Bits.BitPermuteStep2(ref q4, ref q0, 0x0F0F0F0F0F0F0F0FUL, 4);
+            Bits.BitPermuteStep2(ref q5, ref q1, 0x0F0F0F0F0F0F0F0FUL, 4);
+            Bits.BitPermuteStep2(ref q6, ref q2, 0x0F0F0F0F0F0F0F0FUL, 4);
+            Bits.BitPermuteStep2(ref q7, ref q3, 0x0F0F0F0F0F0F0F0FUL, 4);
+
+            q[0] = q0; q[1] = q1; q[2] = q2; q[3] = q3; q[4] = q4; q[5] = q5; q[6] = q6; q[7] = q7;
+        }
+
+        private static void BrAesCtOrtho(uint[] q)
+        {
+            uint q0 = q[0], q1 = q[1], q2 = q[2], q3 = q[3], q4 = q[4], q5 = q[5], q6 = q[6], q7 = q[7];
+
+            Bits.BitPermuteStep2(ref q1, ref q0, 0x55555555U, 1);
+            Bits.BitPermuteStep2(ref q3, ref q2, 0x55555555U, 1);
+            Bits.BitPermuteStep2(ref q5, ref q4, 0x55555555U, 1);
+            Bits.BitPermuteStep2(ref q7, ref q6, 0x55555555U, 1);
+
+            Bits.BitPermuteStep2(ref q2, ref q0, 0x33333333U, 2);
+            Bits.BitPermuteStep2(ref q3, ref q1, 0x33333333U, 2);
+            Bits.BitPermuteStep2(ref q6, ref q4, 0x33333333U, 2);
+            Bits.BitPermuteStep2(ref q7, ref q5, 0x33333333U, 2);
+
+            Bits.BitPermuteStep2(ref q4, ref q0, 0x0F0F0F0FU, 4);
+            Bits.BitPermuteStep2(ref q5, ref q1, 0x0F0F0F0FU, 4);
+            Bits.BitPermuteStep2(ref q6, ref q2, 0x0F0F0F0FU, 4);
+            Bits.BitPermuteStep2(ref q7, ref q3, 0x0F0F0F0FU, 4);
+
+            q[0] = q0; q[1] = q1; q[2] = q2; q[3] = q3; q[4] = q4; q[5] = q5; q[6] = q6; q[7] = q7;
+        }
+
+        private static void BrAesCt64BitsliceSbox(ulong[] q)
+        {
+            /*
+             * This S-box implementation is a straightforward translation of
+             * the circuit described by Boyar and Peralta in "A new
+             * combinational logic minimization technique with applications
+             * to cryptology" (https://eprint.iacr.org/2009/191.pdf).
+             *
+             * Note that variables x* (input) and s* (output) are numbered
+             * in "reverse" order (x0 is the high bit, x7 is the low bit).
+             */
+
+            ulong x0, x1, x2, x3, x4, x5, x6, x7;
+            ulong y1, y2, y3, y4, y5, y6, y7, y8, y9;
+            ulong y10, y11, y12, y13, y14, y15, y16, y17, y18, y19;
+            ulong y20, y21;
+            ulong z0, z1, z2, z3, z4, z5, z6, z7, z8, z9;
+            ulong z10, z11, z12, z13, z14, z15, z16, z17;
+            ulong t0, t1, t2, t3, t4, t5, t6, t7, t8, t9;
+            ulong t10, t11, t12, t13, t14, t15, t16, t17, t18, t19;
+            ulong t20, t21, t22, t23, t24, t25, t26, t27, t28, t29;
+            ulong t30, t31, t32, t33, t34, t35, t36, t37, t38, t39;
+            ulong t40, t41, t42, t43, t44, t45, t46, t47, t48, t49;
+            ulong t50, t51, t52, t53, t54, t55, t56, t57, t58, t59;
+            ulong t60, t61, t62, t63, t64, t65, t66, t67;
+            ulong s0, s1, s2, s3, s4, s5, s6, s7;
+
+            x0 = q[7];
+            x1 = q[6];
+            x2 = q[5];
+            x3 = q[4];
+            x4 = q[3];
+            x5 = q[2];
+            x6 = q[1];
+            x7 = q[0];
+
+            /*
+             * Top linear transformation.
+             */
+            y14 = x3 ^ x5;
+            y13 = x0 ^ x6;
+            y9 = x0 ^ x3;
+            y8 = x0 ^ x5;
+            t0 = x1 ^ x2;
+            y1 = t0 ^ x7;
+            y4 = y1 ^ x3;
+            y12 = y13 ^ y14;
+            y2 = y1 ^ x0;
+            y5 = y1 ^ x6;
+            y3 = y5 ^ y8;
+            t1 = x4 ^ y12;
+            y15 = t1 ^ x5;
+            y20 = t1 ^ x1;
+            y6 = y15 ^ x7;
+            y10 = y15 ^ t0;
+            y11 = y20 ^ y9;
+            y7 = x7 ^ y11;
+            y17 = y10 ^ y11;
+            y19 = y10 ^ y8;
+            y16 = t0 ^ y11;
+            y21 = y13 ^ y16;
+            y18 = x0 ^ y16;
+
+            /*
+             * Non-linear section.
+             */
+            t2 = y12 & y15;
+            t3 = y3 & y6;
+            t4 = t3 ^ t2;
+            t5 = y4 & x7;
+            t6 = t5 ^ t2;
+            t7 = y13 & y16;
+            t8 = y5 & y1;
+            t9 = t8 ^ t7;
+            t10 = y2 & y7;
+            t11 = t10 ^ t7;
+            t12 = y9 & y11;
+            t13 = y14 & y17;
+            t14 = t13 ^ t12;
+            t15 = y8 & y10;
+            t16 = t15 ^ t12;
+            t17 = t4 ^ t14;
+            t18 = t6 ^ t16;
+            t19 = t9 ^ t14;
+            t20 = t11 ^ t16;
+            t21 = t17 ^ y20;
+            t22 = t18 ^ y19;
+            t23 = t19 ^ y21;
+            t24 = t20 ^ y18;
+
+            t25 = t21 ^ t22;
+            t26 = t21 & t23;
+            t27 = t24 ^ t26;
+            t28 = t25 & t27;
+            t29 = t28 ^ t22;
+            t30 = t23 ^ t24;
+            t31 = t22 ^ t26;
+            t32 = t31 & t30;
+            t33 = t32 ^ t24;
+            t34 = t23 ^ t33;
+            t35 = t27 ^ t33;
+            t36 = t24 & t35;
+            t37 = t36 ^ t34;
+            t38 = t27 ^ t36;
+            t39 = t29 & t38;
+            t40 = t25 ^ t39;
+
+            t41 = t40 ^ t37;
+            t42 = t29 ^ t33;
+            t43 = t29 ^ t40;
+            t44 = t33 ^ t37;
+            t45 = t42 ^ t41;
+            z0 = t44 & y15;
+            z1 = t37 & y6;
+            z2 = t33 & x7;
+            z3 = t43 & y16;
+            z4 = t40 & y1;
+            z5 = t29 & y7;
+            z6 = t42 & y11;
+            z7 = t45 & y17;
+            z8 = t41 & y10;
+            z9 = t44 & y12;
+            z10 = t37 & y3;
+            z11 = t33 & y4;
+            z12 = t43 & y13;
+            z13 = t40 & y5;
+            z14 = t29 & y2;
+            z15 = t42 & y9;
+            z16 = t45 & y14;
+            z17 = t41 & y8;
+
+            /*
+             * Bottom linear transformation.
+             */
+            t46 = z15 ^ z16;
+            t47 = z10 ^ z11;
+            t48 = z5 ^ z13;
+            t49 = z9 ^ z10;
+            t50 = z2 ^ z12;
+            t51 = z2 ^ z5;
+            t52 = z7 ^ z8;
+            t53 = z0 ^ z3;
+            t54 = z6 ^ z7;
+            t55 = z16 ^ z17;
+            t56 = z12 ^ t48;
+            t57 = t50 ^ t53;
+            t58 = z4 ^ t46;
+            t59 = z3 ^ t54;
+            t60 = t46 ^ t57;
+            t61 = z14 ^ t57;
+            t62 = t52 ^ t58;
+            t63 = t49 ^ t58;
+            t64 = z4 ^ t59;
+            t65 = t61 ^ t62;
+            t66 = z1 ^ t63;
+            s0 = t59 ^ t63;
+            s6 = t56 ^ ~t62;
+            s7 = t48 ^ ~t60;
+            t67 = t64 ^ t65;
+            s3 = t53 ^ t66;
+            s4 = t51 ^ t66;
+            s5 = t47 ^ t65;
+            s1 = t64 ^ ~s3;
+            s2 = t55 ^ ~t67;
+
+            q[7] = s0;
+            q[6] = s1;
+            q[5] = s2;
+            q[4] = s3;
+            q[3] = s4;
+            q[2] = s5;
+            q[1] = s6;
+            q[0] = s7;
+        }
+
+        private static void ShiftRows(ulong[] q)
+        {
+            for (int i = 0; i < 8; i++)
+            {
+                ulong x = Bits.BitPermuteStep(q[i], 0x00F0_00FF_000F_0000UL, 8);
+                q[i]    = Bits.BitPermuteStep(x   , 0x0F0F_0000_0F0F_0000UL, 4);
+            }
+        }
+
+        private static void MixColumns(ulong[] q)
+        {
+            ulong q0 = q[0], r0 = Longs.RotateRight(q0, 16), s0 = q0 ^ r0;
+            ulong q1 = q[1], r1 = Longs.RotateRight(q1, 16), s1 = q1 ^ r1;
+            ulong q2 = q[2], r2 = Longs.RotateRight(q2, 16), s2 = q2 ^ r2;
+            ulong q3 = q[3], r3 = Longs.RotateRight(q3, 16), s3 = q3 ^ r3;
+            ulong q4 = q[4], r4 = Longs.RotateRight(q4, 16), s4 = q4 ^ r4;
+            ulong q5 = q[5], r5 = Longs.RotateRight(q5, 16), s5 = q5 ^ r5;
+            ulong q6 = q[6], r6 = Longs.RotateRight(q6, 16), s6 = q6 ^ r6;
+            ulong q7 = q[7], r7 = Longs.RotateRight(q7, 16), s7 = q7 ^ r7;
+
+            q[0] = r0       ^ s7 ^ Longs.RotateRight(s0, 32); 
+            q[1] = r1 ^ s0  ^ s7 ^ Longs.RotateRight(s1, 32); 
+            q[2] = r2 ^ s1       ^ Longs.RotateRight(s2, 32);
+            q[3] = r3 ^ s2  ^ s7 ^ Longs.RotateRight(s3, 32);
+            q[4] = r4 ^ s3  ^ s7 ^ Longs.RotateRight(s4, 32);
+            q[5] = r5 ^ s4       ^ Longs.RotateRight(s5, 32);
+            q[6] = r6 ^ s5       ^ Longs.RotateRight(s6, 32);
+            q[7] = r7 ^ s6       ^ Longs.RotateRight(s7, 32);
+        }
+
+        private static void AddRoundKey(ulong[] q, ulong[] sk)
+        {
+            q[0] ^= sk[0];
+            q[1] ^= sk[1];
+            q[2] ^= sk[2];
+            q[3] ^= sk[3];
+            q[4] ^= sk[4];
+            q[5] ^= sk[5];
+            q[6] ^= sk[6];
+            q[7] ^= sk[7];
+        }
+
+        private static void BrAesCt64InterleaveOut(uint[] w, ulong[] q, int pos)
+        {
+            ulong x0, x1, x2, x3;
+
+            x0 = q[pos] & 0x00FF00FF00FF00FFL;
+            x1 = q[pos + 4] & 0x00FF00FF00FF00FFL;
+            x2 = (q[pos] >> 8) & 0x00FF00FF00FF00FFL;
+            x3 = (q[pos + 4] >> 8) & 0x00FF00FF00FF00FFL;
+            x0 |= (x0 >> 8);
+            x1 |= (x1 >> 8);
+            x2 |= (x2 >> 8);
+            x3 |= (x3 >> 8);
+            x0 &= 0x0000FFFF0000FFFFL;
+            x1 &= 0x0000FFFF0000FFFFL;
+            x2 &= 0x0000FFFF0000FFFFL;
+            x3 &= 0x0000FFFF0000FFFFL;
+            pos <<= 2;
+            w[pos] = (uint)(x0 | (x0 >> 16));
+            w[pos + 1] = (uint)(x1 | (x1 >> 16));
+            w[pos + 2] = (uint)(x2 | (x2 >> 16));
+            w[pos + 3] = (uint)(x3 | (x3 >> 16));
+        }
+
+        protected static void Xor(byte[] x, int xOff, byte[] y, int yOff, byte[] z, int zOff, int zLen)
+        {
+            for (int i = 0; i < zLen; i++)
+            {
+                z[zOff + i] = (byte)(x[xOff + i] ^ y[yOff + i]);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected static void Xor(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y, Span<byte> z)
+        {
+            for (int i = 0; i < z.Length; i++)
+            {
+                z[i] = (byte)(x[i] ^ y[i]);
+            }
+        }
+#endif
+    }
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs
new file mode 100644
index 000000000..beff653a7
--- /dev/null
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs
@@ -0,0 +1,134 @@
+using System;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using Org.BouncyCastle.Utilities;
+#endif
+
+namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
+{
+    internal sealed class HarakaSXof
+        : HarakaSBase
+    {
+        public string AlgorithmName => "Haraka-S";
+
+        public HarakaSXof(byte[] pkSeed)
+        {
+            byte[] buf = new byte[640];
+            BlockUpdate(pkSeed, 0, pkSeed.Length);
+            OutputFinal(buf, 0, buf.Length);
+            haraka512_rc = new ulong[10][];
+            haraka256_rc = new uint[10][];
+            for (int i = 0; i < 10; ++i)
+            {
+                haraka512_rc[i] = new ulong[8];
+                haraka256_rc[i] = new uint[8];
+                InterleaveConstant32(haraka256_rc[i], buf, i << 5);
+                InterleaveConstant(haraka512_rc[i], buf, i << 6);
+            }
+        }
+
+        public void Update(byte input)
+        {
+            buffer[off++] ^= input;
+            if (off == 32)
+            {
+                Haraka512Perm(buffer);
+                off = 0;
+            }
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int len)
+        {
+            int i = inOff, loop = (len + off) >> 5;
+            for (int j = 0; j < loop; ++j)
+            {
+                while (off < 32)
+                {
+                    buffer[off++] ^= input[i++];
+                }
+                Haraka512Perm(buffer);
+                off = 0;
+            }
+            while (i < inOff + len)
+            {
+                buffer[off++] ^= input[i++];
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int len = input.Length;
+            int i = 0, loop = (len + off) >> 5;
+            for (int j = 0; j < loop; ++j)
+            {
+                while (off < 32)
+                {
+                    buffer[off++] ^= input[i++];
+                }
+                Haraka512Perm(buffer);
+                off = 0;
+            }
+            while (i < len)
+            {
+                buffer[off++] ^= input[i++];
+            }
+        }
+#endif
+
+        public int OutputFinal(byte[] output, int outOff, int len)
+        {
+            int outLen = len;
+
+            //Finalize
+            buffer[off] ^= 0x1F;
+            buffer[31] ^= 128;
+
+            //Squeeze
+            while (len >= 32)
+            {
+                Haraka512Perm(buffer);
+                Array.Copy(buffer, 0, output, outOff, 32);
+                outOff += 32;
+                len -= 32;
+            }
+            if (len > 0)
+            {
+                Haraka512Perm(buffer);
+                Array.Copy(buffer, 0, output, outOff, len);
+            }
+
+            Reset();
+
+            return outLen;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int OutputFinal(Span<byte> output)
+        {
+            int outLen = output.Length;
+
+            //Finalize
+            buffer[off] ^= 0x1F;
+            buffer[31] ^= 128;
+
+            //Squeeze
+            while (output.Length >= 32)
+            {
+                Haraka512Perm(buffer);
+                output[..32].CopyFrom(buffer);
+                output = output[32..];
+            }
+            if (!output.IsEmpty)
+            {
+                Haraka512Perm(buffer);
+                output.CopyFrom(buffer);
+            }
+
+            Reset();
+
+            return outLen;
+        }
+#endif
+    }
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs
new file mode 100644
index 000000000..87681c484
--- /dev/null
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs
@@ -0,0 +1,209 @@
+#if NETCOREAPP3_0_OR_GREATER
+using System;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
+{
+    internal class HarakaS_X86
+        : IXof
+    {
+        public static bool IsSupported => Haraka512_X86.IsSupported;
+
+        private enum State { Absorbing, Squeezing };
+
+        private readonly Vector128<byte>[] m_roundConstants = new Vector128<byte>[40];
+
+        private readonly byte[] m_buf = new byte[64];
+        private int m_bufPos = 0;
+        private State m_state = State.Absorbing;
+
+        internal HarakaS_X86(ReadOnlySpan<byte> pkSeed)
+        {
+            if (!IsSupported)
+                throw new PlatformNotSupportedException(nameof(HarakaS_X86));
+
+            // Absorb PKSeed
+            Span<byte> buf = stackalloc byte[64];
+            while (pkSeed.Length >= 32)
+            {
+                XorWith(pkSeed[..32], buf);
+                Haraka512_X86.Permute(buf, buf);
+                pkSeed = pkSeed[32..];
+            }
+            XorWith(pkSeed, buf);
+            buf[pkSeed.Length] ^= 0x1F;
+            buf[           31] ^= 0x80;
+
+            // Squeeze round constants
+            int rc = 0;
+            while (rc < 40)
+            {
+                Haraka512_X86.Permute(buf, buf);
+                m_roundConstants[rc++] = Load128(buf[  ..16]);
+                m_roundConstants[rc++] = Load128(buf[16..32]);
+            }
+        }
+
+        internal ReadOnlySpan<Vector128<byte>> RoundConstants => m_roundConstants;
+
+        public string AlgorithmName => "HarakaS";
+
+        public int GetDigestSize() => 32;
+
+        public int GetByteLength() => 32;
+
+        public void Update(byte input)
+        {
+            if (m_state != State.Absorbing)
+                throw new InvalidOperationException();
+
+            m_buf[m_bufPos++] ^= input;
+            if (m_bufPos == 32)
+            {
+                Haraka512_X86.Permute(m_buf, m_buf, m_roundConstants);
+                m_bufPos = 0;
+            }
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            BlockUpdate(input.AsSpan(inOff, inLen));
+        }
+
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (m_state != State.Absorbing)
+                throw new InvalidOperationException();
+
+            int available = 32 - m_bufPos;
+            if (input.Length < available)
+            {
+                XorWith(input, m_buf.AsSpan(m_bufPos));
+                m_bufPos += input.Length;
+                return;
+            }
+
+            XorWith(input[..available], m_buf.AsSpan(m_bufPos));
+            input = input[available..];
+            Haraka512_X86.Permute(m_buf, m_buf, m_roundConstants);
+
+            while (input.Length >= 32)
+            {
+                XorWith(input[..32], m_buf);
+                input = input[32..];
+                Haraka512_X86.Permute(m_buf, m_buf, m_roundConstants);
+            }
+
+            XorWith(input, m_buf);
+            m_bufPos = input.Length;
+        }
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+            return OutputFinal(output.AsSpan(outOff, 32));
+        }
+
+        public int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..32]);
+        }
+
+        public int Output(byte[] output, int outOff, int outLen)
+        {
+            return Output(output.AsSpan(outOff, outLen));
+        }
+
+        public int Output(Span<byte> output)
+        {
+            int result = output.Length;
+
+            if (m_state != State.Squeezing)
+            {
+                m_buf[m_bufPos] ^= 0x1F;
+                m_buf[31] ^= 0x80;
+                m_bufPos = 32;
+                m_state = State.Squeezing;
+
+                if (output.IsEmpty)
+                    return result;
+            }
+            else
+            {
+                int available = 32 - m_bufPos;
+                if (output.Length <= available)
+                {
+                    output.CopyFrom(m_buf.AsSpan(m_bufPos));
+                    m_bufPos += available;
+                    return result;
+                }
+
+                output[..available].CopyFrom(m_buf.AsSpan(m_bufPos));
+                output = output[available..];
+            }
+
+            Debug.Assert(!output.IsEmpty);
+
+            while (output.Length > 32)
+            {
+                Haraka512_X86.Permute(m_buf, m_buf, m_roundConstants);
+                output[..32].CopyFrom(m_buf);
+                output = output[32..];
+            }
+
+            Haraka512_X86.Permute(m_buf, m_buf, m_roundConstants);
+            output.CopyFrom(m_buf);
+            m_bufPos = output.Length;
+
+            return result;
+        }
+
+        public int OutputFinal(byte[] output, int outOff, int outLen)
+        {
+            return OutputFinal(output.AsSpan(outOff, outLen));
+        }
+
+        public int OutputFinal(Span<byte> output)
+        {
+            int result = Output(output);
+            Reset();
+            return result;
+        }
+
+        public void Reset()
+        {
+            Array.Clear(m_buf);
+            m_bufPos = 0;
+            m_state = State.Absorbing;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Vector128<byte> Load128(ReadOnlySpan<byte> t)
+        {
+            if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<byte>>() == 16)
+                return MemoryMarshal.Read<Vector128<byte>>(t);
+
+            return Vector128.Create(
+                BinaryPrimitives.ReadUInt64LittleEndian(t[..8]),
+                BinaryPrimitives.ReadUInt64LittleEndian(t[8..])
+            ).AsByte();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void XorWith(ReadOnlySpan<byte> x, Span<byte> z)
+        {
+            for (int i = 0; i < x.Length; i++)
+            {
+                z[i] ^= x[i];
+            }
+        }
+    }
+}
+#endif
diff --git a/crypto/src/pqc/crypto/sphincsplus/IndexedDigest.cs b/crypto/src/pqc/crypto/sphincsplus/IndexedDigest.cs
index 61ea81c9f..6028ea2d9 100644
--- a/crypto/src/pqc/crypto/sphincsplus/IndexedDigest.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/IndexedDigest.cs
@@ -1,6 +1,6 @@
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class IndexedDigest
+    internal class IndexedDigest
     {
         internal ulong idx_tree;
         internal uint idx_leaf;
@@ -13,4 +13,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             this.digest = digest;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/NodeEntry.cs b/crypto/src/pqc/crypto/sphincsplus/NodeEntry.cs
index 62713f683..d3175349b 100644
--- a/crypto/src/pqc/crypto/sphincsplus/NodeEntry.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/NodeEntry.cs
@@ -1,9 +1,9 @@
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class NodeEntry
+    internal class NodeEntry
     {
-        internal byte[] nodeValue;
-        internal uint nodeHeight;
+        internal readonly byte[] nodeValue;
+        internal readonly uint nodeHeight;
 
         internal NodeEntry(byte[] nodeValue, uint nodeHeight)
         {
@@ -11,4 +11,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             this.nodeHeight = nodeHeight;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/PK.cs b/crypto/src/pqc/crypto/sphincsplus/PK.cs
index 8e97d9adb..3a5723de3 100644
--- a/crypto/src/pqc/crypto/sphincsplus/PK.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/PK.cs
@@ -1,6 +1,6 @@
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class PK
+    internal class PK
     {
         internal byte[] seed;
         internal byte[] root;
@@ -11,4 +11,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             this.root = root;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SIG.cs b/crypto/src/pqc/crypto/sphincsplus/SIG.cs
index ee6234985..2fc375fe6 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SIG.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SIG.cs
@@ -2,7 +2,7 @@ using System;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class SIG
+    internal class SIG
     {
         private byte[] r;
         private SIG_FORS[] sig_fors;
@@ -49,16 +49,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             }
 
             if (offset != signature.Length)
-            {
                 throw new ArgumentException("signature wrong length");
-            }
         }
 
         public byte[] R => r;
 
         public SIG_FORS[] SIG_FORS => sig_fors;
-        
 
         public SIG_XMSS[] SIG_HT => sig_ht;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SIG_FORS.cs b/crypto/src/pqc/crypto/sphincsplus/SIG_FORS.cs
index 4760e9ca9..f052d4220 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SIG_FORS.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SIG_FORS.cs
@@ -1,6 +1,6 @@
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class SIG_FORS
+    internal class SIG_FORS
     {
         internal byte[][] authPath;
         internal byte[] sk;
@@ -15,4 +15,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         public byte[][] AuthPath => authPath;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SIG_XMSS.cs b/crypto/src/pqc/crypto/sphincsplus/SIG_XMSS.cs
index 6df86aac7..4a0a8001d 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SIG_XMSS.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SIG_XMSS.cs
@@ -1,24 +1,18 @@
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class SIG_XMSS
+    internal class SIG_XMSS
     {
         internal byte[] sig;
         internal byte[][] auth;
 
-        public SIG_XMSS(byte[] sig, byte[][] auth)
+        internal SIG_XMSS(byte[] sig, byte[][] auth)
         {
             this.sig = sig;
             this.auth = auth;
         }
 
-        public byte[] GetWOTSSig()
-        {
-            return sig;
-        }
+        internal byte[] WotsSig => sig;
 
-        public byte[][] GetXMSSAUTH()
-        {
-            return auth;
-        }
+        internal byte[][] XmssAuth => auth;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SK.cs b/crypto/src/pqc/crypto/sphincsplus/SK.cs
index 5fb3d0839..86eefa110 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SK.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SK.cs
@@ -1,6 +1,6 @@
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class SK
+    internal class SK
     {
         internal byte[] seed;
         internal byte[] prf;
@@ -11,4 +11,4 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             this.prf = prf;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs
index f477608a6..72fd471d6 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs
@@ -10,7 +10,7 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    abstract class SPHINCSPlusEngine
+    internal abstract class SphincsPlusEngine
     {
         bool robust;
 
@@ -30,18 +30,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         internal uint T; // T = 1 << A
 
-        protected static byte[] xor(byte[] m, byte[] mask)
-        {
-            byte[] r = Arrays.Clone(m);
-            for (int t = 0; t < m.Length; t++)
-            {
-                r[t] ^= mask[t];
-            }
-
-            return r;
-        }
-
-        public SPHINCSPlusEngine(bool robust, int n, uint w, uint d, int a, int k, uint h)
+        public SphincsPlusEngine(bool robust, int n, uint w, uint d, int a, int k, uint h)
         {
             this.N = n;
 
@@ -101,22 +90,35 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             this.T = 1U << a;
         }
 
+        public abstract void Init(byte[] pkSeed);
+
         public abstract byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1);
 
-        public abstract byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void F(byte[] pkSeed, Adrs adrs, Span<byte> m1);
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output);
+#else
+        public abstract void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output);
+#endif
 
         public abstract IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message);
 
-        public abstract byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output);
+#else
+        public abstract void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output);
+#endif
 
-        public abstract byte[] PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs);
+        public abstract void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff);
 
         public abstract byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] message);
 
         internal class Sha2Engine
-            : SPHINCSPlusEngine
+            : SphincsPlusEngine
         {
-            private byte[] padding = new byte[128];
             private HMac treeHMac;
             private Mgf1BytesGenerator mgf1;
             private byte[] hmacBuf;
@@ -126,6 +128,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             private IDigest sha256;
             private byte[] sha256Buf;
 
+            private IMemoable msgMemo;
+            private IMemoable sha256Memo;
+
             public Sha2Engine(bool robust, int n, uint w, uint d, int a, int k, uint h)
                 : base(robust, n, w, d, a, k, h)
             {
@@ -151,6 +156,21 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 this.msgDigestBuf = new byte[msgDigest.GetDigestSize()];
             }
 
+            public override void Init(byte[] pkSeed)
+            {
+                byte[] padding = new byte[bl];
+
+                msgDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
+                msgDigest.BlockUpdate(padding, 0, bl - N);
+                msgMemo = ((IMemoable)msgDigest).Copy();
+                msgDigest.Reset();
+
+                sha256.BlockUpdate(pkSeed, 0, pkSeed.Length);
+                sha256.BlockUpdate(padding, 0, 64 - N);
+                sha256Memo = ((IMemoable)sha256).Copy();
+                sha256.Reset();
+            }
+
             public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1)
             {
                 byte[] compressedAdrs = CompressedAdrs(adrs);
@@ -160,8 +180,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     m1 = Bitmask256(Arrays.Concatenate(pkSeed, compressedAdrs), m1);
                 }
 
-                sha256.BlockUpdate(pkSeed, 0, pkSeed.Length);
-                sha256.BlockUpdate(padding, 0, 64 - pkSeed.Length); // toByte(0, 64 - n)
+                ((IMemoable)sha256).Reset(sha256Memo);
+
                 sha256.BlockUpdate(compressedAdrs, 0, compressedAdrs.Length);
                 sha256.BlockUpdate(m1, 0, m1.Length);
                 sha256.DoFinal(sha256Buf, 0);
@@ -169,13 +189,59 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return Arrays.CopyOfRange(sha256Buf, 0, N);
             }
 
-            public override byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
             {
                 byte[] compressedAdrs = CompressedAdrs(adrs);
-                
 
-                msgDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
-                msgDigest.BlockUpdate(padding, 0, bl - N); // toByte(0, 64 - n)
+                ((IMemoable)sha256).Reset(sha256Memo);
+
+                sha256.BlockUpdate(compressedAdrs);
+
+                if (robust)
+                {
+                    sha256.BlockUpdate(Bitmask256(Arrays.Concatenate(pkSeed, compressedAdrs), m1));
+                }
+                else
+                {
+                    sha256.BlockUpdate(m1);
+                }
+
+                sha256.DoFinal(sha256Buf);
+                m1.CopyFrom(sha256Buf);
+            }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                byte[] compressedAdrs = CompressedAdrs(adrs);
+
+                ((IMemoable)msgDigest).Reset(msgMemo);
+
+                msgDigest.BlockUpdate(compressedAdrs);
+                if (robust)
+                {
+                    byte[] m1m2 = Bitmask(Arrays.Concatenate(pkSeed, compressedAdrs), m1, m2);
+                    msgDigest.BlockUpdate(m1m2);
+                }
+                else
+                {
+                    msgDigest.BlockUpdate(m1);
+                    msgDigest.BlockUpdate(m2);
+                }
+
+                msgDigest.DoFinal(msgDigestBuf);
+
+                output[..N].CopyFrom(msgDigestBuf);
+            }
+#else
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output)
+            {
+                byte[] compressedAdrs = CompressedAdrs(adrs);
+
+                ((IMemoable)msgDigest).Reset(msgMemo);
+
                 msgDigest.BlockUpdate(compressedAdrs, 0, compressedAdrs.Length);
                 if (robust)
                 {
@@ -188,12 +254,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     msgDigest.BlockUpdate(m2, 0, m2.Length);
                 }
 
-                
-                
                 msgDigest.DoFinal(msgDigestBuf, 0);
 
-                return Arrays.CopyOfRange(msgDigestBuf, 0, N);
+                Array.Copy(msgDigestBuf, 0, output, 0, N);
             }
+#endif
 
             public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message)
             {
@@ -202,38 +267,34 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 uint treeBits = FH - leafBits;
                 uint leafBytes = (leafBits + 7) / 8;
                 uint treeBytes = (treeBits + 7) / 8;
-                uint m = (uint) forsMsgBytes + leafBytes + treeBytes;
-                byte[] output = new byte[m];
-                byte[] dig = new byte[msgDigest.GetDigestSize()];
+                uint m = (uint)forsMsgBytes + treeBytes + leafBytes;
 
+                byte[] dig = new byte[msgDigest.GetDigestSize()];
                 msgDigest.BlockUpdate(prf, 0, prf.Length);
                 msgDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 msgDigest.BlockUpdate(pkRoot, 0, pkRoot.Length);
                 msgDigest.BlockUpdate(message, 0, message.Length);
                 msgDigest.DoFinal(dig, 0);
 
-
-                output = Bitmask( Arrays.ConcatenateAll(prf, pkSeed, dig), output);
+                byte[] output = new byte[m];
+                output = Bitmask(Arrays.ConcatenateAll(prf, pkSeed, dig), output);
 
                 // tree index
                 // currently, only indexes up to 64 bits are supported
-                byte[] treeIndexBuf = new byte[8];
-                Array.Copy(output, forsMsgBytes, treeIndexBuf, 8 - treeBytes, treeBytes);
-                ulong treeIndex = Pack.BE_To_UInt64(treeIndexBuf, 0);
-                if(64-treeBits != 0)
-                    treeIndex &= (ulong)((0x7fffffffffffffffL) >> (int)(64 - treeBits - 1));
+                ulong treeIndex = Pack.BE_To_UInt64_Partial(output, forsMsgBytes, (int)treeBytes)
+                                & ulong.MaxValue >> (64 - (int)treeBits);
 
-                byte[] leafIndexBuf = new byte[4];
-                Array.Copy(output, forsMsgBytes + treeBytes, leafIndexBuf, 4 - leafBytes, leafBytes);
-
-                uint leafIndex = Pack.BE_To_UInt32(leafIndexBuf, 0);
-                if(32-leafBits != 0)
-                    leafIndex &= (uint) ((0x7fffffff) >> (int) (32 - leafBits-1));//todo???
+                uint leafIndex = Pack.BE_To_UInt32_Partial(output, forsMsgBytes + (int)treeBytes, (int)leafBytes)
+                               & uint.MaxValue >> (32 - (int)leafBits);
 
                 return new IndexedDigest(treeIndex, leafIndex, Arrays.CopyOfRange(output, 0, forsMsgBytes));
             }
 
-            public override byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+#else
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output)
+#endif
             {
                 byte[] compressedAdrs = CompressedAdrs(adrs);
                 if (robust)
@@ -241,30 +302,32 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     m = Bitmask(Arrays.Concatenate(pkSeed, compressedAdrs), m);
                 }
 
+                ((IMemoable)msgDigest).Reset(msgMemo);
 
-                msgDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
-                msgDigest.BlockUpdate(padding, 0, bl - N); // toByte(0, 64 - n)
                 msgDigest.BlockUpdate(compressedAdrs, 0, compressedAdrs.Length);
                 msgDigest.BlockUpdate(m, 0, m.Length);
                 msgDigest.DoFinal(msgDigestBuf, 0);
 
-                return Arrays.CopyOfRange(msgDigestBuf, 0, N);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                output[..N].CopyFrom(msgDigestBuf);
+#else
+                Array.Copy(msgDigestBuf, 0, output, 0, N);
+#endif
             }
 
-            public override byte[] PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs)
+            public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
             {
                 int n = skSeed.Length;
-                
-                sha256.BlockUpdate(pkSeed, 0, pkSeed.Length);
-                sha256.BlockUpdate(padding, 0, 64 - pkSeed.Length); // toByte(0, 64 - n)
-                
+
+                ((IMemoable)sha256).Reset(sha256Memo);
+
                 byte[] compressedAdrs = CompressedAdrs(adrs);
 
                 sha256.BlockUpdate(compressedAdrs, 0, compressedAdrs.Length);
                 sha256.BlockUpdate(skSeed, 0, skSeed.Length);
                 sha256.DoFinal(sha256Buf, 0);
 
-                return Arrays.CopyOfRange(sha256Buf, 0, n);
+                Array.Copy(sha256Buf, 0, prf, prfOff, n);
             }
 
             public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] message)
@@ -287,7 +350,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
                 return rv;
             }
-            
+
             protected byte[] Bitmask(byte[] key, byte[] m)
             {
                 byte[] mask = new byte[m.Length];
@@ -304,7 +367,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return mask;
             }
 
-
             protected byte[] Bitmask(byte[] key, byte[] m1, byte[] m2)
             {
                 byte[] mask = new byte[m1.Length + m2.Length];
@@ -326,7 +388,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return mask;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            protected byte[] Bitmask256(byte[] key, ReadOnlySpan<byte> m)
+#else
             protected byte[] Bitmask256(byte[] key, byte[] m)
+#endif
             {
                 byte[] mask = new byte[m.Length];
 
@@ -343,11 +409,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
                 return mask;
             }
-
         }
 
         internal class Shake256Engine
-            : SPHINCSPlusEngine
+            : SphincsPlusEngine
         {
             private IXof treeDigest;
             private IXof maskDigest;
@@ -355,11 +420,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             public Shake256Engine(bool robust, int n, uint w, uint d, int a, int k, uint h)
                 : base(robust, n, w, d, a, k, h)
             {
-
                 this.treeDigest = new ShakeDigest(256);
                 this.maskDigest = new ShakeDigest(256);
             }
 
+            public override void Init(byte[] pkSeed)
+            {
+                // TODO: add use of memo
+            }
+
             public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1)
             {
                 byte[] mTheta = m1;
@@ -369,18 +438,50 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 }
 
                 byte[] rv = new byte[N];
-
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 treeDigest.BlockUpdate(mTheta, 0, mTheta.Length);
-                treeDigest.DoFinal(rv, 0, rv.Length);
-
+                treeDigest.OutputFinal(rv, 0, rv.Length);
                 return rv;
             }
 
-            public override byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                if (robust)
+                {
+                    Bitmask(pkSeed, adrs, m1);
+                }
+
+                treeDigest.BlockUpdate(pkSeed);
+                treeDigest.BlockUpdate(adrs.value);
+                treeDigest.BlockUpdate(m1);
+                treeDigest.OutputFinal(m1);
+            }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                treeDigest.BlockUpdate(pkSeed);
+                treeDigest.BlockUpdate(adrs.value);
+
+                if (robust)
+                {
+                    byte[] m1m2 = Bitmask(pkSeed, adrs, m1, m2);
+                    treeDigest.BlockUpdate(m1m2);
+                }
+                else
+                {
+                    treeDigest.BlockUpdate(m1);
+                    treeDigest.BlockUpdate(m2);
+                }
+
+                treeDigest.OutputFinal(output[..N]);
+            }
+#else
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output)
             {
-                byte[] rv = new byte[N];
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
 
@@ -395,11 +496,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     treeDigest.BlockUpdate(m1, 0, m1.Length);
                     treeDigest.BlockUpdate(m2, 0, m2.Length);
                 }
-                
-                treeDigest.DoFinal(rv, 0, rv.Length);
 
-                return rv;
+                treeDigest.OutputFinal(output, 0, N);
             }
+#endif
 
             public override IndexedDigest H_msg(byte[] R, byte[] pkSeed, byte[] pkRoot, byte[] message)
             {
@@ -408,36 +508,31 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 uint treeBits = FH - leafBits;
                 uint leafBytes = (leafBits + 7) / 8;
                 uint treeBytes = (treeBits + 7) / 8;
-                uint m = (uint)(forsMsgBytes + leafBytes + treeBytes);
+                uint m = (uint)(forsMsgBytes + treeBytes + leafBytes);
                 byte[] output = new byte[m];
 
-
                 treeDigest.BlockUpdate(R, 0, R.Length);
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(pkRoot, 0, pkRoot.Length);
                 treeDigest.BlockUpdate(message, 0, message.Length);
-
-                treeDigest.DoFinal(output, 0, output.Length);
+                treeDigest.OutputFinal(output, 0, output.Length);
 
                 // tree index
                 // currently, only indexes up to 64 bits are supported
-                byte[] treeIndexBuf = new byte[8];
-                Array.Copy(output, forsMsgBytes, treeIndexBuf, 8 - treeBytes, treeBytes);
-                ulong treeIndex = Pack.BE_To_UInt64(treeIndexBuf, 0);
-                if(64 - treeBits != 0)
-                    treeIndex &= (ulong) ((0x7fffffffffffffffL) >> (64 - (int)treeBits-1));
-
-                byte[] leafIndexBuf = new byte[4];
-                Array.Copy(output, forsMsgBytes + treeBytes, leafIndexBuf, 4 - leafBytes, leafBytes);
+                ulong treeIndex = Pack.BE_To_UInt64_Partial(output, forsMsgBytes, (int)treeBytes)
+                                & ulong.MaxValue >> (64 - (int)treeBits);
 
-                uint leafIndex = Pack.BE_To_UInt32(leafIndexBuf, 0);
-                if (32 - leafBits != 0)
-                    leafIndex &= (uint)((0x7fffffff) >> (32 - (int)leafBits-1));
+                uint leafIndex = Pack.BE_To_UInt32_Partial(output, forsMsgBytes + (int)treeBytes, (int)leafBytes)
+                               & uint.MaxValue >> (32 - (int)leafBits);
 
                 return new IndexedDigest(treeIndex, leafIndex, Arrays.CopyOfRange(output, 0, forsMsgBytes));
             }
 
-            public override byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+#else
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output)
+#endif
             {
                 byte[] mTheta = m;
                 if (robust)
@@ -445,25 +540,22 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     mTheta = Bitmask(pkSeed, adrs, m);
                 }
 
-                byte[] rv = new byte[N];
-
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 treeDigest.BlockUpdate(mTheta, 0, mTheta.Length);
-                treeDigest.DoFinal(rv, 0, rv.Length);
-
-                return rv;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                treeDigest.OutputFinal(output[..N]);
+#else
+                treeDigest.OutputFinal(output, 0, N);
+#endif
             }
 
-            public override byte[] PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs)
+            public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
             {
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 treeDigest.BlockUpdate(skSeed, 0, skSeed.Length);
-                
-                byte[] prf = new byte[N];
-                treeDigest.DoFinal(prf, 0, N);
-                return prf;
+                treeDigest.OutputFinal(prf, prfOff, N);
             }
 
             public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] message)
@@ -472,7 +564,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 treeDigest.BlockUpdate(randomiser, 0, randomiser.Length);
                 treeDigest.BlockUpdate(message, 0, message.Length);
                 byte[] output = new byte[N];
-                treeDigest.DoFinal(output, 0, output.Length);
+                treeDigest.OutputFinal(output, 0, output.Length);
                 return output;
             }
 
@@ -482,8 +574,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
                 maskDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 maskDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
-
-                maskDigest.DoFinal(mask, 0, mask.Length);
+                maskDigest.OutputFinal(mask, 0, mask.Length);
 
                 for (int i = 0; i < m.Length; ++i)
                 {
@@ -492,14 +583,29 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
                 return mask;
             }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            protected void Bitmask(ReadOnlySpan<byte> pkSeed, Adrs adrs, Span<byte> m)
+            {
+                Span<byte> mask = stackalloc byte[m.Length];
+                maskDigest.BlockUpdate(pkSeed);
+                maskDigest.BlockUpdate(adrs.value);
+                maskDigest.OutputFinal(mask);
+
+                for (int i = 0; i < m.Length; ++i)
+                {
+                    m[i] ^= mask[i];
+                }
+            }
+#endif
+
             protected byte[] Bitmask(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
             {
                 byte[] mask = new byte[m1.Length + m2.Length];
 
                 maskDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 maskDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
-
-                maskDigest.DoFinal(mask, 0, mask.Length);
+                maskDigest.OutputFinal(mask, 0, mask.Length);
 
                 for (int i = 0; i < m1.Length; ++i)
                 {
@@ -514,5 +620,341 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             }
 
         }
+
+        internal class HarakaSEngine
+            : SphincsPlusEngine
+        {
+            public HarakaSXof harakaSXof;
+            public HarakaS256Digest harakaS256Digest;
+            public HarakaS512Digest harakaS512Digest;
+
+            public HarakaSEngine(bool robust, int n, uint w, uint d, int a, int k, uint h)
+                : base(robust, n, w, d, a, k, h)
+            {
+
+            }
+
+            public override void Init(byte[] pkSeed)
+            {
+                harakaSXof = new HarakaSXof(pkSeed);
+                harakaS256Digest = new HarakaS256Digest(harakaSXof);
+                harakaS512Digest = new HarakaS512Digest(harakaSXof);
+            }
+
+            public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1)
+            {
+                byte[] hash = new byte[32];
+                harakaS512Digest.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                if (robust)
+                {
+                    harakaS256Digest.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                    harakaS256Digest.DoFinal(hash, 0);
+                    for (int i = 0; i < m1.Length; ++i)
+                    {
+                        hash[i] ^= m1[i];
+                    }
+                    harakaS512Digest.BlockUpdate(hash, 0, m1.Length);
+                }
+                else
+                {
+                    harakaS512Digest.BlockUpdate(m1, 0, m1.Length);
+                }
+                // NOTE The digest implementation implicitly pads the input with zeros up to 64 length
+                harakaS512Digest.DoFinal(hash, 0);
+                return N == 32 ? hash : Arrays.CopyOfRange(hash, 0, N);
+            }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                Span<byte> hash = stackalloc byte[32];
+                if (robust)
+                {
+                    harakaS256Digest.BlockUpdate(adrs.value);
+                    harakaS256Digest.DoFinal(hash);
+                    for (int i = 0; i < m1.Length; ++i)
+                    {
+                        m1[i] ^= hash[i];
+                    }
+                }
+
+                harakaS512Digest.BlockUpdate(adrs.value);
+                harakaS512Digest.BlockUpdate(m1);
+                // NOTE The digest implementation implicitly pads the input with zeros up to 64 length
+                harakaS512Digest.DoFinal(hash);
+                m1.CopyFrom(hash);
+            }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                Span<byte> m = stackalloc byte[m1.Length + m2.Length];
+                m1.CopyTo(m);
+                m2.CopyTo(m[m1.Length..]);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
+                harakaSXof.BlockUpdate(adrs.value);
+                harakaSXof.BlockUpdate(m);
+                harakaSXof.OutputFinal(output[..N]);
+            }
+#else
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output)
+            {
+                byte[] m = new byte[m1.Length + m2.Length];
+                Array.Copy(m1, 0, m, 0, m1.Length);
+                Array.Copy(m2, 0, m, m1.Length, m2.Length);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
+                harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                harakaSXof.BlockUpdate(m, 0, m.Length);
+                harakaSXof.OutputFinal(output, 0, N);
+            }
+#endif
+
+            public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message)
+            {
+                int forsMsgBytes = ((A * K) + 7) >> 3;
+                uint leafBits = FH / D;
+                uint treeBits = FH - leafBits;
+                uint leafBytes = (leafBits + 7) >> 3;
+                uint treeBytes = (treeBits + 7) >> 3;
+
+                byte[] output = new byte[forsMsgBytes + treeBytes + leafBytes];
+                harakaSXof.BlockUpdate(prf, 0, prf.Length);
+                harakaSXof.BlockUpdate(pkRoot, 0, pkRoot.Length);
+                harakaSXof.BlockUpdate(message, 0, message.Length);
+                harakaSXof.OutputFinal(output, 0, output.Length);
+
+                // tree index
+                // currently, only indexes up to 64 bits are supported
+                ulong treeIndex = Pack.BE_To_UInt64_Partial(output, forsMsgBytes, (int)treeBytes)
+                                & ulong.MaxValue >> (64 - (int)treeBits);
+
+                uint leafIndex = Pack.BE_To_UInt32_Partial(output, forsMsgBytes + (int)treeBytes, (int)leafBytes)
+                               & uint.MaxValue >> (32 - (int)leafBits);
+
+                return new IndexedDigest(treeIndex, leafIndex, Arrays.CopyOfRange(output, 0, forsMsgBytes));
+            }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+#else
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output)
+#endif
+            {
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
+                harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                harakaSXof.BlockUpdate(m, 0, m.Length);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                harakaSXof.OutputFinal(output[..N]);
+#else
+                harakaSXof.OutputFinal(output, 0, N);
+#endif
+            }
+
+            public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
+            {
+                byte[] rv = new byte[32];
+                harakaS512Digest.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                harakaS512Digest.BlockUpdate(skSeed, 0, skSeed.Length);
+                harakaS512Digest.DoFinal(rv, 0);
+                Array.Copy(rv, 0, prf, prfOff, N);
+            }
+
+            public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] message)
+            {
+                byte[] rv = new byte[N];
+                harakaSXof.BlockUpdate(prf, 0, prf.Length);
+                harakaSXof.BlockUpdate(randomiser, 0, randomiser.Length);
+                harakaSXof.BlockUpdate(message, 0, message.Length);
+                harakaSXof.OutputFinal(rv, 0, rv.Length);
+                return rv;
+            }
+
+            protected void Bitmask(Adrs adrs, byte[] m)
+            {
+                byte[] mask = new byte[m.Length];
+                harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                harakaSXof.OutputFinal(mask, 0, mask.Length);
+                for (int i = 0; i < m.Length; ++i)
+                {
+                    m[i] ^= mask[i];
+                }
+            }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            protected void Bitmask(Adrs adrs, Span<byte> m)
+            {
+                Span<byte> mask = stackalloc byte[m.Length];
+                harakaSXof.BlockUpdate(adrs.value);
+                harakaSXof.OutputFinal(mask);
+                for (int i = 0; i < m.Length; ++i)
+                {
+                    m[i] ^= mask[i];
+                }
+            }
+#endif
+        }
+
+#if NETCOREAPP3_0_OR_GREATER
+        internal class HarakaSEngine_X86
+            : SphincsPlusEngine
+        {
+            public static bool IsSupported => Haraka256_X86.IsSupported && Haraka512_X86.IsSupported
+                && HarakaS_X86.IsSupported;
+
+            private HarakaS_X86 m_harakaS;
+
+            public HarakaSEngine_X86(bool robust, int n, uint w, uint d, int a, int k, uint h)
+                : base(robust, n, w, d, a, k, h)
+            {
+            }
+
+            public override void Init(byte[] pkSeed)
+            {
+                m_harakaS = new HarakaS_X86(pkSeed);
+            }
+
+            public override byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1)
+            {
+                Span<byte> buf = stackalloc byte[64];
+                adrs.value.CopyTo(buf);
+
+                if (robust)
+                {
+                    Span<byte> mask = stackalloc byte[32];
+                    Haraka256_X86.Hash(adrs.value, mask, m_harakaS.RoundConstants);
+                    for (int i = 0; i < m1.Length; ++i)
+                    {
+                        buf[32 + i] = (byte)(m1[i] ^ mask[i]);
+                    }
+                }
+                else
+                {
+                    m1.CopyTo(buf[32..]);
+                }
+                Haraka512_X86.Hash(buf, buf, m_harakaS.RoundConstants);
+                return buf[..N].ToArray();
+            }
+
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                Span<byte> buf = stackalloc byte[64];
+                adrs.value.CopyTo(buf);
+
+                if (robust)
+                {
+                    Span<byte> mask = stackalloc byte[32];
+                    Haraka256_X86.Hash(adrs.value, mask, m_harakaS.RoundConstants);
+                    for (int i = 0; i < m1.Length; ++i)
+                    {
+                        buf[32 + i] = (byte)(m1[i] ^ mask[i]);
+                    }
+                }
+                else
+                {
+                    m1.CopyTo(buf[32..]);
+                }
+                Haraka512_X86.Hash(buf, buf, m_harakaS.RoundConstants);
+                m1.CopyFrom(buf);
+            }
+
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                Span<byte> m = stackalloc byte[m1.Length + m2.Length];
+                m1.CopyTo(m);
+                m2.CopyTo(m[m1.Length..]);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
+                m_harakaS.BlockUpdate(adrs.value);
+                m_harakaS.BlockUpdate(m);
+                m_harakaS.OutputFinal(output[..N]);
+            }
+
+            public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message)
+            {
+                int forsMsgBytes = ((A * K) + 7) >> 3;
+                int leafBits = (int)(FH / D);
+                int treeBits = (int)FH - leafBits;
+                int leafBytes = (leafBits + 7) >> 3;
+                int treeBytes = (treeBits + 7) >> 3;
+
+                byte[] output = new byte[forsMsgBytes];
+                Span<byte> indices = stackalloc byte[treeBytes + leafBytes];
+
+                m_harakaS.BlockUpdate(prf);
+                m_harakaS.BlockUpdate(pkRoot);
+                m_harakaS.BlockUpdate(message);
+                m_harakaS.Output(output);
+                m_harakaS.OutputFinal(indices);
+
+                // tree index
+                // currently, only indexes up to 64 bits are supported
+                ulong treeIndex = Pack.BE_To_UInt64_Partial(indices[..treeBytes])
+                                & ulong.MaxValue >> (64 - treeBits);
+
+                uint leafIndex = Pack.BE_To_UInt32_Partial(indices[treeBytes..])
+                               & uint.MaxValue >> (32 - leafBits);
+
+                return new IndexedDigest(treeIndex, leafIndex, output);
+            }
+
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+            {
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
+                m_harakaS.BlockUpdate(adrs.value);
+                m_harakaS.BlockUpdate(m);
+                m_harakaS.OutputFinal(output[..N]);
+            }
+
+            public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
+            {
+                Span<byte> buf = stackalloc byte[64];
+                adrs.value.CopyTo(buf);
+                skSeed.CopyTo(buf[32..]);
+                Haraka512_X86.Hash(buf, buf, m_harakaS.RoundConstants);
+                buf[..N].CopyTo(prf.AsSpan(prfOff));
+            }
+
+            public override byte[] PRF_msg(byte[] prf, byte[] randomiser, byte[] message)
+            {
+                byte[] rv = new byte[N];
+                m_harakaS.BlockUpdate(prf);
+                m_harakaS.BlockUpdate(randomiser);
+                m_harakaS.BlockUpdate(message);
+                m_harakaS.OutputFinal(rv);
+                return rv;
+            }
+
+            protected void Bitmask(Adrs adrs, Span<byte> m)
+            {
+                Span<byte> mask = stackalloc byte[m.Length];
+                m_harakaS.BlockUpdate(adrs.value);
+                m_harakaS.OutputFinal(mask);
+                for (int i = 0; i < m.Length; ++i)
+                {
+                    m[i] ^= mask[i];
+                }
+            }
+        }
+#endif
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyGenerationParameters.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyGenerationParameters.cs
index 2239d1162..12339ddc6 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyGenerationParameters.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyGenerationParameters.cs
@@ -3,17 +3,17 @@ using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    public class SPHINCSPlusKeyGenerationParameters
+    public sealed class SphincsPlusKeyGenerationParameters
         : KeyGenerationParameters
     {
-        private SPHINCSPlusParameters parameters;
+        private readonly SphincsPlusParameters m_parameters;
 
-        public SPHINCSPlusKeyGenerationParameters(SecureRandom random, SPHINCSPlusParameters parameters)
+        public SphincsPlusKeyGenerationParameters(SecureRandom random, SphincsPlusParameters parameters)
             : base(random, 256)
         {
-            this.parameters = parameters;
+            m_parameters = parameters;
         }
 
-        internal SPHINCSPlusParameters Parameters => parameters;
+        public SphincsPlusParameters Parameters => m_parameters;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyPairGenerator.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyPairGenerator.cs
index 9e5724027..86493657d 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyPairGenerator.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyPairGenerator.cs
@@ -1,41 +1,61 @@
 
+using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    public class SPHINCSPlusKeyPairGenerator
+    public sealed class SphincsPlusKeyPairGenerator
         : IAsymmetricCipherKeyPairGenerator
     {
         private SecureRandom random;
-        private SPHINCSPlusParameters parameters;
+        private SphincsPlusParameters parameters;
 
         public void Init(KeyGenerationParameters param)
         {
             random = param.Random;
-            parameters = ((SPHINCSPlusKeyGenerationParameters) param).Parameters;
+            parameters = ((SphincsPlusKeyGenerationParameters)param).Parameters;
         }
 
         public AsymmetricCipherKeyPair GenerateKeyPair()
         {
-            SPHINCSPlusEngine engine = parameters.GetEngine();
-
-            SK sk = new SK(SecRand(engine.N), SecRand(engine.N));
-            byte[] pkSeed = SecRand(engine.N);
+            SphincsPlusEngine engine = parameters.GetEngine();
+            byte[] pkSeed;
+            SK sk;
+
+            if (engine is SphincsPlusEngine.HarakaSEngine
+#if NETCOREAPP3_0_OR_GREATER
+                || engine is SphincsPlusEngine.HarakaSEngine_X86
+#endif
+                )
+            {
+                // required to pass kat tests
+                byte[] tmparray = SecRand(engine.N * 3);
+                byte[] skseed = new byte[engine.N];
+                byte[] skprf = new byte[engine.N];
+                pkSeed = new byte[engine.N];
+                Array.Copy(tmparray, 0, skseed, 0, engine.N);
+                Array.Copy(tmparray, engine.N, skprf, 0, engine.N);
+                Array.Copy(tmparray, engine.N << 1, pkSeed, 0, engine.N);
+                sk = new SK(skseed, skprf);
+            }
+            else
+            {
+                sk = new SK(SecRand(engine.N), SecRand(engine.N));
+                pkSeed = SecRand(engine.N);
+            }
+            engine.Init(pkSeed);
             // TODO
             PK pk = new PK(pkSeed, new HT(engine, sk.seed, pkSeed).HTPubKey);
 
-            return new AsymmetricCipherKeyPair(new SPHINCSPlusPublicKeyParameters(parameters, pk),
-                new SPHINCSPlusPrivateKeyParameters(parameters, sk, pk));
+            return new AsymmetricCipherKeyPair(new SphincsPlusPublicKeyParameters(parameters, pk),
+                new SphincsPlusPrivateKeyParameters(parameters, sk, pk));
         }
 
         private byte[] SecRand(int n)
         {
-            byte[] rv = new byte[n];
-
-            random.NextBytes(rv);
-
-            return rv;
+            return SecureRandom.GetNextBytes(random, n);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyParameters.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyParameters.cs
index 8a8edf653..82220f9db 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyParameters.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusKeyParameters.cs
@@ -2,20 +2,17 @@ using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    public class SPHINCSPlusKeyParameters
+    public abstract class SphincsPlusKeyParameters
         : AsymmetricKeyParameter
     {
-        SPHINCSPlusParameters parameters;
+        protected readonly SphincsPlusParameters m_parameters;
 
-        protected SPHINCSPlusKeyParameters(bool isPrivate, SPHINCSPlusParameters parameters)
+        protected SphincsPlusKeyParameters(bool isPrivate, SphincsPlusParameters parameters)
             : base(isPrivate)
         {
-            this.parameters = parameters;
+            m_parameters = parameters;
         }
 
-        public SPHINCSPlusParameters GetParameters()
-        {
-            return parameters;
-        }
+        public SphincsPlusParameters Parameters => m_parameters;
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusParameters.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusParameters.cs
index 3faaca896..8cde7cf7f 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusParameters.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusParameters.cs
@@ -1,95 +1,114 @@
 using System;
 using System.Collections.Generic;
+
 using Org.BouncyCastle.Crypto.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    interface ISPHINCSPlusEngineProvider
+    internal interface ISphincsPlusEngineProvider
     {
-        int N
-        {
-            get;
-        }
+        int N { get; }
 
-        SPHINCSPlusEngine Get();
+        SphincsPlusEngine Get();
     }
 
-    public class SPHINCSPlusParameters
+    public sealed class SphincsPlusParameters
     {
-        public static SPHINCSPlusParameters sha2_128f = new SPHINCSPlusParameters("sha2-128f-robust",
+        public static SphincsPlusParameters sha2_128f = new SphincsPlusParameters("sha2-128f-robust",
             new Sha2EngineProvider(true, 16, 16, 22, 6, 33, 66));
 
-        public static SPHINCSPlusParameters sha2_128s = new SPHINCSPlusParameters("sha2-128s-robust",
+        public static SphincsPlusParameters sha2_128s = new SphincsPlusParameters("sha2-128s-robust",
             new Sha2EngineProvider(true, 16, 16, 7, 12, 14, 63));
 
-        public static SPHINCSPlusParameters sha2_192f = new SPHINCSPlusParameters("sha2-192f-robust",
+        public static SphincsPlusParameters sha2_192f = new SphincsPlusParameters("sha2-192f-robust",
             new Sha2EngineProvider(true, 24, 16, 22, 8, 33, 66));
 
-        public static SPHINCSPlusParameters sha2_192s = new SPHINCSPlusParameters("sha2-192s-robust",
+        public static SphincsPlusParameters sha2_192s = new SphincsPlusParameters("sha2-192s-robust",
             new Sha2EngineProvider(true, 24, 16, 7, 14, 17, 63));
 
-        public static SPHINCSPlusParameters sha2_256f = new SPHINCSPlusParameters("sha2-256f-robust",
+        public static SphincsPlusParameters sha2_256f = new SphincsPlusParameters("sha2-256f-robust",
             new Sha2EngineProvider(true, 32, 16, 17, 9, 35, 68));
 
-        public static SPHINCSPlusParameters sha2_256s = new SPHINCSPlusParameters("sha2-256s-robust",
+        public static SphincsPlusParameters sha2_256s = new SphincsPlusParameters("sha2-256s-robust",
             new Sha2EngineProvider(true, 32, 16, 8, 14, 22, 64));
 
-        public static SPHINCSPlusParameters sha2_128f_simple = new SPHINCSPlusParameters("sha2-128f-simple",
+        public static SphincsPlusParameters sha2_128f_simple = new SphincsPlusParameters("sha2-128f-simple",
             new Sha2EngineProvider(false, 16, 16, 22, 6, 33, 66));
 
-        public static SPHINCSPlusParameters sha2_128s_simple = new SPHINCSPlusParameters("sha2-128s-simple",
+        public static SphincsPlusParameters sha2_128s_simple = new SphincsPlusParameters("sha2-128s-simple",
             new Sha2EngineProvider(false, 16, 16, 7, 12, 14, 63));
 
-        public static SPHINCSPlusParameters sha2_192f_simple = new SPHINCSPlusParameters("sha2-192f-simple",
+        public static SphincsPlusParameters sha2_192f_simple = new SphincsPlusParameters("sha2-192f-simple",
             new Sha2EngineProvider(false, 24, 16, 22, 8, 33, 66));
 
-        public static SPHINCSPlusParameters sha2_192s_simple = new SPHINCSPlusParameters("sha2-192s-simple",
+        public static SphincsPlusParameters sha2_192s_simple = new SphincsPlusParameters("sha2-192s-simple",
             new Sha2EngineProvider(false, 24, 16, 7, 14, 17, 63));
 
-        public static SPHINCSPlusParameters sha2_256f_simple = new SPHINCSPlusParameters("sha2-256f-simple",
+        public static SphincsPlusParameters sha2_256f_simple = new SphincsPlusParameters("sha2-256f-simple",
             new Sha2EngineProvider(false, 32, 16, 17, 9, 35, 68));
 
-        public static SPHINCSPlusParameters sha2_256s_simple = new SPHINCSPlusParameters("sha2-256s-simple",
+        public static SphincsPlusParameters sha2_256s_simple = new SphincsPlusParameters("sha2-256s-simple",
             new Sha2EngineProvider(false, 32, 16, 8, 14, 22, 64));
 
         // SHAKE-256.
 
-        public static SPHINCSPlusParameters shake_128f = new SPHINCSPlusParameters("shake-128f-robust",
+        public static SphincsPlusParameters shake_128f = new SphincsPlusParameters("shake-128f-robust",
             new Shake256EngineProvider(true, 16, 16, 22, 6, 33, 66));
 
-        public static SPHINCSPlusParameters shake_128s = new SPHINCSPlusParameters("shake-128s-robust",
+        public static SphincsPlusParameters shake_128s = new SphincsPlusParameters("shake-128s-robust",
             new Shake256EngineProvider(true, 16, 16, 7, 12, 14, 63));
 
-        public static SPHINCSPlusParameters shake_192f = new SPHINCSPlusParameters("shake-192f-robust",
+        public static SphincsPlusParameters shake_192f = new SphincsPlusParameters("shake-192f-robust",
             new Shake256EngineProvider(true, 24, 16, 22, 8, 33, 66));
 
-        public static SPHINCSPlusParameters shake_192s = new SPHINCSPlusParameters("shake-192s-robust",
+        public static SphincsPlusParameters shake_192s = new SphincsPlusParameters("shake-192s-robust",
             new Shake256EngineProvider(true, 24, 16, 7, 14, 17, 63));
 
-        public static SPHINCSPlusParameters shake_256f = new SPHINCSPlusParameters("shake-256f-robust",
+        public static SphincsPlusParameters shake_256f = new SphincsPlusParameters("shake-256f-robust",
             new Shake256EngineProvider(true, 32, 16, 17, 9, 35, 68));
 
-        public static SPHINCSPlusParameters shake_256s = new SPHINCSPlusParameters("shake-256s-robust",
+        public static SphincsPlusParameters shake_256s = new SphincsPlusParameters("shake-256s-robust",
             new Shake256EngineProvider(true, 32, 16, 8, 14, 22, 64));
 
-        public static SPHINCSPlusParameters shake_128f_simple = new SPHINCSPlusParameters("shake-128f-simple",
+        public static SphincsPlusParameters shake_128f_simple = new SphincsPlusParameters("shake-128f-simple",
             new Shake256EngineProvider(false, 16, 16, 22, 6, 33, 66));
 
-        public static SPHINCSPlusParameters shake_128s_simple = new SPHINCSPlusParameters("shake-128s-simple",
+        public static SphincsPlusParameters shake_128s_simple = new SphincsPlusParameters("shake-128s-simple",
             new Shake256EngineProvider(false, 16, 16, 7, 12, 14, 63));
 
-        public static SPHINCSPlusParameters shake_192f_simple = new SPHINCSPlusParameters("shake-192f-simple",
+        public static SphincsPlusParameters shake_192f_simple = new SphincsPlusParameters("shake-192f-simple",
             new Shake256EngineProvider(false, 24, 16, 22, 8, 33, 66));
 
-        public static SPHINCSPlusParameters shake_192s_simple = new SPHINCSPlusParameters("shake-192s-simple",
+        public static SphincsPlusParameters shake_192s_simple = new SphincsPlusParameters("shake-192s-simple",
             new Shake256EngineProvider(false, 24, 16, 7, 14, 17, 63));
 
-        public static SPHINCSPlusParameters shake_256f_simple = new SPHINCSPlusParameters("shake-256f-simple",
+        public static SphincsPlusParameters shake_256f_simple = new SphincsPlusParameters("shake-256f-simple",
             new Shake256EngineProvider(false, 32, 16, 17, 9, 35, 68));
 
-        public static SPHINCSPlusParameters shake_256s_simple = new SPHINCSPlusParameters("shake-256s-simple",
+        public static SphincsPlusParameters shake_256s_simple = new SphincsPlusParameters("shake-256s-simple",
             new Shake256EngineProvider(false, 32, 16, 8, 14, 22, 64));
 
+        // Haraka.
+
+        public static SphincsPlusParameters haraka_128f = new SphincsPlusParameters("haraka-128f-robust", new Haraka256EngineProvider(true, 16, 16, 22, 6, 33, 66));
+        public static SphincsPlusParameters haraka_128s = new SphincsPlusParameters("haraka-128s-robust", new Haraka256EngineProvider(true, 16, 16, 7, 12, 14, 63));
+
+        public static SphincsPlusParameters haraka_256f = new SphincsPlusParameters("haraka-256f-robust", new Haraka256EngineProvider(true, 32, 16, 17, 9, 35, 68));
+        public static SphincsPlusParameters haraka_256s = new SphincsPlusParameters("haraka-256s-robust", new Haraka256EngineProvider(true, 32, 16, 8, 14, 22, 64));
+
+        public static SphincsPlusParameters haraka_192f = new SphincsPlusParameters("haraka-192f-robust", new Haraka256EngineProvider(true, 24, 16, 22, 8, 33, 66));
+        public static SphincsPlusParameters haraka_192s = new SphincsPlusParameters("haraka-192s-robust", new Haraka256EngineProvider(true, 24, 16, 7, 14, 17, 63));
+
+        public static SphincsPlusParameters haraka_128f_simple = new SphincsPlusParameters("haraka-128f-simple", new Haraka256EngineProvider(false, 16, 16, 22, 6, 33, 66));
+        public static SphincsPlusParameters haraka_128s_simple = new SphincsPlusParameters("haraka-128s-simple", new Haraka256EngineProvider(false, 16, 16, 7, 12, 14, 63));
+
+        public static SphincsPlusParameters haraka_192f_simple = new SphincsPlusParameters("haraka-192f-simple", new Haraka256EngineProvider(false, 24, 16, 22, 8, 33, 66));
+        public static SphincsPlusParameters haraka_192s_simple = new SphincsPlusParameters("haraka-192s-simple", new Haraka256EngineProvider(false, 24, 16, 7, 14, 17, 63));
+
+        public static SphincsPlusParameters haraka_256f_simple = new SphincsPlusParameters("haraka-256f-simple", new Haraka256EngineProvider(false, 32, 16, 17, 9, 35, 68));
+        public static SphincsPlusParameters haraka_256s_simple = new SphincsPlusParameters("haraka-256s-simple", new Haraka256EngineProvider(false, 32, 16, 8, 14, 22, 64));
+
+
         private static uint sphincsPlus_sha2_128f_robust = 0x010101;
         private static uint sphincsPlus_sha2_128s_robust = 0x010102;
         private static uint sphincsPlus_sha2_192f_robust = 0x010103;
@@ -118,84 +137,127 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         private static uint sphincsPlus_shake_256f_simple = 0x020205;
         private static uint sphincsPlus_shake_256s_simple = 0x020206;
 
-        private static Dictionary<uint, SPHINCSPlusParameters> oidToParams = new Dictionary<uint, SPHINCSPlusParameters>();
-        private static Dictionary<SPHINCSPlusParameters, uint> paramsToOid = new Dictionary<SPHINCSPlusParameters, uint>();
+        private static uint sphincsPlus_haraka_128f_robust = 0x030101;
+        private static uint sphincsPlus_haraka_128s_robust = 0x030102;
+        private static uint sphincsPlus_haraka_192f_robust = 0x030103;
+        private static uint sphincsPlus_haraka_192s_robust = 0x030104;
+        private static uint sphincsPlus_haraka_256f_robust = 0x030105;
+        private static uint sphincsPlus_haraka_256s_robust = 0x030106;
 
-        static SPHINCSPlusParameters()
+        private static uint sphincsPlus_haraka_128f_simple = 0x030201;
+        private static uint sphincsPlus_haraka_128s_simple = 0x030202;
+        private static uint sphincsPlus_haraka_192f_simple = 0x030203;
+        private static uint sphincsPlus_haraka_192s_simple = 0x030204;
+        private static uint sphincsPlus_haraka_256f_simple = 0x030205;
+        private static uint sphincsPlus_haraka_256s_simple = 0x030206;
+
+
+        private static Dictionary<uint, SphincsPlusParameters> oidToParams = new Dictionary<uint, SphincsPlusParameters>();
+        private static Dictionary<SphincsPlusParameters, uint> paramsToOid = new Dictionary<SphincsPlusParameters, uint>();
+
+        static SphincsPlusParameters()
         {
-            oidToParams[sphincsPlus_sha2_128f_robust] = SPHINCSPlusParameters.sha2_128f;
-            oidToParams[sphincsPlus_sha2_128s_robust] = SPHINCSPlusParameters.sha2_128s;
-            oidToParams[sphincsPlus_sha2_192f_robust] = SPHINCSPlusParameters.sha2_192f;
-            oidToParams[sphincsPlus_sha2_192s_robust] = SPHINCSPlusParameters.sha2_192s;
-            oidToParams[sphincsPlus_sha2_256f_robust] = SPHINCSPlusParameters.sha2_256f;
-            oidToParams[sphincsPlus_sha2_256s_robust] = SPHINCSPlusParameters.sha2_256s;
-
-            oidToParams[sphincsPlus_sha2_128f_simple] = SPHINCSPlusParameters.sha2_128f_simple;
-            oidToParams[sphincsPlus_sha2_128s_simple] = SPHINCSPlusParameters.sha2_128s_simple;
-            oidToParams[sphincsPlus_sha2_192f_simple] = SPHINCSPlusParameters.sha2_192f_simple;
-            oidToParams[sphincsPlus_sha2_192s_simple] = SPHINCSPlusParameters.sha2_192s_simple;
-            oidToParams[sphincsPlus_sha2_256f_simple] = SPHINCSPlusParameters.sha2_256f_simple;
-            oidToParams[sphincsPlus_sha2_256s_simple] = SPHINCSPlusParameters.sha2_256s_simple;
-
-            oidToParams[sphincsPlus_shake_128f_robust] = SPHINCSPlusParameters.shake_128f;
-            oidToParams[sphincsPlus_shake_128s_robust] = SPHINCSPlusParameters.shake_128s;
-            oidToParams[sphincsPlus_shake_192f_robust] = SPHINCSPlusParameters.shake_192f;
-            oidToParams[sphincsPlus_shake_192s_robust] = SPHINCSPlusParameters.shake_192s;
-            oidToParams[sphincsPlus_shake_256f_robust] = SPHINCSPlusParameters.shake_256f;
-            oidToParams[sphincsPlus_shake_256s_robust] = SPHINCSPlusParameters.shake_256s;
-
-            oidToParams[sphincsPlus_shake_128f_simple] = SPHINCSPlusParameters.shake_128f_simple;
-            oidToParams[sphincsPlus_shake_128s_simple] = SPHINCSPlusParameters.shake_128s_simple;
-            oidToParams[sphincsPlus_shake_192f_simple] = SPHINCSPlusParameters.shake_192f_simple;
-            oidToParams[sphincsPlus_shake_192s_simple] = SPHINCSPlusParameters.shake_192s_simple;
-            oidToParams[sphincsPlus_shake_256f_simple] = SPHINCSPlusParameters.shake_256f_simple;
-            oidToParams[sphincsPlus_shake_256s_simple] = SPHINCSPlusParameters.shake_256s_simple;
-
-            paramsToOid[SPHINCSPlusParameters.sha2_128f] = sphincsPlus_sha2_128f_robust;
-            paramsToOid[SPHINCSPlusParameters.sha2_128s] = sphincsPlus_sha2_128s_robust;
-            paramsToOid[SPHINCSPlusParameters.sha2_192f] = sphincsPlus_sha2_192f_robust;
-            paramsToOid[SPHINCSPlusParameters.sha2_192s] = sphincsPlus_sha2_192s_robust;
-            paramsToOid[SPHINCSPlusParameters.sha2_256f] = sphincsPlus_sha2_256f_robust;
-            paramsToOid[SPHINCSPlusParameters.sha2_256s] = sphincsPlus_sha2_256s_robust;
-
-            paramsToOid[SPHINCSPlusParameters.sha2_128f_simple] = sphincsPlus_sha2_128f_simple;
-            paramsToOid[SPHINCSPlusParameters.sha2_128s_simple] = sphincsPlus_sha2_128s_simple;
-            paramsToOid[SPHINCSPlusParameters.sha2_192f_simple] = sphincsPlus_sha2_192f_simple;
-            paramsToOid[SPHINCSPlusParameters.sha2_192s_simple] = sphincsPlus_sha2_192s_simple;
-            paramsToOid[SPHINCSPlusParameters.sha2_256f_simple] = sphincsPlus_sha2_256f_simple;
-            paramsToOid[SPHINCSPlusParameters.sha2_256s_simple] = sphincsPlus_sha2_256s_simple;
-
-            paramsToOid[SPHINCSPlusParameters.shake_128f] = sphincsPlus_shake_128f_robust;
-            paramsToOid[SPHINCSPlusParameters.shake_128s] = sphincsPlus_shake_128s_robust;
-            paramsToOid[SPHINCSPlusParameters.shake_192f] = sphincsPlus_shake_192f_robust;
-            paramsToOid[SPHINCSPlusParameters.shake_192s] = sphincsPlus_shake_192s_robust;
-            paramsToOid[SPHINCSPlusParameters.shake_256f] = sphincsPlus_shake_256f_robust;
-            paramsToOid[SPHINCSPlusParameters.shake_256s] = sphincsPlus_shake_256s_robust;
-
-            paramsToOid[SPHINCSPlusParameters.shake_128f_simple] = sphincsPlus_shake_128f_simple;
-            paramsToOid[SPHINCSPlusParameters.shake_128s_simple] = sphincsPlus_shake_128s_simple;
-            paramsToOid[SPHINCSPlusParameters.shake_192f_simple] = sphincsPlus_shake_192f_simple;
-            paramsToOid[SPHINCSPlusParameters.shake_192s_simple] = sphincsPlus_shake_192s_simple;
-            paramsToOid[SPHINCSPlusParameters.shake_256f_simple] = sphincsPlus_shake_256f_simple;
-            paramsToOid[SPHINCSPlusParameters.shake_256s_simple] = sphincsPlus_shake_256s_simple;
+            oidToParams[sphincsPlus_sha2_128f_robust] = SphincsPlusParameters.sha2_128f;
+            oidToParams[sphincsPlus_sha2_128s_robust] = SphincsPlusParameters.sha2_128s;
+            oidToParams[sphincsPlus_sha2_192f_robust] = SphincsPlusParameters.sha2_192f;
+            oidToParams[sphincsPlus_sha2_192s_robust] = SphincsPlusParameters.sha2_192s;
+            oidToParams[sphincsPlus_sha2_256f_robust] = SphincsPlusParameters.sha2_256f;
+            oidToParams[sphincsPlus_sha2_256s_robust] = SphincsPlusParameters.sha2_256s;
+
+            oidToParams[sphincsPlus_sha2_128f_simple] = SphincsPlusParameters.sha2_128f_simple;
+            oidToParams[sphincsPlus_sha2_128s_simple] = SphincsPlusParameters.sha2_128s_simple;
+            oidToParams[sphincsPlus_sha2_192f_simple] = SphincsPlusParameters.sha2_192f_simple;
+            oidToParams[sphincsPlus_sha2_192s_simple] = SphincsPlusParameters.sha2_192s_simple;
+            oidToParams[sphincsPlus_sha2_256f_simple] = SphincsPlusParameters.sha2_256f_simple;
+            oidToParams[sphincsPlus_sha2_256s_simple] = SphincsPlusParameters.sha2_256s_simple;
+
+            oidToParams[sphincsPlus_shake_128f_robust] = SphincsPlusParameters.shake_128f;
+            oidToParams[sphincsPlus_shake_128s_robust] = SphincsPlusParameters.shake_128s;
+            oidToParams[sphincsPlus_shake_192f_robust] = SphincsPlusParameters.shake_192f;
+            oidToParams[sphincsPlus_shake_192s_robust] = SphincsPlusParameters.shake_192s;
+            oidToParams[sphincsPlus_shake_256f_robust] = SphincsPlusParameters.shake_256f;
+            oidToParams[sphincsPlus_shake_256s_robust] = SphincsPlusParameters.shake_256s;
+
+            oidToParams[sphincsPlus_shake_128f_simple] = SphincsPlusParameters.shake_128f_simple;
+            oidToParams[sphincsPlus_shake_128s_simple] = SphincsPlusParameters.shake_128s_simple;
+            oidToParams[sphincsPlus_shake_192f_simple] = SphincsPlusParameters.shake_192f_simple;
+            oidToParams[sphincsPlus_shake_192s_simple] = SphincsPlusParameters.shake_192s_simple;
+            oidToParams[sphincsPlus_shake_256f_simple] = SphincsPlusParameters.shake_256f_simple;
+            oidToParams[sphincsPlus_shake_256s_simple] = SphincsPlusParameters.shake_256s_simple;
+
+            oidToParams[sphincsPlus_haraka_128f_simple] = SphincsPlusParameters.haraka_128f_simple;
+            oidToParams[sphincsPlus_haraka_128f_robust] = SphincsPlusParameters.haraka_128f;
+            oidToParams[sphincsPlus_haraka_192f_simple] = SphincsPlusParameters.haraka_192f_simple;
+            oidToParams[sphincsPlus_haraka_192f_robust] = SphincsPlusParameters.haraka_192f;
+            oidToParams[sphincsPlus_haraka_256f_simple] = SphincsPlusParameters.haraka_256f_simple;
+            oidToParams[sphincsPlus_haraka_256f_robust] = SphincsPlusParameters.haraka_256f;
+
+            oidToParams[sphincsPlus_haraka_128s_simple] = SphincsPlusParameters.haraka_128s_simple;
+            oidToParams[sphincsPlus_haraka_128s_robust] = SphincsPlusParameters.haraka_128s;
+            oidToParams[sphincsPlus_haraka_192s_simple] = SphincsPlusParameters.haraka_192s_simple;
+            oidToParams[sphincsPlus_haraka_192s_robust] = SphincsPlusParameters.haraka_192s;
+            oidToParams[sphincsPlus_haraka_256s_simple] = SphincsPlusParameters.haraka_256s_simple;
+            oidToParams[sphincsPlus_haraka_256s_robust] = SphincsPlusParameters.haraka_256s;
+
+
+            paramsToOid[SphincsPlusParameters.sha2_128f] = sphincsPlus_sha2_128f_robust;
+            paramsToOid[SphincsPlusParameters.sha2_128s] = sphincsPlus_sha2_128s_robust;
+            paramsToOid[SphincsPlusParameters.sha2_192f] = sphincsPlus_sha2_192f_robust;
+            paramsToOid[SphincsPlusParameters.sha2_192s] = sphincsPlus_sha2_192s_robust;
+            paramsToOid[SphincsPlusParameters.sha2_256f] = sphincsPlus_sha2_256f_robust;
+            paramsToOid[SphincsPlusParameters.sha2_256s] = sphincsPlus_sha2_256s_robust;
+
+            paramsToOid[SphincsPlusParameters.sha2_128f_simple] = sphincsPlus_sha2_128f_simple;
+            paramsToOid[SphincsPlusParameters.sha2_128s_simple] = sphincsPlus_sha2_128s_simple;
+            paramsToOid[SphincsPlusParameters.sha2_192f_simple] = sphincsPlus_sha2_192f_simple;
+            paramsToOid[SphincsPlusParameters.sha2_192s_simple] = sphincsPlus_sha2_192s_simple;
+            paramsToOid[SphincsPlusParameters.sha2_256f_simple] = sphincsPlus_sha2_256f_simple;
+            paramsToOid[SphincsPlusParameters.sha2_256s_simple] = sphincsPlus_sha2_256s_simple;
+
+            paramsToOid[SphincsPlusParameters.shake_128f] = sphincsPlus_shake_128f_robust;
+            paramsToOid[SphincsPlusParameters.shake_128s] = sphincsPlus_shake_128s_robust;
+            paramsToOid[SphincsPlusParameters.shake_192f] = sphincsPlus_shake_192f_robust;
+            paramsToOid[SphincsPlusParameters.shake_192s] = sphincsPlus_shake_192s_robust;
+            paramsToOid[SphincsPlusParameters.shake_256f] = sphincsPlus_shake_256f_robust;
+            paramsToOid[SphincsPlusParameters.shake_256s] = sphincsPlus_shake_256s_robust;
+
+            paramsToOid[SphincsPlusParameters.shake_128f_simple] = sphincsPlus_shake_128f_simple;
+            paramsToOid[SphincsPlusParameters.shake_128s_simple] = sphincsPlus_shake_128s_simple;
+            paramsToOid[SphincsPlusParameters.shake_192f_simple] = sphincsPlus_shake_192f_simple;
+            paramsToOid[SphincsPlusParameters.shake_192s_simple] = sphincsPlus_shake_192s_simple;
+            paramsToOid[SphincsPlusParameters.shake_256f_simple] = sphincsPlus_shake_256f_simple;
+            paramsToOid[SphincsPlusParameters.shake_256s_simple] = sphincsPlus_shake_256s_simple;
+
+            paramsToOid[SphincsPlusParameters.haraka_128f_simple] = sphincsPlus_haraka_128f_simple;
+            paramsToOid[SphincsPlusParameters.haraka_192f_simple] = sphincsPlus_haraka_192f_simple;
+            paramsToOid[SphincsPlusParameters.haraka_256f_simple] = sphincsPlus_haraka_256f_simple;
+            paramsToOid[SphincsPlusParameters.haraka_128s_simple] = sphincsPlus_haraka_128s_simple;
+            paramsToOid[SphincsPlusParameters.haraka_192s_simple] = sphincsPlus_haraka_192s_simple;
+            paramsToOid[SphincsPlusParameters.haraka_256s_simple] = sphincsPlus_haraka_256s_simple;
+            paramsToOid[SphincsPlusParameters.haraka_128f] = sphincsPlus_haraka_128f_robust;
+            paramsToOid[SphincsPlusParameters.haraka_192f] = sphincsPlus_haraka_192f_robust;
+            paramsToOid[SphincsPlusParameters.haraka_256f] = sphincsPlus_haraka_256f_robust;
+            paramsToOid[SphincsPlusParameters.haraka_128s] = sphincsPlus_haraka_128s_robust;
+            paramsToOid[SphincsPlusParameters.haraka_192s] = sphincsPlus_haraka_192s_robust;
+            paramsToOid[SphincsPlusParameters.haraka_256s] = sphincsPlus_haraka_256s_robust;
         }
 
-        private String name;
-        private ISPHINCSPlusEngineProvider engineProvider;
+        private readonly string m_name;
+        private readonly ISphincsPlusEngineProvider m_engineProvider;
 
-        private SPHINCSPlusParameters(String name, ISPHINCSPlusEngineProvider engineProvider)
+        private SphincsPlusParameters(string name, ISphincsPlusEngineProvider engineProvider)
         {
-            this.name = name;
-            this.engineProvider = engineProvider;
+            m_name = name;
+            m_engineProvider = engineProvider;
         }
 
-        public String Name => name;
+        public string Name => m_name;
 
-        internal int N => engineProvider.N;
+        internal int N => m_engineProvider.N;
 
-        internal SPHINCSPlusEngine GetEngine()
+        internal SphincsPlusEngine GetEngine()
         {
-            return engineProvider.Get();
+            return m_engineProvider.Get();
         }
 
         /**
@@ -204,9 +266,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
          * @param id the oid of interest.
          * @return the parameter set.
          */
-        public static SPHINCSPlusParameters GetParams(uint id)
+        public static SphincsPlusParameters GetParams(int id)
         {
-            return (SPHINCSPlusParameters)oidToParams[id];
+            return oidToParams[Convert.ToUInt32(id)];
         }
 
         /**
@@ -215,18 +277,19 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
          * @param params the parameters of interest.
          * @return the OID for the parameter set.
          */
-        public static uint GetID(SPHINCSPlusParameters parameters)
+        public static int GetID(SphincsPlusParameters parameters)
         {
-            return paramsToOid[parameters];
+            return Convert.ToInt32(paramsToOid[parameters]);
         }
 
         public byte[] GetEncoded()
         {
-            return Pack.UInt32_To_BE(GetID(this));
+            return Pack.UInt32_To_BE((uint)GetID(this));
         }
     }
 
-    class Sha2EngineProvider : ISPHINCSPlusEngineProvider
+    internal sealed class Sha2EngineProvider
+        : ISphincsPlusEngineProvider
     {
         private readonly bool robust;
         private readonly int n;
@@ -235,7 +298,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         private readonly int a;
         private readonly int k;
         private readonly uint h;
-        
+
         internal Sha2EngineProvider(bool robust, int n, uint w, uint d, int a, int k, uint h)
         {
             this.robust = robust;
@@ -249,13 +312,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         public int N => this.n;
 
-        public SPHINCSPlusEngine Get()
+        public SphincsPlusEngine Get()
         {
-            return new SPHINCSPlusEngine.Sha2Engine(robust, n, w, d, a, k, h);
+            return new SphincsPlusEngine.Sha2Engine(robust, n, w, d, a, k, h);
         }
     }
 
-    class Shake256EngineProvider : ISPHINCSPlusEngineProvider
+    internal sealed class Shake256EngineProvider
+        : ISphincsPlusEngineProvider
     {
         private readonly bool robust;
         private readonly int n;
@@ -278,9 +342,44 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         public int N => this.n;
 
-        public SPHINCSPlusEngine Get()
+        public SphincsPlusEngine Get()
         {
-            return new SPHINCSPlusEngine.Shake256Engine(robust, n, w, d, a, k, h);
+            return new SphincsPlusEngine.Shake256Engine(robust, n, w, d, a, k, h);
+        }
+    }
+
+    internal sealed class Haraka256EngineProvider
+        : ISphincsPlusEngineProvider
+    {
+        private readonly bool robust;
+        private readonly int n;
+        private readonly uint w;
+        private readonly uint d;
+        private readonly int a;
+        private readonly int k;
+        private readonly uint h;
+
+        public Haraka256EngineProvider(bool robust, int n, uint w, uint d, int a, int k, uint h)
+        {
+            this.robust = robust;
+            this.n = n;
+            this.w = w;
+            this.d = d;
+            this.a = a;
+            this.k = k;
+            this.h = h;
+        }
+
+        public int N => this.n;
+
+        public SphincsPlusEngine Get()
+        {
+#if NETCOREAPP3_0_OR_GREATER
+            if (SphincsPlusEngine.HarakaSEngine_X86.IsSupported)
+                return new SphincsPlusEngine.HarakaSEngine_X86(robust, n, w, d, a, k, h);
+#endif
+
+            return new SphincsPlusEngine.HarakaSEngine(robust, n, w, d, a, k, h);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPrivateKeyParameters.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPrivateKeyParameters.cs
index 42c20f25d..55757b927 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPrivateKeyParameters.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPrivateKeyParameters.cs
@@ -1,17 +1,17 @@
-
 using System;
+
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    public class SPHINCSPlusPrivateKeyParameters
-        : SPHINCSPlusKeyParameters
+    public sealed class SphincsPlusPrivateKeyParameters
+        : SphincsPlusKeyParameters
     {
-        internal SK sk;
-        internal PK pk;
+        internal readonly SK m_sk;
+        internal readonly PK m_pk;
 
-        public SPHINCSPlusPrivateKeyParameters(SPHINCSPlusParameters parameters, byte[] skpkEncoded)
+        public SphincsPlusPrivateKeyParameters(SphincsPlusParameters parameters, byte[] skpkEncoded)
             : base(true, parameters)
         {
             int n = parameters.N;
@@ -20,47 +20,47 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 throw new ArgumentException("private key encoding does not match parameters");
             }
 
-            this.sk = new SK(Arrays.CopyOfRange(skpkEncoded, 0, n), Arrays.CopyOfRange(skpkEncoded, n, 2 * n));
-            this.pk = new PK(Arrays.CopyOfRange(skpkEncoded, 2 * n, 3 * n),
-                Arrays.CopyOfRange(skpkEncoded, 3 * n, 4 * n));
+            m_sk = new SK(Arrays.CopyOfRange(skpkEncoded, 0, n), Arrays.CopyOfRange(skpkEncoded, n, 2 * n));
+            m_pk = new PK(Arrays.CopyOfRange(skpkEncoded, 2 * n, 3 * n), Arrays.CopyOfRange(skpkEncoded, 3 * n, 4 * n));
         }
 
-        internal SPHINCSPlusPrivateKeyParameters(SPHINCSPlusParameters parameters, SK sk, PK pk)
+        internal SphincsPlusPrivateKeyParameters(SphincsPlusParameters parameters, SK sk, PK pk)
             : base(true, parameters)
         {
-            this.sk = sk;
-            this.pk = pk;
+            m_sk = sk;
+            m_pk = pk;
         }
 
         public byte[] GetSeed()
         {
-            return Arrays.Clone(sk.seed);
+            return Arrays.Clone(m_sk.seed);
         }
 
         public byte[] GetPrf()
         {
-            return Arrays.Clone(sk.prf);
+            return Arrays.Clone(m_sk.prf);
         }
 
         public byte[] GetPublicSeed()
         {
-            return Arrays.Clone(pk.seed);
+            return Arrays.Clone(m_pk.seed);
         }
 
         public byte[] GetPublicKey()
         {
-            return Arrays.Concatenate(pk.seed, pk.root);
+            return Arrays.Concatenate(m_pk.seed, m_pk.root);
         }
 
         public byte[] GetEncoded()
         {
-            return Arrays.Concatenate(Pack.UInt32_To_BE(SPHINCSPlusParameters.GetID(GetParameters())),
-                Arrays.ConcatenateAll(sk.seed, sk.prf, pk.seed, pk.root));
+            var id = Pack.UInt32_To_BE((uint)SphincsPlusParameters.GetID(Parameters));
+            return Arrays.ConcatenateAll(id, m_sk.seed, m_sk.prf, m_pk.seed, m_pk.root);
         }
 
         public byte[] GetEncodedPublicKey()
         {
-            return Arrays.ConcatenateAll(Pack.UInt32_To_BE(SPHINCSPlusParameters.GetID(GetParameters())), pk.seed, pk.root);
+            var id = Pack.UInt32_To_BE((uint)SphincsPlusParameters.GetID(Parameters));
+            return Arrays.ConcatenateAll(id, m_pk.seed, m_pk.root);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPublicKeyParameters.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPublicKeyParameters.cs
index 429234ee7..b34843998 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPublicKeyParameters.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusPublicKeyParameters.cs
@@ -1,45 +1,45 @@
 using System;
+
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    public class SPHINCSPlusPublicKeyParameters
-        : SPHINCSPlusKeyParameters
+    public sealed class SphincsPlusPublicKeyParameters
+        : SphincsPlusKeyParameters
     {
-        private PK pk;
+        private readonly PK m_pk;
 
-        public SPHINCSPlusPublicKeyParameters(SPHINCSPlusParameters parameters, byte[] pkEncoded)
+        public SphincsPlusPublicKeyParameters(SphincsPlusParameters parameters, byte[] pkEncoded)
             : base(false, parameters)
         {
             int n = parameters.N;
             if (pkEncoded.Length != 2 * n)
-            {
-                throw new ArgumentException("public key encoding does not match parameters");
-            }
+                throw new ArgumentException("public key encoding does not match parameters", nameof(pkEncoded));
 
-            this.pk = new PK(Arrays.CopyOfRange(pkEncoded, 0, n), Arrays.CopyOfRange(pkEncoded, n, 2 * n));
+            m_pk = new PK(Arrays.CopyOfRange(pkEncoded, 0, n), Arrays.CopyOfRange(pkEncoded, n, 2 * n));
         }
 
-        internal SPHINCSPlusPublicKeyParameters(SPHINCSPlusParameters parameters, PK pk)
+        internal SphincsPlusPublicKeyParameters(SphincsPlusParameters parameters, PK pk)
             : base(false, parameters)
         {
-            this.pk = pk;
+            m_pk = pk;
         }
 
         public byte[] GetSeed()
         {
-            return Arrays.Clone(pk.seed);
+            return Arrays.Clone(m_pk.seed);
         }
 
         public byte[] GetRoot()
         {
-            return Arrays.Clone(pk.root);
+            return Arrays.Clone(m_pk.root);
         }
 
         public byte[] GetEncoded()
         {
-            return Arrays.ConcatenateAll(Pack.UInt32_To_BE(SPHINCSPlusParameters.GetID(GetParameters())), pk.seed, pk.root);
+            var id = Pack.UInt32_To_BE((uint)SphincsPlusParameters.GetID(Parameters));
+            return Arrays.ConcatenateAll(id, m_pk.seed, m_pk.root);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusSigner.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusSigner.cs
index ec0b9691e..5c576eb15 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusSigner.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusSigner.cs
@@ -7,7 +7,6 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-
     /**
      * SPHINCS+ signer.
      * <p>
@@ -18,18 +17,18 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
      *     for further details.
      * </p>
      */
-    public class SPHINCSPlusSigner
+    public sealed class SphincsPlusSigner
         : IMessageSigner
     {
-        private SPHINCSPlusPrivateKeyParameters privKey;
-        private SPHINCSPlusPublicKeyParameters pubKey;
+        private SphincsPlusPrivateKeyParameters m_privKey;
+        private SphincsPlusPublicKeyParameters m_pubKey;
 
-        private SecureRandom random;
+        private SecureRandom m_random;
 
         /**
          * Base constructor.
          */
-        public SPHINCSPlusSigner()
+        public SphincsPlusSigner()
         {
         }
 
@@ -37,19 +36,19 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         {
             if (forSigning)
             {
-                if (param is ParametersWithRandom)
+                if (param is ParametersWithRandom parametersWithRandom)
                 {
-                    privKey = ((SPHINCSPlusPrivateKeyParameters) ((ParametersWithRandom) param).Parameters);
-                    this.random = ((ParametersWithRandom) param).Random;
+                    m_privKey = (SphincsPlusPrivateKeyParameters)parametersWithRandom.Parameters;
+                    m_random = parametersWithRandom.Random;
                 }
                 else
                 {
-                    privKey = (SPHINCSPlusPrivateKeyParameters) param;
+                    m_privKey = (SphincsPlusPrivateKeyParameters)param;
                 }
             }
             else
             {
-                pubKey = (SPHINCSPlusPublicKeyParameters) param;
+                m_pubKey = (SphincsPlusPublicKeyParameters)param;
             }
         }
 
@@ -59,45 +58,45 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             // # Output: SPHINCS+ signature SIG
             // init
 
-            SPHINCSPlusEngine engine = privKey.GetParameters().GetEngine();
-
+            SphincsPlusEngine engine = m_privKey.Parameters.GetEngine();
+            engine.Init(m_privKey.GetPublicSeed());
             // generate randomizer
             byte[] optRand = new byte[engine.N];
-            if (random != null)
+            if (m_random != null)
             {
-                random.NextBytes(optRand);
+                m_random.NextBytes(optRand);
             }
             else
             {
-                Array.Copy(privKey.pk.seed, 0, optRand, 0, optRand.Length);
+                Array.Copy(m_privKey.m_pk.seed, 0, optRand, 0, optRand.Length);
             }
 
             Fors fors = new Fors(engine);
-            byte[] R = engine.PRF_msg(privKey.sk.prf, optRand, message);
+            byte[] R = engine.PRF_msg(m_privKey.m_sk.prf, optRand, message);
             // compute message digest and index
-            IndexedDigest idxDigest = engine.H_msg(R, privKey.pk.seed, privKey.pk.root, message);
+            IndexedDigest idxDigest = engine.H_msg(R, m_privKey.m_pk.seed, m_privKey.m_pk.root, message);
             byte[] mHash = idxDigest.digest;
             ulong idx_tree = idxDigest.idx_tree;
             uint idx_leaf = idxDigest.idx_leaf;
             // FORS sign
             Adrs adrs = new Adrs();
-            adrs.SetType(Adrs.FORS_TREE);
+            adrs.SetAdrsType(Adrs.FORS_TREE);
             adrs.SetTreeAddress(idx_tree);
             adrs.SetKeyPairAddress(idx_leaf);
-            SIG_FORS[] sig_fors = fors.Sign(mHash, privKey.sk.seed, privKey.pk.seed, adrs);
+            SIG_FORS[] sig_fors = fors.Sign(mHash, m_privKey.m_sk.seed, m_privKey.m_pk.seed, adrs);
             // get FORS public key - spec shows M?
             adrs = new Adrs();
-            adrs.SetType(Adrs.FORS_TREE);
+            adrs.SetAdrsType(Adrs.FORS_TREE);
             adrs.SetTreeAddress(idx_tree);
             adrs.SetKeyPairAddress(idx_leaf);
 
-            byte[] PK_FORS = fors.PKFromSig(sig_fors, mHash, privKey.pk.seed, adrs);
+            byte[] PK_FORS = fors.PKFromSig(sig_fors, mHash, m_privKey.m_pk.seed, adrs);
 
             // sign FORS public key with HT
             Adrs treeAdrs = new Adrs();
-            treeAdrs.SetType(Adrs.TREE);
+            treeAdrs.SetAdrsType(Adrs.TREE);
 
-            HT ht = new HT(engine, privKey.GetSeed(), privKey.GetPublicSeed());
+            HT ht = new HT(engine, m_privKey.GetSeed(), m_privKey.GetPublicSeed());
             byte[] SIG_HT = ht.Sign(PK_FORS, idx_tree, idx_leaf);
             byte[][] sigComponents = new byte[sig_fors.Length + 2][];
             sigComponents[0] = R;
@@ -118,7 +117,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             //# Output: bool
 
             // init
-            SPHINCSPlusEngine engine = pubKey.GetParameters().GetEngine();
+            SphincsPlusEngine engine = m_pubKey.Parameters.GetEngine();
+            engine.Init(m_pubKey.GetSeed());
 
             Adrs adrs = new Adrs();
             SIG sig = new SIG(engine.N, engine.K, engine.A, engine.D, engine.H_PRIME, engine.WOTS_LEN, signature);
@@ -128,24 +128,24 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             SIG_XMSS[] SIG_HT = sig.SIG_HT;
 
             // compute message digest and index
-            IndexedDigest idxDigest = engine.H_msg(R, pubKey.GetSeed(), pubKey.GetRoot(), message);
+            IndexedDigest idxDigest = engine.H_msg(R, m_pubKey.GetSeed(), m_pubKey.GetRoot(), message);
             byte[] mHash = idxDigest.digest;
             ulong idx_tree = idxDigest.idx_tree;
             uint idx_leaf = idxDigest.idx_leaf;
 
             // compute FORS public key
-            adrs.SetType(Adrs.FORS_TREE);
+            adrs.SetAdrsType(Adrs.FORS_TREE);
             adrs.SetLayerAddress(0);
             adrs.SetTreeAddress(idx_tree);
             adrs.SetKeyPairAddress(idx_leaf);
-            byte[] PK_FORS = new Fors(engine).PKFromSig(sig_fors, mHash, pubKey.GetSeed(), adrs);
+            byte[] PK_FORS = new Fors(engine).PKFromSig(sig_fors, mHash, m_pubKey.GetSeed(), adrs);
             // verify HT signature
-            adrs.SetType(Adrs.TREE);
+            adrs.SetAdrsType(Adrs.TREE);
             adrs.SetLayerAddress(0);
             adrs.SetTreeAddress(idx_tree);
             adrs.SetKeyPairAddress(idx_leaf);
-            HT ht = new HT(engine, null, pubKey.GetSeed());
-            return ht.Verify(PK_FORS, SIG_HT, pubKey.GetSeed(), idx_tree, idx_leaf, pubKey.GetRoot());
+            HT ht = new HT(engine, null, m_pubKey.GetSeed());
+            return ht.Verify(PK_FORS, SIG_HT, m_pubKey.GetSeed(), idx_tree, idx_leaf, m_pubKey.GetRoot());
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs b/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs
index 508accc06..bd2d306b1 100644
--- a/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs
@@ -1,82 +1,133 @@
-
 using System;
+
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class WotsPlus
+    internal class WotsPlus
     {
-        private SPHINCSPlusEngine engine;
+        private SphincsPlusEngine engine;
         private uint w;
 
-        internal WotsPlus(SPHINCSPlusEngine engine)
+        internal WotsPlus(SphincsPlusEngine engine)
         {
             this.engine = engine;
             this.w = this.engine.WOTS_W;
         }
 
-        internal byte[] PKGen(byte[] skSeed, byte[] pkSeed, Adrs paramAdrs)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void PKGen(byte[] skSeed, byte[] pkSeed, Adrs paramAdrs, Span<byte> output)
+#else
+        internal void PKGen(byte[] skSeed, byte[] pkSeed, Adrs paramAdrs, byte[] output)
+#endif
         {
             Adrs wotspkAdrs = new Adrs(paramAdrs); // copy address to create OTS public key address
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            byte[] tmpConcat = new byte[engine.WOTS_LEN * engine.N];
+#else
             byte[][] tmp = new byte[engine.WOTS_LEN][];
+            byte[] sk = new byte[engine.N];
+#endif
             for (uint i = 0; i < engine.WOTS_LEN; i++)
             {
                 Adrs adrs = new Adrs(paramAdrs);
-                adrs.SetType(Adrs.WOTS_PRF);
+                adrs.SetAdrsType(Adrs.WOTS_PRF);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
                 adrs.SetChainAddress(i);
                 adrs.SetHashAddress(0);
-                
-                byte[] sk = engine.PRF(pkSeed, skSeed, adrs);
-                adrs.SetType(Adrs.WOTS_HASH);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                engine.PRF(pkSeed, skSeed, adrs, tmpConcat, engine.N * (int)i);
+#else
+                engine.PRF(pkSeed, skSeed, adrs, sk, 0);
+#endif
+
+                adrs.SetAdrsType(Adrs.WOTS_HASH);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
                 adrs.SetChainAddress(i);
                 adrs.SetHashAddress(0);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Chain(0, w - 1, pkSeed, adrs, tmpConcat.AsSpan(engine.N * (int)i, engine.N));
+#else
                 tmp[i] = Chain(sk, 0, w - 1, pkSeed, adrs);
+#endif
             }
 
-            wotspkAdrs.SetType(Adrs.WOTS_PK);
+            wotspkAdrs.SetAdrsType(Adrs.WOTS_PK);
             wotspkAdrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
 
-            return engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            engine.T_l(pkSeed, wotspkAdrs, tmpConcat, output);
+#else
+            engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp), output);
+#endif
         }
 
-        //    #Input: Input string X, start index i, number of steps s, public seed PK.seed,
-        //    address Adrs
-        //    #Output: value of F iterated s times on X
-        byte[] Chain(byte[] X, uint i, uint s, byte[] pkSeed, Adrs adrs)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        // #Input: Input string X, start index i, number of steps s, public seed PK.seed, address Adrs
+        // #Output: value of F iterated s times on X
+        private bool Chain(uint i, uint s, byte[] pkSeed, Adrs adrs, Span<byte> X)
         {
             if (s == 0)
-            {
-                return Arrays.Clone(X);
-            }
+                return true;
 
+            // TODO Check this since the highest we use is i + s - 1
             if ((i + s) > (this.w - 1))
+                return false;
+
+            for (uint j = 0; j < s; ++j)
             {
-                return null;
+                adrs.SetHashAddress(i + j);
+                engine.F(pkSeed, adrs, X);
             }
 
-            byte[] tmp = Chain(X, i, s - 1, pkSeed, adrs);
-            adrs.SetHashAddress(i + s - 1);
-            tmp = engine.F(pkSeed, adrs, tmp);
+            return true;
+        }
+#else
+        // #Input: Input string X, start index i, number of steps s, public seed PK.seed, address Adrs
+        // #Output: value of F iterated s times on X
+        private byte[] Chain(byte[] X, uint i, uint s, byte[] pkSeed, Adrs adrs)
+        {
+            if (s == 0)
+                return Arrays.Clone(X);
+
+            // TODO Check this since the highest we use is i + s - 1
+            if ((i + s) > (this.w - 1))
+                return null;
 
-            return tmp;
+            byte[] result = X;
+            for (uint j = 0; j < s; ++j)
+            {
+                adrs.SetHashAddress(i + j);
+                result = engine.F(pkSeed, adrs, result);
+            }
+            return result;
         }
+#endif
 
-        //
         // #Input: Message M, secret seed SK.seed, public seed PK.seed, address Adrs
         // #Output: WOTS+ signature sig
-        public byte[] Sign(byte[] M, byte[] skSeed, byte[] pkSeed, Adrs paramAdrs)
+        internal byte[] Sign(byte[] M, byte[] skSeed, byte[] pkSeed, Adrs paramAdrs)
         {
             Adrs adrs = new Adrs(paramAdrs);
 
-            uint csum = 0;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<uint> msg = stackalloc uint[engine.WOTS_LEN];
+
+            // convert message to base w
+            BaseW(M, w, msg[..engine.WOTS_LEN1]);
+#else
+            uint[] msg = new uint[engine.WOTS_LEN];
+
             // convert message to base w
-            uint[] msg = BaseW(M, w, engine.WOTS_LEN1);
+            BaseW(M, 0, w, msg, 0, engine.WOTS_LEN1);
+#endif
+
             // compute checksum
+            uint csum = 0;
             for (int i = 0; i < engine.WOTS_LEN1; i++)
             {
                 csum += w - 1 - msg[i];
@@ -85,92 +136,164 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             // convert csum to base w
             if ((engine.WOTS_LOGW % 8) != 0)
             {
-                csum = csum << (8 - ((engine.WOTS_LEN2 * engine.WOTS_LOGW) % 8));
+                csum <<= 8 - (engine.WOTS_LEN2 * engine.WOTS_LOGW % 8);
             }
-
             int len_2_bytes = (engine.WOTS_LEN2 * engine.WOTS_LOGW + 7) / 8;
-            byte[] bytes = Pack.UInt32_To_BE(csum);
-            msg = Arrays.Concatenate(msg,
-                BaseW(Arrays.CopyOfRange(bytes, len_2_bytes, bytes.Length), w, engine.WOTS_LEN2));
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> csum_bytes = stackalloc byte[4];
+            Pack.UInt32_To_BE(csum, csum_bytes);
+            BaseW(csum_bytes[^len_2_bytes..], w, msg[engine.WOTS_LEN1..]);
+
+            byte[] sigConcat = new byte[engine.WOTS_LEN * engine.N];
+#else
+            byte[] csum_bytes = Pack.UInt32_To_BE(csum);
+            BaseW(csum_bytes, 4 - len_2_bytes, w, msg, engine.WOTS_LEN1, engine.WOTS_LEN2);
+
             byte[][] sig = new byte[engine.WOTS_LEN][];
-            for (uint i = 0; i < engine.WOTS_LEN; i++)
+            byte[] sk = new byte[engine.N];
+#endif
+            for (int i = 0; i < engine.WOTS_LEN; i++)
             {
-                adrs.SetType(Adrs.WOTS_PRF);
+                adrs.SetAdrsType(Adrs.WOTS_PRF);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
-                adrs.SetChainAddress(i);
+                adrs.SetChainAddress((uint)i);
                 adrs.SetHashAddress(0);
-                byte[] sk = engine.PRF(pkSeed, skSeed, adrs);
-                adrs.SetType(Adrs.WOTS_HASH);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                engine.PRF(pkSeed, skSeed, adrs, sigConcat, engine.N * i);
+#else
+                engine.PRF(pkSeed, skSeed, adrs, sk, 0);
+#endif
+
+                adrs.SetAdrsType(Adrs.WOTS_HASH);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
-                adrs.SetChainAddress(i);
+                adrs.SetChainAddress((uint)i);
                 adrs.SetHashAddress(0);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Chain(0, msg[i], pkSeed, adrs, sigConcat.AsSpan(engine.N * i, engine.N));
+#else
                 sig[i] = Chain(sk, 0, msg[i], pkSeed, adrs);
+#endif
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return sigConcat;
+#else
             return Arrays.ConcatenateAll(sig);
+#endif
         }
 
         //
         // Input: len_X-byte string X, int w, output length out_len
-        // Output: out_len int array basew
-        uint[] BaseW(byte[] X, uint w, int out_len)
+        // Output: outLen int array basew
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void BaseW(ReadOnlySpan<byte> X, uint w, Span<uint> output)
         {
-            int input = 0;
-            int outputIndex = 0;
             int total = 0;
             int bits = 0;
-            uint[] output = new uint[out_len];
+            int XOff = 0;
+            int outOff = 0;
 
-            for (int consumed = 0; consumed < out_len; consumed++)
+            for (int consumed = 0; consumed < output.Length; consumed++)
             {
                 if (bits == 0)
                 {
-                    total = X[input];
-                    input++;
+                    total = X[XOff++];
                     bits += 8;
                 }
 
                 bits -= engine.WOTS_LOGW;
-                output[outputIndex] = (uint) ((total >> bits) & (w - 1));
-                outputIndex++;
+                output[outOff++] = (uint)((total >> bits) & (w - 1));
             }
+        }
+#else
+        internal void BaseW(byte[] X, int XOff, uint w, uint[] output, int outOff, int outLen)
+        {
+            int total = 0;
+            int bits = 0;
 
-            return output;
+            for (int consumed = 0; consumed < outLen; consumed++)
+            {
+                if (bits == 0)
+                {
+                    total = X[XOff++];
+                    bits += 8;
+                }
+
+                bits -= engine.WOTS_LOGW;
+                output[outOff++] = (uint)((total >> bits) & (w - 1));
+            }
         }
+#endif
 
-        public byte[] PKFromSig(byte[] sig, byte[] M, byte[] pkSeed, Adrs adrs)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void PKFromSig(byte[] sig, byte[] M, byte[] pkSeed, Adrs adrs, Span<byte> output)
+#else
+        internal void PKFromSig(byte[] sig, byte[] M, byte[] pkSeed, Adrs adrs, byte[] output)
+#endif
         {
-            uint csum = 0;
             Adrs wotspkAdrs = new Adrs(adrs);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<uint> msg = stackalloc uint[engine.WOTS_LEN];
+
             // convert message to base w
-            uint[] msg = BaseW(M, w, engine.WOTS_LEN1);
+            BaseW(M, w, msg[..engine.WOTS_LEN1]);
+#else
+            uint[] msg = new uint[engine.WOTS_LEN];
+
+            // convert message to base w
+            BaseW(M, 0, w, msg, 0, engine.WOTS_LEN1);
+#endif
+
             // compute checksum
+            uint csum = 0;
             for (int i = 0; i < engine.WOTS_LEN1; i++)
             {
-                csum += (uint) (w - 1 - msg[i]);
+                csum += w - 1 - msg[i];
             }
 
             // convert csum to base w
-            csum = csum << (8 - ((engine.WOTS_LEN2 * engine.WOTS_LOGW) % 8));
+            csum <<= 8 - (engine.WOTS_LEN2 * engine.WOTS_LOGW % 8);
             int len_2_bytes = (engine.WOTS_LEN2 * engine.WOTS_LOGW + 7) / 8;
 
-            msg = Arrays.Concatenate(msg,
-                BaseW(Arrays.CopyOfRange(Pack.UInt32_To_BE(csum), 4 - len_2_bytes, 4), w, engine.WOTS_LEN2));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> csum_bytes = stackalloc byte[4];
+            Pack.UInt32_To_BE(csum, csum_bytes);
+            BaseW(csum_bytes[^len_2_bytes..], w, msg[engine.WOTS_LEN1..]);
+
+            byte[] tmpConcat = new byte[engine.WOTS_LEN * engine.N];
+#else
+            byte[] csum_bytes = Pack.UInt32_To_BE(csum);
+            BaseW(csum_bytes, 4 - len_2_bytes, w, msg, engine.WOTS_LEN1, engine.WOTS_LEN2);
 
             byte[] sigI = new byte[engine.N];
             byte[][] tmp = new byte[engine.WOTS_LEN][];
-            for (uint i = 0; i < engine.WOTS_LEN; i++)
+#endif
+            for (int i = 0; i < engine.WOTS_LEN; i++)
             {
-                adrs.SetChainAddress(i);
-                Array.Copy(sig, i * engine.N, sigI, 0, engine.N);
+                adrs.SetChainAddress((uint)i);
+
+                int sigPos = engine.N * i;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Array.Copy(sig, sigPos, tmpConcat, sigPos, engine.N);
+                Chain(msg[i], w - 1 - msg[i], pkSeed, adrs, tmpConcat.AsSpan(sigPos, engine.N));
+#else
+                Array.Copy(sig, sigPos, sigI, 0, engine.N);
                 tmp[i] = Chain(sigI, msg[i], w - 1 - msg[i], pkSeed, adrs);
-            } // f6be78d057cc8056907ad2bf83cc8be7
+#endif
+            }
 
-            wotspkAdrs.SetType(Adrs.WOTS_PK);
+            wotspkAdrs.SetAdrsType(Adrs.WOTS_PK);
             wotspkAdrs.SetKeyPairAddress(adrs.GetKeyPairAddress());
 
-            return engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            engine.T_l(pkSeed, wotspkAdrs, tmpConcat, output);
+#else
+            engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp), output);
+#endif
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/utils/PqcUtilities.cs b/crypto/src/pqc/crypto/utils/PqcUtilities.cs
index a1fb04340..67e58fd28 100644
--- a/crypto/src/pqc/crypto/utils/PqcUtilities.cs
+++ b/crypto/src/pqc/crypto/utils/PqcUtilities.cs
@@ -1,9 +1,13 @@
-using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.BC;
+using Org.BouncyCastle.Pqc.Crypto.Bike;
 using Org.BouncyCastle.Pqc.Crypto.Cmce;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using Org.BouncyCastle.Pqc.Crypto.Falcon;
+using Org.BouncyCastle.Pqc.Crypto.Hqc;
 using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Pqc.Crypto.Saber;
 using Org.BouncyCastle.Pqc.Crypto.Sike;
@@ -11,21 +15,37 @@ using Org.BouncyCastle.Pqc.Crypto.SphincsPlus;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 {
-    public class PqcUtilities
+    internal class PqcUtilities
     {
         private readonly static Dictionary<CmceParameters, DerObjectIdentifier> mcElieceOids = new Dictionary<CmceParameters, DerObjectIdentifier>();
         private readonly static Dictionary<DerObjectIdentifier, CmceParameters> mcElieceParams = new Dictionary<DerObjectIdentifier, CmceParameters>();
         
-        private readonly static Dictionary<SABERParameters, DerObjectIdentifier> saberOids = new Dictionary<SABERParameters, DerObjectIdentifier>();
-        private readonly static Dictionary<DerObjectIdentifier, SABERParameters> saberParams = new Dictionary<DerObjectIdentifier, SABERParameters>();
+        private readonly static Dictionary<SaberParameters, DerObjectIdentifier> saberOids = new Dictionary<SaberParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, SaberParameters> saberParams = new Dictionary<DerObjectIdentifier, SaberParameters>();
 
         private readonly static Dictionary<PicnicParameters, DerObjectIdentifier> picnicOids = new Dictionary<PicnicParameters, DerObjectIdentifier>();
         private readonly static Dictionary<DerObjectIdentifier, PicnicParameters> picnicParams = new Dictionary<DerObjectIdentifier, PicnicParameters>();
-        
-        private readonly static Dictionary<SIKEParameters, DerObjectIdentifier> sikeOids = new Dictionary<SIKEParameters, DerObjectIdentifier>();
-        private readonly static Dictionary<DerObjectIdentifier, SIKEParameters> sikeParams = new Dictionary<DerObjectIdentifier, SIKEParameters>();
 
-        
+#pragma warning disable CS0618 // Type or member is obsolete
+        private readonly static Dictionary<SikeParameters, DerObjectIdentifier> sikeOids = new Dictionary<SikeParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, SikeParameters> sikeParams = new Dictionary<DerObjectIdentifier, SikeParameters>();
+#pragma warning restore CS0618 // Type or member is obsolete
+
+        private readonly static Dictionary<KyberParameters, DerObjectIdentifier> kyberOids = new Dictionary<KyberParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, KyberParameters> kyberParams = new Dictionary<DerObjectIdentifier, KyberParameters>();
+
+        private readonly static Dictionary<DilithiumParameters, DerObjectIdentifier> dilithiumOids = new Dictionary<DilithiumParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, DilithiumParameters> dilithiumParams = new Dictionary<DerObjectIdentifier, DilithiumParameters>();
+
+        private readonly static Dictionary<FalconParameters, DerObjectIdentifier> falconOids = new Dictionary<FalconParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, FalconParameters> falconParams = new Dictionary<DerObjectIdentifier, FalconParameters>();
+
+        private readonly static Dictionary<BikeParameters, DerObjectIdentifier> bikeOids = new Dictionary<BikeParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, BikeParameters> bikeParams = new Dictionary<DerObjectIdentifier, BikeParameters>();
+
+        private readonly static Dictionary<HqcParameters, DerObjectIdentifier> hqcOids = new Dictionary<HqcParameters, DerObjectIdentifier>();
+        private readonly static Dictionary<DerObjectIdentifier, HqcParameters> hqcParams = new Dictionary<DerObjectIdentifier, HqcParameters>();
+
         static PqcUtilities()
         {
             // CMCE
@@ -51,26 +71,43 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
             mcElieceParams[BCObjectIdentifiers.mceliece8192128_r3] = CmceParameters.mceliece8192128r3;
             mcElieceParams[BCObjectIdentifiers.mceliece8192128f_r3] = CmceParameters.mceliece8192128fr3;
             
-            saberOids[SABERParameters.lightsaberkem128r3] = BCObjectIdentifiers.lightsaberkem128r3;
-            saberOids[SABERParameters.saberkem128r3] = BCObjectIdentifiers.saberkem128r3;
-            saberOids[SABERParameters.firesaberkem128r3] = BCObjectIdentifiers.firesaberkem128r3;
-            saberOids[SABERParameters.lightsaberkem192r3] = BCObjectIdentifiers.lightsaberkem192r3;
-            saberOids[SABERParameters.saberkem192r3] = BCObjectIdentifiers.saberkem192r3;
-            saberOids[SABERParameters.firesaberkem192r3] = BCObjectIdentifiers.firesaberkem192r3;
-            saberOids[SABERParameters.lightsaberkem256r3] = BCObjectIdentifiers.lightsaberkem256r3;
-            saberOids[SABERParameters.saberkem256r3] = BCObjectIdentifiers.saberkem256r3;
-            saberOids[SABERParameters.firesaberkem256r3] = BCObjectIdentifiers.firesaberkem256r3;
-            
-            saberParams[BCObjectIdentifiers.lightsaberkem128r3] = SABERParameters.lightsaberkem128r3;
-            saberParams[BCObjectIdentifiers.saberkem128r3] = SABERParameters.saberkem128r3;
-            saberParams[BCObjectIdentifiers.firesaberkem128r3] = SABERParameters.firesaberkem128r3;
-            saberParams[BCObjectIdentifiers.lightsaberkem192r3] = SABERParameters.lightsaberkem192r3;
-            saberParams[BCObjectIdentifiers.saberkem192r3] = SABERParameters.saberkem192r3;
-            saberParams[BCObjectIdentifiers.firesaberkem192r3] = SABERParameters.firesaberkem192r3;
-            saberParams[BCObjectIdentifiers.lightsaberkem256r3] = SABERParameters.lightsaberkem256r3;
-            saberParams[BCObjectIdentifiers.saberkem256r3] = SABERParameters.saberkem256r3;
-            saberParams[BCObjectIdentifiers.firesaberkem256r3] = SABERParameters.firesaberkem256r3;
+            saberOids[SaberParameters.lightsaberkem128r3] = BCObjectIdentifiers.lightsaberkem128r3;
+            saberOids[SaberParameters.saberkem128r3] = BCObjectIdentifiers.saberkem128r3;
+            saberOids[SaberParameters.firesaberkem128r3] = BCObjectIdentifiers.firesaberkem128r3;
+            saberOids[SaberParameters.lightsaberkem192r3] = BCObjectIdentifiers.lightsaberkem192r3;
+            saberOids[SaberParameters.saberkem192r3] = BCObjectIdentifiers.saberkem192r3;
+            saberOids[SaberParameters.firesaberkem192r3] = BCObjectIdentifiers.firesaberkem192r3;
+            saberOids[SaberParameters.lightsaberkem256r3] = BCObjectIdentifiers.lightsaberkem256r3;
+            saberOids[SaberParameters.saberkem256r3] = BCObjectIdentifiers.saberkem256r3;
+            saberOids[SaberParameters.firesaberkem256r3] = BCObjectIdentifiers.firesaberkem256r3;
+            saberOids[SaberParameters.ulightsaberkemr3] = BCObjectIdentifiers.ulightsaberkemr3;
+            saberOids[SaberParameters.usaberkemr3] = BCObjectIdentifiers.usaberkemr3;
+            saberOids[SaberParameters.ufiresaberkemr3] = BCObjectIdentifiers.ufiresaberkemr3;
+            saberOids[SaberParameters.lightsaberkem90sr3] = BCObjectIdentifiers.lightsaberkem90sr3;
+            saberOids[SaberParameters.saberkem90sr3] = BCObjectIdentifiers.saberkem90sr3;
+            saberOids[SaberParameters.firesaberkem90sr3] = BCObjectIdentifiers.firesaberkem90sr3;
+            saberOids[SaberParameters.ulightsaberkem90sr3] = BCObjectIdentifiers.ulightsaberkem90sr3;
+            saberOids[SaberParameters.usaberkem90sr3] = BCObjectIdentifiers.usaberkem90sr3;
+            saberOids[SaberParameters.ufiresaberkem90sr3] = BCObjectIdentifiers.ufiresaberkem90sr3;
 
+            saberParams[BCObjectIdentifiers.lightsaberkem128r3] = SaberParameters.lightsaberkem128r3;
+            saberParams[BCObjectIdentifiers.saberkem128r3] = SaberParameters.saberkem128r3;
+            saberParams[BCObjectIdentifiers.firesaberkem128r3] = SaberParameters.firesaberkem128r3;
+            saberParams[BCObjectIdentifiers.lightsaberkem192r3] = SaberParameters.lightsaberkem192r3;
+            saberParams[BCObjectIdentifiers.saberkem192r3] = SaberParameters.saberkem192r3;
+            saberParams[BCObjectIdentifiers.firesaberkem192r3] = SaberParameters.firesaberkem192r3;
+            saberParams[BCObjectIdentifiers.lightsaberkem256r3] = SaberParameters.lightsaberkem256r3;
+            saberParams[BCObjectIdentifiers.saberkem256r3] = SaberParameters.saberkem256r3;
+            saberParams[BCObjectIdentifiers.firesaberkem256r3] = SaberParameters.firesaberkem256r3;
+            saberParams[BCObjectIdentifiers.ulightsaberkemr3] = SaberParameters.ulightsaberkemr3;
+            saberParams[BCObjectIdentifiers.usaberkemr3] = SaberParameters.usaberkemr3;
+            saberParams[BCObjectIdentifiers.ufiresaberkemr3] = SaberParameters.ufiresaberkemr3;
+            saberParams[BCObjectIdentifiers.lightsaberkem90sr3] = SaberParameters.lightsaberkem90sr3;
+            saberParams[BCObjectIdentifiers.saberkem90sr3] = SaberParameters.saberkem90sr3;
+            saberParams[BCObjectIdentifiers.firesaberkem90sr3] = SaberParameters.firesaberkem90sr3;
+            saberParams[BCObjectIdentifiers.ulightsaberkem90sr3] = SaberParameters.ulightsaberkem90sr3;
+            saberParams[BCObjectIdentifiers.usaberkem90sr3] = SaberParameters.usaberkem90sr3;
+            saberParams[BCObjectIdentifiers.ufiresaberkem90sr3] = SaberParameters.ufiresaberkem90sr3;
             
             picnicOids[PicnicParameters.picnicl1fs] = BCObjectIdentifiers.picnicl1fs;
             picnicOids[PicnicParameters.picnicl1ur] = BCObjectIdentifiers.picnicl1ur;
@@ -97,27 +134,80 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
             picnicParams[BCObjectIdentifiers.picnicl1full] = PicnicParameters.picnicl1full;
             picnicParams[BCObjectIdentifiers.picnicl3full] = PicnicParameters.picnicl3full;
             picnicParams[BCObjectIdentifiers.picnicl5full] = PicnicParameters.picnicl5full;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            sikeParams[BCObjectIdentifiers.sikep434] = SikeParameters.sikep434;
+            sikeParams[BCObjectIdentifiers.sikep503] = SikeParameters.sikep503;
+            sikeParams[BCObjectIdentifiers.sikep610] = SikeParameters.sikep610;
+            sikeParams[BCObjectIdentifiers.sikep751] = SikeParameters.sikep751;
+            sikeParams[BCObjectIdentifiers.sikep434_compressed] = SikeParameters.sikep434_compressed;
+            sikeParams[BCObjectIdentifiers.sikep503_compressed] = SikeParameters.sikep503_compressed;
+            sikeParams[BCObjectIdentifiers.sikep610_compressed] = SikeParameters.sikep610_compressed;
+            sikeParams[BCObjectIdentifiers.sikep751_compressed] = SikeParameters.sikep751_compressed;
+
+            sikeOids[SikeParameters.sikep434] = BCObjectIdentifiers.sikep434;
+            sikeOids[SikeParameters.sikep503] = BCObjectIdentifiers.sikep503;
+            sikeOids[SikeParameters.sikep610] = BCObjectIdentifiers.sikep610;
+            sikeOids[SikeParameters.sikep751] = BCObjectIdentifiers.sikep751;
+            sikeOids[SikeParameters.sikep434_compressed] = BCObjectIdentifiers.sikep434_compressed;
+            sikeOids[SikeParameters.sikep503_compressed] = BCObjectIdentifiers.sikep503_compressed;
+            sikeOids[SikeParameters.sikep610_compressed] = BCObjectIdentifiers.sikep610_compressed;
+            sikeOids[SikeParameters.sikep751_compressed] = BCObjectIdentifiers.sikep751_compressed;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+            kyberOids[KyberParameters.kyber512] = BCObjectIdentifiers.kyber512;
+            kyberOids[KyberParameters.kyber768] = BCObjectIdentifiers.kyber768;
+            kyberOids[KyberParameters.kyber1024] = BCObjectIdentifiers.kyber1024;
+            kyberOids[KyberParameters.kyber512_aes] = BCObjectIdentifiers.kyber512_aes;
+            kyberOids[KyberParameters.kyber768_aes] = BCObjectIdentifiers.kyber768_aes;
+            kyberOids[KyberParameters.kyber1024_aes] = BCObjectIdentifiers.kyber1024_aes;   
+            
+            kyberParams[BCObjectIdentifiers.kyber512] = KyberParameters.kyber512;
+            kyberParams[BCObjectIdentifiers.kyber768] = KyberParameters.kyber768;
+            kyberParams[BCObjectIdentifiers.kyber1024] = KyberParameters.kyber1024;
+            kyberParams[BCObjectIdentifiers.kyber512_aes] = KyberParameters.kyber512_aes;
+            kyberParams[BCObjectIdentifiers.kyber768_aes] = KyberParameters.kyber768_aes;
+            kyberParams[BCObjectIdentifiers.kyber1024_aes] = KyberParameters.kyber1024_aes;
+            
+            
+            falconOids[FalconParameters.falcon_512] = BCObjectIdentifiers.falcon_512;
+            falconOids[FalconParameters.falcon_1024] = BCObjectIdentifiers.falcon_1024;
+            
+            falconParams[BCObjectIdentifiers.falcon_512] = FalconParameters.falcon_512;
+            falconParams[BCObjectIdentifiers.falcon_1024] = FalconParameters.falcon_1024;
             
-            sikeParams[BCObjectIdentifiers.sikep434] = SIKEParameters.sikep434;
-            sikeParams[BCObjectIdentifiers.sikep503] = SIKEParameters.sikep503;
-            sikeParams[BCObjectIdentifiers.sikep610] = SIKEParameters.sikep610;
-            sikeParams[BCObjectIdentifiers.sikep751] = SIKEParameters.sikep751;
-            sikeParams[BCObjectIdentifiers.sikep434_compressed] = SIKEParameters.sikep434_compressed;
-            sikeParams[BCObjectIdentifiers.sikep503_compressed] = SIKEParameters.sikep503_compressed;
-            sikeParams[BCObjectIdentifiers.sikep610_compressed] = SIKEParameters.sikep610_compressed;
-            sikeParams[BCObjectIdentifiers.sikep751_compressed] = SIKEParameters.sikep751_compressed;
+            dilithiumOids[DilithiumParameters.Dilithium2] = BCObjectIdentifiers.dilithium2;
+            dilithiumOids[DilithiumParameters.Dilithium3] = BCObjectIdentifiers.dilithium3;
+            dilithiumOids[DilithiumParameters.Dilithium5] = BCObjectIdentifiers.dilithium5;
+            dilithiumOids[DilithiumParameters.Dilithium2Aes] = BCObjectIdentifiers.dilithium2_aes;
+            dilithiumOids[DilithiumParameters.Dilithium3Aes] = BCObjectIdentifiers.dilithium3_aes;
+            dilithiumOids[DilithiumParameters.Dilithium5Aes] = BCObjectIdentifiers.dilithium5_aes;
             
-            sikeOids[SIKEParameters.sikep434] = BCObjectIdentifiers.sikep434;
-            sikeOids[SIKEParameters.sikep503] = BCObjectIdentifiers.sikep503;
-            sikeOids[SIKEParameters.sikep610] = BCObjectIdentifiers.sikep610;
-            sikeOids[SIKEParameters.sikep751] = BCObjectIdentifiers.sikep751;
-            sikeOids[SIKEParameters.sikep434_compressed] = BCObjectIdentifiers.sikep434_compressed;
-            sikeOids[SIKEParameters.sikep503_compressed] = BCObjectIdentifiers.sikep503_compressed;
-            sikeOids[SIKEParameters.sikep610_compressed] = BCObjectIdentifiers.sikep610_compressed;
-            sikeOids[SIKEParameters.sikep751_compressed] = BCObjectIdentifiers.sikep751_compressed;
+            dilithiumParams[BCObjectIdentifiers.dilithium2] = DilithiumParameters.Dilithium2;
+            dilithiumParams[BCObjectIdentifiers.dilithium3] = DilithiumParameters.Dilithium3;
+            dilithiumParams[BCObjectIdentifiers.dilithium5] = DilithiumParameters.Dilithium5;
+            dilithiumParams[BCObjectIdentifiers.dilithium2_aes] = DilithiumParameters.Dilithium2Aes;
+            dilithiumParams[BCObjectIdentifiers.dilithium3_aes] = DilithiumParameters.Dilithium3Aes;
+            dilithiumParams[BCObjectIdentifiers.dilithium5_aes] = DilithiumParameters.Dilithium5Aes;
+
+            bikeParams[BCObjectIdentifiers.bike128] = BikeParameters.bike128;
+            bikeParams[BCObjectIdentifiers.bike192] = BikeParameters.bike192;
+            bikeParams[BCObjectIdentifiers.bike256] = BikeParameters.bike256;
+
+            bikeOids[BikeParameters.bike128] = BCObjectIdentifiers.bike128;
+            bikeOids[BikeParameters.bike192] = BCObjectIdentifiers.bike192;
+            bikeOids[BikeParameters.bike256] = BCObjectIdentifiers.bike256;
+
+            hqcParams[BCObjectIdentifiers.hqc128] = HqcParameters.hqc128;
+            hqcParams[BCObjectIdentifiers.hqc192] = HqcParameters.hqc192;
+            hqcParams[BCObjectIdentifiers.hqc256] = HqcParameters.hqc256;
+
+            hqcOids[HqcParameters.hqc128] = BCObjectIdentifiers.hqc128;
+            hqcOids[HqcParameters.hqc192] = BCObjectIdentifiers.hqc192;
+            hqcOids[HqcParameters.hqc256] = BCObjectIdentifiers.hqc256;
         }
 
-        public static DerObjectIdentifier McElieceOidLookup(CmceParameters parameters)
+        internal static DerObjectIdentifier McElieceOidLookup(CmceParameters parameters)
         {
             return mcElieceOids[parameters];
         }
@@ -127,28 +217,48 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
             return mcElieceParams[oid];
         }
         
-        internal static DerObjectIdentifier SaberOidLookup(SABERParameters parameters)
+        internal static DerObjectIdentifier SaberOidLookup(SaberParameters parameters)
         {
             return saberOids[parameters];
         }
-        internal static SABERParameters SaberParamsLookup(DerObjectIdentifier oid)
+        internal static SaberParameters SaberParamsLookup(DerObjectIdentifier oid)
         {
             return saberParams[oid];
         }
+        internal static KyberParameters KyberParamsLookup(DerObjectIdentifier oid)
+        {
+            return kyberParams[oid];
+        }       
+        internal static DerObjectIdentifier KyberOidLookup(KyberParameters parameters)
+        {
+            return kyberOids[parameters];
+        }
+        internal static FalconParameters FalconParamsLookup(DerObjectIdentifier oid)
+        {
+            return falconParams[oid];
+        }       
+        internal static DerObjectIdentifier FalconOidLookup(FalconParameters parameters)
+        {
+            return falconOids[parameters];
+        }
+        internal static DilithiumParameters DilithiumParamsLookup(DerObjectIdentifier oid)
+        {
+            return dilithiumParams[oid];
+        }       
+        internal static DerObjectIdentifier DilithiumOidLookup(DilithiumParameters parameters)
+        {
+            return dilithiumOids[parameters];
+        }
 
-        internal static DerObjectIdentifier SphincsPlusOidLookup(SPHINCSPlusParameters parameters)
+        internal static DerObjectIdentifier SphincsPlusOidLookup(SphincsPlusParameters parameters)
         {
-            uint pId = SPHINCSPlusParameters.GetID(parameters);
+            int pId = SphincsPlusParameters.GetID(parameters);
 
             if ((pId & 0x020000) == 0x020000)
-            {
                 return BCObjectIdentifiers.sphincsPlus_shake_256;
-            }
 
             if ((pId & 0x05) == 0x05 || (pId & 0x06) == 0x06)
-            {
                 return BCObjectIdentifiers.sphincsPlus_sha_512;
-            }
 
             return BCObjectIdentifiers.sphincsPlus_sha_256;
         }
@@ -162,15 +272,37 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
         {
             return picnicParams[oid];
         }
-        internal static DerObjectIdentifier SikeOidLookup(SIKEParameters parameters)
+
+#pragma warning disable CS0618 // Type or member is obsolete
+        internal static DerObjectIdentifier SikeOidLookup(SikeParameters parameters)
         {
             return sikeOids[parameters];
         }
 
-        internal static SIKEParameters SikeParamsLookup(DerObjectIdentifier oid)
+        internal static SikeParameters SikeParamsLookup(DerObjectIdentifier oid)
         {
             return sikeParams[oid];
         }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+        internal static DerObjectIdentifier BikeOidLookup(BikeParameters parameters)
+        {
+            return bikeOids[parameters];
+        }
+
+        internal static BikeParameters BikeParamsLookup(DerObjectIdentifier oid)
+        {
+            return bikeParams[oid];
+        }
 
+        internal static DerObjectIdentifier HqcOidLookup(HqcParameters parameters)
+        {
+            return hqcOids[parameters];
+        }
+
+        internal static HqcParameters HqcParamsLookup(DerObjectIdentifier oid)
+        {
+            return hqcParams[oid];
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/utils/PrivateKeyFactory.cs b/crypto/src/pqc/crypto/utils/PrivateKeyFactory.cs
index 0fadab855..6ad9bdb0c 100644
--- a/crypto/src/pqc/crypto/utils/PrivateKeyFactory.cs
+++ b/crypto/src/pqc/crypto/utils/PrivateKeyFactory.cs
@@ -9,7 +9,12 @@ using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Pqc.Asn1;
+using Org.BouncyCastle.Pqc.Crypto.Bike;
 using Org.BouncyCastle.Pqc.Crypto.Cmce;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using Org.BouncyCastle.Pqc.Crypto.Falcon;
+using Org.BouncyCastle.Pqc.Crypto.Hqc;
 using Org.BouncyCastle.Pqc.Crypto.Lms;
 using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Pqc.Crypto.Saber;
@@ -19,9 +24,8 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 {
-    public class PrivateKeyFactory
+    public static class PrivateKeyFactory
     {
-
         /// <summary> Create a private key parameter from a PKCS8 PrivateKeyInfo encoding.</summary>
         /// <param name="privateKeyInfoData"> the PrivateKeyInfo encoding</param>
         /// <returns> a suitable private key parameter</returns>
@@ -40,7 +44,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
             return CreateKey(PrivateKeyInfo.GetInstance(new Asn1InputStream(inStr).ReadObject()));
         }
 
-
         /// <summary> Create a private key parameter from the passed in PKCS8 PrivateKeyInfo object.</summary>
         /// <param name="keyInfo"> the PrivateKeyInfo object containing the key material</param>
         /// <returns> a suitable private key parameter</returns>
@@ -61,14 +64,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
                     {
                         byte[] pubEnc = pubKey.GetOctets();
 
-                        return LMSPrivateKeyParameters.GetInstance(Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length),
+                        return LmsPrivateKeyParameters.GetInstance(Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length),
                             Arrays.CopyOfRange(pubEnc, 4, pubEnc.Length));
                     }
 
-                    return LMSPrivateKeyParameters.GetInstance(Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length));
+                    return LmsPrivateKeyParameters.GetInstance(Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length));
                 }
             }
-
             if (algOID.On(BCObjectIdentifiers.pqc_kem_mceliece))
             {
                 CmcePrivateKey cmceKey = CmcePrivateKey.GetInstance(keyInfo.ParsePrivateKey());
@@ -76,20 +78,19 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 
                 return new CmcePrivateKeyParameters(spParams, cmceKey.Delta, cmceKey.C, cmceKey.G, cmceKey.Alpha, cmceKey.S);
             }
-            
             if (algOID.On(BCObjectIdentifiers.sphincsPlus))
             {
                 byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePrivateKey()).GetOctets();
-                SPHINCSPlusParameters spParams = SPHINCSPlusParameters.GetParams((uint)BigInteger.ValueOf(Pack.BE_To_UInt32(keyEnc, 0)).IntValue);
+                SphincsPlusParameters spParams = SphincsPlusParameters.GetParams(BigInteger.ValueOf(Pack.BE_To_UInt32(keyEnc, 0)).IntValue);
 
-                return new SPHINCSPlusPrivateKeyParameters(spParams, Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length));
+                return new SphincsPlusPrivateKeyParameters(spParams, Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length));
             }
             if (algOID.On(BCObjectIdentifiers.pqc_kem_saber))
             {
                 byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePrivateKey()).GetOctets();
-                SABERParameters spParams = PqcUtilities.SaberParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
+                SaberParameters spParams = PqcUtilities.SaberParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
 
-                return new SABERPrivateKeyParameters(spParams, keyEnc);
+                return new SaberPrivateKeyParameters(spParams, keyEnc);
             }
             if (algOID.On(BCObjectIdentifiers.picnic))
             {
@@ -98,17 +99,139 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 
                 return new PicnicPrivateKeyParameters(picnicParams, keyEnc);
             }
+#pragma warning disable CS0618 // Type or member is obsolete
             if (algOID.On(BCObjectIdentifiers.pqc_kem_sike))
             {
                 byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePrivateKey()).GetOctets();
-                SIKEParameters sikeParams = PqcUtilities.SikeParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
+                SikeParameters sikeParams = PqcUtilities.SikeParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
 
-                return new SIKEPrivateKeyParameters(sikeParams, keyEnc);
+                return new SikePrivateKeyParameters(sikeParams, keyEnc);
             }
-            
-            
-            throw new Exception("algorithm identifier in private key not recognised");
+#pragma warning restore CS0618 // Type or member is obsolete
+            if (algOID.On(BCObjectIdentifiers.pqc_kem_bike))
+            {
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePrivateKey()).GetOctets();
+                BikeParameters bikeParams = PqcUtilities.BikeParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
+
+                byte[] h0 = Arrays.CopyOfRange(keyEnc, 0, bikeParams.RByte);
+                byte[] h1 = Arrays.CopyOfRange(keyEnc, bikeParams.RByte, 2 * bikeParams.RByte);
+                byte[] sigma = Arrays.CopyOfRange(keyEnc, 2 * bikeParams.RByte, keyEnc.Length);
+
+                return new BikePrivateKeyParameters(bikeParams, h0, h1, sigma);
+            }
+            if (algOID.On(BCObjectIdentifiers.pqc_kem_hqc))
+            {
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePrivateKey()).GetOctets();
+                HqcParameters hqcParams = PqcUtilities.HqcParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
+
+                return new HqcPrivateKeyParameters(hqcParams, keyEnc);
+            }
+            if (algOID.Equals(BCObjectIdentifiers.kyber512)
+                || algOID.Equals(BCObjectIdentifiers.kyber512_aes)
+                || algOID.Equals(BCObjectIdentifiers.kyber768)
+                || algOID.Equals(BCObjectIdentifiers.kyber768_aes)
+                || algOID.Equals(BCObjectIdentifiers.kyber1024)
+                || algOID.Equals(BCObjectIdentifiers.kyber1024_aes))
+            {
+                Asn1Sequence keyEnc = Asn1Sequence.GetInstance(keyInfo.ParsePrivateKey());
+
+                KyberParameters spParams = PqcUtilities.KyberParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
 
+                int version = DerInteger.GetInstance(keyEnc[0]).Value.IntValue;
+                if (version != 0)
+                {
+                    throw new IOException("unknown private key version: " + version);
+                }
+ 
+                if (keyInfo.PublicKeyData != null)
+                {
+                    Asn1Sequence pubKey = Asn1Sequence.GetInstance(keyInfo.PublicKeyData.GetOctets());
+                    return new KyberPrivateKeyParameters(spParams,
+                        Asn1OctetString.GetInstance(keyEnc[1]).GetDerEncoded(), 
+                        Asn1OctetString.GetInstance(keyEnc[2]).GetOctets(), 
+                        Asn1OctetString.GetInstance(keyEnc[3]).GetOctets(),
+                        Asn1OctetString.GetInstance(pubKey[0]).GetOctets(), // t
+                        Asn1OctetString.GetInstance(pubKey[1]).GetOctets()); // rho
+                }
+                else
+                {
+                    return new KyberPrivateKeyParameters(spParams,
+                        Asn1OctetString.GetInstance(keyEnc[1]).GetOctets(),
+                        Asn1OctetString.GetInstance(keyEnc[2]).GetOctets(),
+                        Asn1OctetString.GetInstance(keyEnc[3]).GetOctets(),
+                        null,
+                        null);
+                }
+            }
+            if (algOID.Equals(BCObjectIdentifiers.dilithium2)
+                || algOID.Equals(BCObjectIdentifiers.dilithium3)
+                || algOID.Equals(BCObjectIdentifiers.dilithium5)
+                || algOID.Equals(BCObjectIdentifiers.dilithium2_aes)
+                || algOID.Equals(BCObjectIdentifiers.dilithium3_aes)
+                || algOID.Equals(BCObjectIdentifiers.dilithium5_aes))
+            {
+                Asn1Sequence keyEnc = Asn1Sequence.GetInstance(keyInfo.ParsePrivateKey());
+
+                DilithiumParameters spParams = PqcUtilities.DilithiumParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
+
+                int version = DerInteger.GetInstance(keyEnc[0]).Value.IntValue;
+                if (version != 0)
+                    throw new IOException("unknown private key version: " + version);
+
+                if (keyInfo.PublicKeyData != null)
+                {
+                    Asn1Sequence pubKey = Asn1Sequence.GetInstance(keyInfo.PublicKeyData.GetOctets());
+                    return new DilithiumPrivateKeyParameters(spParams,
+                        DerBitString.GetInstance(keyEnc[1]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[2]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[3]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[4]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[5]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[6]).GetOctets(),
+                        Asn1OctetString.GetInstance(pubKey[1]).GetOctets()); // encT1
+                }
+                else
+                {
+                    return new DilithiumPrivateKeyParameters(spParams,
+                        DerBitString.GetInstance(keyEnc[1]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[2]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[3]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[4]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[5]).GetOctets(),
+                        DerBitString.GetInstance(keyEnc[6]).GetOctets(),
+                        null);
+                }
+            }
+            if (algOID.Equals(BCObjectIdentifiers.falcon_512) || algOID.Equals(BCObjectIdentifiers.falcon_1024))
+            {
+                Asn1Sequence keyEnc = Asn1Sequence.GetInstance(keyInfo.ParsePrivateKey());
+                FalconParameters spParams = PqcUtilities.FalconParamsLookup(keyInfo.PrivateKeyAlgorithm.Algorithm);
+                    
+                DerBitString publicKeyData = keyInfo.PublicKeyData;
+                int version = DerInteger.GetInstance(keyEnc[0]).Value.IntValue;
+                if (version != 1)
+                    throw new IOException("unknown private key version: " + version);
+
+                if (keyInfo.PublicKeyData != null)
+                {
+                    //ASN1Sequence pubKey = ASN1Sequence.getInstance(keyInfo.getPublicKeyData().getOctets());
+                    return new FalconPrivateKeyParameters(spParams,
+                        Asn1OctetString.GetInstance(keyEnc[1]).GetOctets(),
+                        Asn1OctetString.GetInstance(keyEnc[2]).GetOctets(),
+                        Asn1OctetString.GetInstance(keyEnc[3]).GetOctets(),
+                        publicKeyData.GetOctets()); // encT1
+                }
+                else
+                {
+                    return new FalconPrivateKeyParameters(spParams,
+                        Asn1OctetString.GetInstance(keyEnc[1]).GetOctets(),
+                        Asn1OctetString.GetInstance(keyEnc[2]).GetOctets(),
+                        Asn1OctetString.GetInstance(keyEnc[3]).GetOctets(),
+                        null);
+                }
+            }
+
+            throw new Exception("algorithm identifier in private key not recognised");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/utils/PrivateKeyInfoFactory.cs b/crypto/src/pqc/crypto/utils/PrivateKeyInfoFactory.cs
index c5c3f9e45..53b34b3f4 100644
--- a/crypto/src/pqc/crypto/utils/PrivateKeyInfoFactory.cs
+++ b/crypto/src/pqc/crypto/utils/PrivateKeyInfoFactory.cs
@@ -5,7 +5,12 @@ using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Pqc.Asn1;
+using Org.BouncyCastle.Pqc.Crypto.Bike;
 using Org.BouncyCastle.Pqc.Crypto.Cmce;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using Org.BouncyCastle.Pqc.Crypto.Falcon;
+using Org.BouncyCastle.Pqc.Crypto.Hqc;
 using Org.BouncyCastle.Pqc.Crypto.Lms;
 using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Pqc.Crypto.Saber;
@@ -15,13 +20,8 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 {
-    public class PrivateKeyInfoFactory
+    public static class PrivateKeyInfoFactory
     {
-        private PrivateKeyInfoFactory()
-        {
-
-        }
-
         /// <summary> Create a PrivateKeyInfo representation of a private key.</summary>
         /// <param name="privateKey"> the key to be encoded into the info object.</param>
         /// <returns> the appropriate PrivateKeyInfo</returns>
@@ -38,79 +38,144 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
         /// <exception cref="ArgumentException"> on an error encoding the key</exception>
         public static PrivateKeyInfo CreatePrivateKeyInfo(AsymmetricKeyParameter privateKey, Asn1Set attributes)
         {
-            if (privateKey is LMSPrivateKeyParameters)
+            if (privateKey is LmsPrivateKeyParameters lmsPrivateKeyParameters)
             {
-                LMSPrivateKeyParameters parameters = (LMSPrivateKeyParameters)privateKey;
-
-                byte[] encoding = Composer.Compose().U32Str(1).Bytes(parameters).Build();
-                byte[] pubEncoding = Composer.Compose().U32Str(1).Bytes(parameters.GetPublicKey()).Build();
+                byte[] encoding = Composer.Compose().U32Str(1).Bytes(lmsPrivateKeyParameters).Build();
+                byte[] pubEncoding = Composer.Compose().U32Str(1).Bytes(lmsPrivateKeyParameters.GetPublicKey()).Build();
 
                 AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdAlgHssLmsHashsig);
                 return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes, pubEncoding);
             }
-            if (privateKey is HSSPrivateKeyParameters)
+            if (privateKey is HssPrivateKeyParameters hssPrivateKeyParameters)
             {
-                HSSPrivateKeyParameters parameters = (HSSPrivateKeyParameters)privateKey;
-
-                byte[] encoding = Composer.Compose().U32Str(parameters.L).Bytes(parameters).Build();
-                byte[] pubEncoding = Composer.Compose().U32Str(parameters.L).Bytes(parameters.GetPublicKey().GetLmsPublicKey()).Build();
+                int L = hssPrivateKeyParameters.L;
+                byte[] encoding = Composer.Compose().U32Str(L).Bytes(hssPrivateKeyParameters).Build();
+                byte[] pubEncoding = Composer.Compose().U32Str(L).Bytes(hssPrivateKeyParameters.GetPublicKey().LmsPublicKey).Build();
 
                 AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdAlgHssLmsHashsig);
                 return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes, pubEncoding);
             }
-            if (privateKey is SPHINCSPlusPrivateKeyParameters)
+            if (privateKey is SphincsPlusPrivateKeyParameters sphincsPlusPrivateKeyParameters)
             {
-                SPHINCSPlusPrivateKeyParameters parameters = (SPHINCSPlusPrivateKeyParameters)privateKey;
+                byte[] encoding = sphincsPlusPrivateKeyParameters.GetEncoded();
+                byte[] pubEncoding = sphincsPlusPrivateKeyParameters.GetEncodedPublicKey();
 
-                byte[] encoding = parameters.GetEncoded();
-                byte[] pubEncoding = parameters.GetEncodedPublicKey();
-
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.SphincsPlusOidLookup(parameters.GetParameters()));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.SphincsPlusOidLookup(sphincsPlusPrivateKeyParameters.Parameters));
                 return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes, pubEncoding);
             }
-            if (privateKey is CmcePrivateKeyParameters)
+            if (privateKey is CmcePrivateKeyParameters cmcePrivateKeyParameters)
             {
-                CmcePrivateKeyParameters parameters = (CmcePrivateKeyParameters) privateKey;
-
-                byte[] encoding = parameters.GetEncoded();
+                byte[] encoding = cmcePrivateKeyParameters.GetEncoded();
                 AlgorithmIdentifier algorithmIdentifier =
-                    new AlgorithmIdentifier(PqcUtilities.McElieceOidLookup(parameters.Parameters));
+                    new AlgorithmIdentifier(PqcUtilities.McElieceOidLookup(cmcePrivateKeyParameters.Parameters));
 
-                CmcePublicKey CmcePub = new CmcePublicKey(parameters.ReconstructPublicKey());
-                CmcePrivateKey CmcePriv = new CmcePrivateKey(0, parameters.Delta, parameters.C, parameters.G,
-                    parameters.Alpha, parameters.S, CmcePub);
+                CmcePublicKey CmcePub = new CmcePublicKey(cmcePrivateKeyParameters.ReconstructPublicKey());
+                CmcePrivateKey CmcePriv = new CmcePrivateKey(0, cmcePrivateKeyParameters.Delta,
+                    cmcePrivateKeyParameters.C, cmcePrivateKeyParameters.G, cmcePrivateKeyParameters.Alpha,
+                    cmcePrivateKeyParameters.S, CmcePub);
                 return new PrivateKeyInfo(algorithmIdentifier, CmcePriv, attributes);
             }
-            if (privateKey is SABERPrivateKeyParameters)
+            if (privateKey is SaberPrivateKeyParameters saberPrivateKeyParameters)
             {
-                SABERPrivateKeyParameters parameters = (SABERPrivateKeyParameters)privateKey;
+                byte[] encoding = saberPrivateKeyParameters.GetEncoded();
 
-                byte[] encoding = parameters.GetEncoded();
-
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.SaberOidLookup(parameters.GetParameters()));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.SaberOidLookup(saberPrivateKeyParameters.Parameters));
 
                 return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes);
             }
-            if (privateKey is PicnicPrivateKeyParameters)
+            if (privateKey is PicnicPrivateKeyParameters picnicPrivateKeyParameters)
             {
-                PicnicPrivateKeyParameters parameters = (PicnicPrivateKeyParameters)privateKey;
+                byte[] encoding = picnicPrivateKeyParameters.GetEncoded();
 
-                byte[] encoding = parameters.GetEncoded();
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.PicnicOidLookup(picnicPrivateKeyParameters.Parameters));
+                return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes);
+            }
+#pragma warning disable CS0618 // Type or member is obsolete
+            if (privateKey is SikePrivateKeyParameters sikePrivateKeyParameters)
+            {
+                byte[] encoding = sikePrivateKeyParameters.GetEncoded();
 
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.PicnicOidLookup(parameters.Parameters));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.SikeOidLookup(sikePrivateKeyParameters.Parameters));
                 return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes);
             }
-            if (privateKey is SIKEPrivateKeyParameters)
+#pragma warning restore CS0618 // Type or member is obsolete
+            if (privateKey is FalconPrivateKeyParameters falconPrivateKeyParameters)
             {
-                SIKEPrivateKeyParameters parameters = (SIKEPrivateKeyParameters)privateKey;
+                Asn1EncodableVector v = new Asn1EncodableVector();
 
-                byte[] encoding = parameters.GetEncoded();
+                v.Add(new DerInteger(1));
+                v.Add(new DerOctetString(falconPrivateKeyParameters.GetSpolyLittleF()));
+                v.Add(new DerOctetString(falconPrivateKeyParameters.GetG()));
+                v.Add(new DerOctetString(falconPrivateKeyParameters.GetSpolyBigF()));
+
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.FalconOidLookup(falconPrivateKeyParameters.Parameters));
+
+                return new PrivateKeyInfo(algorithmIdentifier, new DerSequence(v), attributes,
+                    falconPrivateKeyParameters.GetPublicKey());
+            }
+            if (privateKey is KyberPrivateKeyParameters kyberPrivateKeyParameters)
+            {
+                Asn1EncodableVector v = new Asn1EncodableVector();
 
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.SikeOidLookup(parameters.GetParameters()));
+                v.Add(new DerInteger(0));
+                v.Add(new DerOctetString(kyberPrivateKeyParameters.S));
+                v.Add(new DerOctetString(kyberPrivateKeyParameters.Hpk));
+                v.Add(new DerOctetString(kyberPrivateKeyParameters.Nonce));
+
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.KyberOidLookup(kyberPrivateKeyParameters.Parameters));
+
+                Asn1EncodableVector vPub = new Asn1EncodableVector();
+                vPub.Add(new DerOctetString(kyberPrivateKeyParameters.T));
+                vPub.Add(new DerOctetString(kyberPrivateKeyParameters.Rho));
+
+                return new PrivateKeyInfo(algorithmIdentifier, new DerSequence(v), attributes,
+                    new DerSequence(vPub).GetEncoded());
+            }
+            if (privateKey is DilithiumPrivateKeyParameters dilithiumPrivateKeyParameters)
+            {
+                Asn1EncodableVector v = new Asn1EncodableVector();
+
+                v.Add(new DerInteger(0));
+                v.Add(new DerBitString(dilithiumPrivateKeyParameters.Rho));
+                v.Add(new DerBitString(dilithiumPrivateKeyParameters.K));
+                v.Add(new DerBitString(dilithiumPrivateKeyParameters.Tr));
+                v.Add(new DerBitString(dilithiumPrivateKeyParameters.S1));
+                v.Add(new DerBitString(dilithiumPrivateKeyParameters.S2));
+                v.Add(new DerBitString(dilithiumPrivateKeyParameters.T0));
+
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.DilithiumOidLookup(dilithiumPrivateKeyParameters.Parameters));
+
+                Asn1EncodableVector vPub = new Asn1EncodableVector();
+                vPub.Add(new DerOctetString(dilithiumPrivateKeyParameters.Rho));
+                vPub.Add(new DerOctetString(dilithiumPrivateKeyParameters.T1));
+
+                return new PrivateKeyInfo(algorithmIdentifier, new DerSequence(v), attributes,
+                    new DerSequence(vPub).GetEncoded());
+            }
+            if (privateKey is BikePrivateKeyParameters bikePrivateKeyParameters)
+            {
+                byte[] encoding = bikePrivateKeyParameters.GetEncoded();
+
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.BikeOidLookup(bikePrivateKeyParameters.Parameters));
+                return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes);
+            }
+            else if (privateKey is HqcPrivateKeyParameters hqcPrivateKeyParameters)
+            {
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.HqcOidLookup(hqcPrivateKeyParameters.Parameters));
+                byte[] encoding = hqcPrivateKeyParameters.PrivateKey;
                 return new PrivateKeyInfo(algorithmIdentifier, new DerOctetString(encoding), attributes);
             }
 
             throw new ArgumentException("Class provided is not convertible: " + Platform.GetTypeName(privateKey));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/utils/PublicKeyFactory.cs b/crypto/src/pqc/crypto/utils/PublicKeyFactory.cs
index 3610711ff..9eea279b1 100644
--- a/crypto/src/pqc/crypto/utils/PublicKeyFactory.cs
+++ b/crypto/src/pqc/crypto/utils/PublicKeyFactory.cs
@@ -4,12 +4,19 @@ using System.IO;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.BC;
+using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Pqc.Asn1;
+using Org.BouncyCastle.Pqc.Crypto.Bike;
 using Org.BouncyCastle.Pqc.Crypto.Cmce;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using Org.BouncyCastle.Pqc.Crypto.Falcon;
+using Org.BouncyCastle.Pqc.Crypto.Hqc;
+using Org.BouncyCastle.Pqc.Crypto.Lms;
 using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Pqc.Crypto.Saber;
 using Org.BouncyCastle.Pqc.Crypto.Sike;
@@ -18,63 +25,101 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 {
-    public class PublicKeyFactory
+    public static class PublicKeyFactory
     {
-        private static Dictionary<DerObjectIdentifier, SubjectPublicKeyInfoConverter> converters = new Dictionary<DerObjectIdentifier, SubjectPublicKeyInfoConverter>();
-
+        private static Dictionary<DerObjectIdentifier, SubjectPublicKeyInfoConverter> Converters =
+            new Dictionary<DerObjectIdentifier, SubjectPublicKeyInfoConverter>();
 
         static PublicKeyFactory()
         {
-            converters[BCObjectIdentifiers.sphincsPlus] = new SPHINCSPlusConverter();
-            converters[BCObjectIdentifiers.sphincsPlus_shake_256] = new SPHINCSPlusConverter();
-            converters[BCObjectIdentifiers.sphincsPlus_sha_256] = new SPHINCSPlusConverter();
-            converters[BCObjectIdentifiers.sphincsPlus_sha_512] = new SPHINCSPlusConverter();
+            Converters[PkcsObjectIdentifiers.IdAlgHssLmsHashsig] = new LmsConverter();
+
+            Converters[BCObjectIdentifiers.sphincsPlus] = new SphincsPlusConverter();
+            Converters[BCObjectIdentifiers.sphincsPlus_shake_256] = new SphincsPlusConverter();
+            Converters[BCObjectIdentifiers.sphincsPlus_sha_256] = new SphincsPlusConverter();
+            Converters[BCObjectIdentifiers.sphincsPlus_sha_512] = new SphincsPlusConverter();
             
-            converters[BCObjectIdentifiers.mceliece348864_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece348864f_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece460896_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece460896f_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece6688128_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece6688128f_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece6960119_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece6960119f_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece8192128_r3] = new CmceConverter();
-            converters[BCObjectIdentifiers.mceliece8192128f_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece348864_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece348864f_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece460896_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece460896f_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece6688128_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece6688128f_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece6960119_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece6960119f_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece8192128_r3] = new CmceConverter();
+            Converters[BCObjectIdentifiers.mceliece8192128f_r3] = new CmceConverter();
            
-            converters[BCObjectIdentifiers.lightsaberkem128r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.saberkem128r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.firesaberkem128r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.lightsaberkem192r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.saberkem192r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.firesaberkem192r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.lightsaberkem256r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.saberkem256r3] = new SaberConverter();
-            converters[BCObjectIdentifiers.firesaberkem256r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.lightsaberkem128r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.saberkem128r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.firesaberkem128r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.lightsaberkem192r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.saberkem192r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.firesaberkem192r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.lightsaberkem256r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.saberkem256r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.firesaberkem256r3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.ulightsaberkemr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.usaberkemr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.ufiresaberkemr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.lightsaberkem90sr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.saberkem90sr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.firesaberkem90sr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.ulightsaberkem90sr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.usaberkem90sr3] = new SaberConverter();
+            Converters[BCObjectIdentifiers.ufiresaberkem90sr3] = new SaberConverter();
+            
+            Converters[BCObjectIdentifiers.picnic] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl1fs] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl1ur] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl3fs] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl3ur] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl5fs] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl5ur] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnic3l1] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnic3l3] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnic3l5] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl1full] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl3full] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.picnicl5full] = new PicnicConverter();
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            Converters[BCObjectIdentifiers.sikep434] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep503] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep610] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep751] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep434_compressed] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep503_compressed] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep610_compressed] = new SikeConverter();
+            Converters[BCObjectIdentifiers.sikep751_compressed] = new SikeConverter();
+#pragma warning restore CS0618 // Type or member is obsolete
+
+            Converters[BCObjectIdentifiers.dilithium2] = new DilithiumConverter();
+            Converters[BCObjectIdentifiers.dilithium3] = new DilithiumConverter();
+            Converters[BCObjectIdentifiers.dilithium5] = new DilithiumConverter();
+            Converters[BCObjectIdentifiers.dilithium2_aes] = new DilithiumConverter();
+            Converters[BCObjectIdentifiers.dilithium3_aes] = new DilithiumConverter();
+            Converters[BCObjectIdentifiers.dilithium5_aes] = new DilithiumConverter();
             
-            converters[BCObjectIdentifiers.picnic] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl1fs] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl1ur] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl3fs] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl3ur] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl5fs] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl5ur] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnic3l1] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnic3l3] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnic3l5] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl1full] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl3full] = new PicnicConverter();
-            converters[BCObjectIdentifiers.picnicl5full] = new PicnicConverter();
+            Converters[BCObjectIdentifiers.falcon_512] = new FalconConverter();
+            Converters[BCObjectIdentifiers.falcon_1024] = new FalconConverter();
             
-            converters[BCObjectIdentifiers.sikep434] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep503] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep610] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep751] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep434_compressed] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep503_compressed] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep610_compressed] = new SikeConverter();
-            converters[BCObjectIdentifiers.sikep751_compressed] = new SikeConverter();
+            Converters[BCObjectIdentifiers.kyber512] = new KyberConverter();
+            Converters[BCObjectIdentifiers.kyber512_aes] = new KyberConverter();
+            Converters[BCObjectIdentifiers.kyber768] = new KyberConverter();
+            Converters[BCObjectIdentifiers.kyber768_aes] = new KyberConverter();
+            Converters[BCObjectIdentifiers.kyber1024] = new KyberConverter();
+            Converters[BCObjectIdentifiers.kyber1024_aes] = new KyberConverter();
+
+            Converters[BCObjectIdentifiers.bike128] = new BikeConverter();
+            Converters[BCObjectIdentifiers.bike192] = new BikeConverter();
+            Converters[BCObjectIdentifiers.bike256] = new BikeConverter();
+
+            Converters[BCObjectIdentifiers.hqc128] = new HqcConverter();
+            Converters[BCObjectIdentifiers.hqc192] = new HqcConverter();
+            Converters[BCObjectIdentifiers.hqc256] = new HqcConverter();
         }
-        
+
         /// <summary> Create a public key from a SubjectPublicKeyInfo encoding</summary>
         /// <param name="keyInfoData"> the SubjectPublicKeyInfo encoding</param>
         /// <returns> the appropriate key parameter</returns>
@@ -107,10 +152,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
         /// <param name="defaultParams"> default parameters that might be needed.</param>
         /// <returns> the appropriate key parameter</returns>
         /// <exception cref="IOException"> on an error decoding the key</exception>
-        public static AsymmetricKeyParameter CreateKey(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+        public static AsymmetricKeyParameter CreateKey(SubjectPublicKeyInfo keyInfo, object defaultParams)
         {
             AlgorithmIdentifier algId = keyInfo.AlgorithmID;
-            SubjectPublicKeyInfoConverter converter = (SubjectPublicKeyInfoConverter)converters[algId.Algorithm];
+            SubjectPublicKeyInfoConverter converter = (SubjectPublicKeyInfoConverter)Converters[algId.Algorithm];
 
             if (converter != null)
             {
@@ -123,26 +168,49 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
         }
         private abstract class SubjectPublicKeyInfoConverter
         {
-            internal abstract AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams);
+            internal abstract AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams);
         }
-        
-        private class SPHINCSPlusConverter
+
+        private class LmsConverter
+        :   SubjectPublicKeyInfoConverter
+        {
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
+            {
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
+
+                if (Pack.BE_To_UInt32(keyEnc, 0) == 1U)
+                {
+                    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);
+                }
+            }
+        }
+
+        private class SphincsPlusConverter
             : SubjectPublicKeyInfoConverter
         {
-            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
             {
-            byte[] keyEnc = DerOctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
 
-            SPHINCSPlusParameters spParams = SPHINCSPlusParameters.GetParams((uint)BigInteger.ValueOf(Pack.BE_To_UInt32(keyEnc, 0)).IntValue);
+                SphincsPlusParameters spParams = SphincsPlusParameters.GetParams((int)Pack.BE_To_UInt32(keyEnc, 0));
 
-            return new SPHINCSPlusPublicKeyParameters(spParams, Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length));
+                return new SphincsPlusPublicKeyParameters(spParams, Arrays.CopyOfRange(keyEnc, 4, keyEnc.Length));
             }
         }
         
         private class CmceConverter
             : SubjectPublicKeyInfoConverter
         {
-            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
             {
                 byte[] keyEnc = CmcePublicKey.GetInstance(keyInfo.ParsePublicKey()).T;
 
@@ -155,40 +223,142 @@ namespace Org.BouncyCastle.Pqc.Crypto.Utilities
         private class SaberConverter
             : SubjectPublicKeyInfoConverter
         {
-            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
             {
-                byte[] keyEnc = DerOctetString.GetInstance(
+                byte[] keyEnc = Asn1OctetString.GetInstance(
                     DerSequence.GetInstance(keyInfo.ParsePublicKey())[0]).GetOctets();
 
-                SABERParameters saberParams = PqcUtilities.SaberParamsLookup(keyInfo.AlgorithmID.Algorithm);
+                SaberParameters saberParams = PqcUtilities.SaberParamsLookup(keyInfo.AlgorithmID.Algorithm);
 
-                    return new SABERPublicKeyParameters(saberParams, keyEnc);
+                return new SaberPublicKeyParameters(saberParams, keyEnc);
             }
         }
         
         private class PicnicConverter
             : SubjectPublicKeyInfoConverter
         {
-            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
             {
-                byte[] keyEnc = DerOctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
 
                 PicnicParameters picnicParams = PqcUtilities.PicnicParamsLookup(keyInfo.AlgorithmID.Algorithm);
 
                 return new PicnicPublicKeyParameters(picnicParams, keyEnc);
             }
         }
+        [Obsolete("Will be removed")]
         private class SikeConverter
             : SubjectPublicKeyInfoConverter
         {
-            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
+            {
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
+
+                SikeParameters sikeParams = PqcUtilities.SikeParamsLookup(keyInfo.AlgorithmID.Algorithm);
+
+                return new SikePublicKeyParameters(sikeParams, keyEnc);
+            }
+        }
+        private class DilithiumConverter
+            : SubjectPublicKeyInfoConverter
+        {
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
+            {
+                DilithiumParameters dilithiumParams = PqcUtilities.DilithiumParamsLookup(keyInfo.AlgorithmID.Algorithm);
+
+                Asn1Object obj = keyInfo.ParsePublicKey();
+                if (obj is Asn1Sequence)
+                {
+                    Asn1Sequence keySeq = Asn1Sequence.GetInstance(obj);
+
+                    return new DilithiumPublicKeyParameters(dilithiumParams,
+                        Asn1OctetString.GetInstance(keySeq[0]).GetOctets(),
+                        Asn1OctetString.GetInstance(keySeq[1]).GetOctets());
+                }
+                else
+                {
+                    byte[] encKey = Asn1OctetString.GetInstance(obj).GetOctets();
+
+                    return new DilithiumPublicKeyParameters(dilithiumParams, encKey);
+                }
+            }
+        }
+
+        private class KyberConverter
+            : SubjectPublicKeyInfoConverter
+        {
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
+            {
+                KyberParameters kyberParameters = PqcUtilities.KyberParamsLookup(keyInfo.AlgorithmID.Algorithm);
+
+                Asn1Object obj = keyInfo.ParsePublicKey();
+                if (obj is Asn1Sequence)
+                {
+                    Asn1Sequence keySeq = Asn1Sequence.GetInstance(obj);
+
+                    return new KyberPublicKeyParameters(kyberParameters,
+                        Asn1OctetString.GetInstance(keySeq[0]).GetOctets(),
+                        Asn1OctetString.GetInstance(keySeq[1]).GetOctets());
+                }
+                else
+                {
+                    byte[] encKey = Asn1OctetString.GetInstance(obj).GetOctets();
+
+                    return new KyberPublicKeyParameters(kyberParameters, encKey);
+                }
+            }
+        }
+
+        private class FalconConverter 
+            : SubjectPublicKeyInfoConverter
+        {
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
+            {
+                FalconParameters falconParams = PqcUtilities.FalconParamsLookup(keyInfo.AlgorithmID.Algorithm);
+
+                Asn1Object obj = keyInfo.ParsePublicKey();
+                if (obj is Asn1Sequence)
+                {
+                    byte[] keyEnc = Asn1OctetString.GetInstance(Asn1Sequence.GetInstance(obj)[0]).GetOctets();
+
+                    return new FalconPublicKeyParameters(falconParams, keyEnc);
+                }
+                else
+                {
+                    // header byte + h
+                    byte[] keyEnc = Asn1OctetString.GetInstance(obj).GetOctets();
+
+                    if (keyEnc[0] != (byte)(0x00 + falconParams.LogN))
+                    {
+                        throw new ArgumentException("byte[] enc of Falcon h value not tagged correctly");
+                    }
+                    return new FalconPublicKeyParameters(falconParams, Arrays.CopyOfRange(keyEnc, 1, keyEnc.Length));
+                }
+            }
+        }
+
+        private class BikeConverter: SubjectPublicKeyInfoConverter
+        {
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
+            {
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
+
+                BikeParameters bikeParams = PqcUtilities.BikeParamsLookup(keyInfo.AlgorithmID.Algorithm);
+
+                return new BikePublicKeyParameters(bikeParams, keyEnc);
+            }
+        }
+
+        private class HqcConverter : SubjectPublicKeyInfoConverter
+        {
+            internal override AsymmetricKeyParameter GetPublicKeyParameters(SubjectPublicKeyInfo keyInfo, object defaultParams)
             {
-                byte[] keyEnc = DerOctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
+                byte[] keyEnc = Asn1OctetString.GetInstance(keyInfo.ParsePublicKey()).GetOctets();
 
-                SIKEParameters sikeParams = PqcUtilities.SikeParamsLookup(keyInfo.AlgorithmID.Algorithm);
+                HqcParameters hqcParams = PqcUtilities.HqcParamsLookup(keyInfo.AlgorithmID.Algorithm);
 
-                return new SIKEPublicKeyParameters(sikeParams, keyEnc);
+                return new HqcPublicKeyParameters(hqcParams, keyEnc);
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/pqc/crypto/utils/SecretWithEncapsulationImpl.cs b/crypto/src/pqc/crypto/utils/SecretWithEncapsulationImpl.cs
index 17057ebfd..c4d3eb44f 100644
--- a/crypto/src/pqc/crypto/utils/SecretWithEncapsulationImpl.cs
+++ b/crypto/src/pqc/crypto/utils/SecretWithEncapsulationImpl.cs
@@ -1,59 +1,59 @@
 using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 {
     public class SecretWithEncapsulationImpl
-            : ISecretWithEncapsulation
-        {
-            private volatile bool hasBeenDestroyed = false;
+        : ISecretWithEncapsulation
+    {
+        private volatile bool hasBeenDestroyed = false;
 
-            private byte[] sessionKey;
-            private byte[] cipher_text;
+        private byte[] sessionKey;
+        private byte[] cipher_text;
 
-            public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
-            {
-                this.sessionKey = sessionKey;
-                this.cipher_text = cipher_text;
-            }
+        public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
+        {
+            this.sessionKey = sessionKey;
+            this.cipher_text = cipher_text;
+        }
 
-            public byte[] GetSecret()
-            {
-                CheckDestroyed();
+        public byte[] GetSecret()
+        {
+            CheckDestroyed();
 
-                return Arrays.Clone(sessionKey);
-            }
+            return Arrays.Clone(sessionKey);
+        }
 
-            public byte[] GetEncapsulation()
-            {
-                CheckDestroyed();
+        public byte[] GetEncapsulation()
+        {
+            CheckDestroyed();
 
-                return Arrays.Clone(cipher_text);
-            }
+            return Arrays.Clone(cipher_text);
+        }
 
-            public void Dispose()
+        public void Dispose()
+        {
+            if (!hasBeenDestroyed)
             {
-                if (!hasBeenDestroyed)
-                {
-                    hasBeenDestroyed = true;
-                    Arrays.Clear(sessionKey);
-                    Arrays.Clear(cipher_text);
-                }
+                hasBeenDestroyed = true;
+                Arrays.Clear(sessionKey);
+                Arrays.Clear(cipher_text);
             }
+        }
 
-            public bool IsDestroyed()
-            {
-                return hasBeenDestroyed;
-            }
+        public bool IsDestroyed()
+        {
+            return hasBeenDestroyed;
+        }
 
-            void CheckDestroyed()
+        void CheckDestroyed()
+        {
+            if (IsDestroyed())
             {
-                if (IsDestroyed())
-                {
-                    throw new Exception("data has been destroyed");
-                }
+                throw new Exception("data has been destroyed");
             }
         }
-    
-}
\ No newline at end of file
+    }
+}
diff --git a/crypto/src/pqc/crypto/utils/SubjectPublicKeyInfoFactory.cs b/crypto/src/pqc/crypto/utils/SubjectPublicKeyInfoFactory.cs
index 0cf80bbe3..b88834e50 100644
--- a/crypto/src/pqc/crypto/utils/SubjectPublicKeyInfoFactory.cs
+++ b/crypto/src/pqc/crypto/utils/SubjectPublicKeyInfoFactory.cs
@@ -1,11 +1,17 @@
 using System;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Pqc.Asn1;
+using Org.BouncyCastle.Pqc.Crypto.Bike;
 using Org.BouncyCastle.Pqc.Crypto.Cmce;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using Org.BouncyCastle.Pqc.Crypto.Falcon;
+using Org.BouncyCastle.Pqc.Crypto.Hqc;
+using Org.BouncyCastle.Pqc.Crypto.Lms;
 using Org.BouncyCastle.Pqc.Crypto.Picnic;
 using Org.BouncyCastle.Pqc.Crypto.Saber;
 using Org.BouncyCastle.Pqc.Crypto.Sike;
@@ -14,114 +20,129 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.Utilities
 {
-    
     /// <summary>
     /// A factory to produce Public Key Info Objects.
     /// </summary>
-    public class SubjectPublicKeyInfoFactory
+    public static class SubjectPublicKeyInfoFactory
     {
-        private SubjectPublicKeyInfoFactory()
-        {
-        }
-
         /// <summary>
         /// Create a Subject Public Key Info object for a given public key.
         /// </summary>
         /// <param name="publicKey">One of ElGammalPublicKeyParameters, DSAPublicKeyParameter, DHPublicKeyParameters, RsaKeyParameters or ECPublicKeyParameters</param>
         /// <returns>A subject public key info object.</returns>
         /// <exception cref="Exception">Throw exception if object provided is not one of the above.</exception>
-        public static SubjectPublicKeyInfo CreateSubjectPublicKeyInfo(
-            AsymmetricKeyParameter publicKey)
+        public static SubjectPublicKeyInfo CreateSubjectPublicKeyInfo(AsymmetricKeyParameter publicKey)
         {
             if (publicKey == null)
                 throw new ArgumentNullException("publicKey");
             if (publicKey.IsPrivate)
                 throw new ArgumentException("Private key passed - public key expected.", "publicKey");
-            
-            if (publicKey is SPHINCSPlusPublicKeyParameters)
+
+            if (publicKey is LmsPublicKeyParameters lmsPublicKeyParameters)
             {
-                SPHINCSPlusPublicKeyParameters parameters = (SPHINCSPlusPublicKeyParameters)publicKey;
+                byte[] encoding = Composer.Compose().U32Str(1).Bytes(lmsPublicKeyParameters).Build();
 
-                byte[] encoding = parameters.GetEncoded();
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdAlgHssLmsHashsig);
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
+            }
+            if (publicKey is HssPublicKeyParameters hssPublicKeyParameters)
+            {
+                int L = hssPublicKeyParameters.L;
+                byte[] encoding = Composer.Compose().U32Str(L).Bytes(hssPublicKeyParameters.LmsPublicKey).Build();
 
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.SphincsPlusOidLookup(parameters.GetParameters()));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdAlgHssLmsHashsig);
                 return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
             }
-            if (publicKey is CmcePublicKeyParameters)
+            if (publicKey is SphincsPlusPublicKeyParameters sphincsPlusPublicKeyParameters)
             {
-                CmcePublicKeyParameters key = (CmcePublicKeyParameters)publicKey;
+                byte[] encoding = sphincsPlusPublicKeyParameters.GetEncoded();
 
-                byte[] encoding = key.GetEncoded();
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.SphincsPlusOidLookup(sphincsPlusPublicKeyParameters.Parameters));
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
+            }
+            if (publicKey is CmcePublicKeyParameters cmcePublicKeyParameters)
+            {
+                byte[] encoding = cmcePublicKeyParameters.GetEncoded();
 
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.McElieceOidLookup(key.Parameters));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.McElieceOidLookup(cmcePublicKeyParameters.Parameters));
 
                 // https://datatracker.ietf.org/doc/draft-uni-qsckeys/
                 return new SubjectPublicKeyInfo(algorithmIdentifier, new CmcePublicKey(encoding));
             }
-            if (publicKey is SABERPublicKeyParameters)
+            if (publicKey is SaberPublicKeyParameters saberPublicKeyParameters)
             {
-                SABERPublicKeyParameters parameters = (SABERPublicKeyParameters)publicKey;
-
-                byte[] encoding = parameters.GetEncoded();
+                byte[] encoding = saberPublicKeyParameters.GetEncoded();
 
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.SaberOidLookup(parameters.GetParameters()));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.SaberOidLookup(saberPublicKeyParameters.Parameters));
 
                 // https://datatracker.ietf.org/doc/draft-uni-qsckeys/
                 return new SubjectPublicKeyInfo(algorithmIdentifier, new DerSequence(new DerOctetString(encoding)));
             }
-            if (publicKey is PicnicPublicKeyParameters)
+            if (publicKey is PicnicPublicKeyParameters picnicPublicKeyParameters)
             {
-                PicnicPublicKeyParameters parameters = (PicnicPublicKeyParameters)publicKey;
+                byte[] encoding = picnicPublicKeyParameters.GetEncoded();
 
-                byte[] encoding = parameters.GetEncoded();
-
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.PicnicOidLookup(parameters.Parameters));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.PicnicOidLookup(picnicPublicKeyParameters.Parameters));
                 return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
             }
-            if (publicKey is SIKEPublicKeyParameters)
+#pragma warning disable CS0618 // Type or member is obsolete
+            if (publicKey is SikePublicKeyParameters sikePublicKeyParameters)
             {
-                SIKEPublicKeyParameters parameters = (SIKEPublicKeyParameters)publicKey;
-
-                byte[] encoding = parameters.GetEncoded();
+                byte[] encoding = sikePublicKeyParameters.GetEncoded();
 
-                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PqcUtilities.SikeOidLookup(parameters.GetParameters()));
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.SikeOidLookup(sikePublicKeyParameters.Parameters));
                 return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
             }
-            
-            throw new ArgumentException("Class provided no convertible: " + Platform.GetTypeName(publicKey));
-
-        }
-        
-        private static void ExtractBytes(
-            byte[]		encKey,
-            int			offset,
-            BigInteger	bI)
-        {
-            byte[] val = bI.ToByteArray();
-            int n = (bI.BitLength + 7) / 8;
+#pragma warning restore CS0618 // Type or member is obsolete
+            if (publicKey is FalconPublicKeyParameters falconPublicKeyParameters)
+            {
+                byte[] encoding = falconPublicKeyParameters.GetEncoded();
 
-            for (int i = 0; i < n; ++i)
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.FalconOidLookup(falconPublicKeyParameters.Parameters));
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DerSequence(new DerOctetString(encoding)));
+            }
+            if (publicKey is KyberPublicKeyParameters kyberPublicKeyParameters)
             {
-                encKey[offset + i] = val[val.Length - 1 - i];
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.KyberOidLookup(kyberPublicKeyParameters.Parameters));
+                Asn1EncodableVector v = new Asn1EncodableVector();
+                v.Add(new DerOctetString(kyberPublicKeyParameters.T));
+                v.Add(new DerOctetString(kyberPublicKeyParameters.Rho));
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DerSequence(v));
             }
-        }
-
-
-        private static void ExtractBytes(byte[] encKey, int size, int offSet, BigInteger bI)
-        {
-            byte[] val = bI.ToByteArray();
-            if (val.Length < size)
+            if (publicKey is DilithiumPublicKeyParameters dilithiumPublicKeyParameters)
             {
-                byte[] tmp = new byte[size];
-                Array.Copy(val, 0, tmp, tmp.Length - val.Length, val.Length);
-                val = tmp;
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.DilithiumOidLookup(dilithiumPublicKeyParameters.Parameters));
+            
+                return new SubjectPublicKeyInfo(algorithmIdentifier,
+                    new DerOctetString(Arrays.Concatenate(dilithiumPublicKeyParameters.Rho, dilithiumPublicKeyParameters.T1)));
             }
+            if (publicKey is BikePublicKeyParameters bikePublicKeyParameters)
+            { 
+                byte[] encoding = bikePublicKeyParameters.GetEncoded();
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.BikeOidLookup(bikePublicKeyParameters.Parameters));
 
-            for (int i = 0; i != size; i++)
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
+            }
+            if (publicKey is HqcPublicKeyParameters hqcPublicKeyParameters)
             {
-                encKey[offSet + i] = val[val.Length - 1 - i];
+                byte[] encoding = hqcPublicKeyParameters.GetEncoded();
+
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(
+                    PqcUtilities.HqcOidLookup(hqcPublicKeyParameters.Parameters));
+
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DerOctetString(encoding));
             }
-        }
 
+            throw new ArgumentException("Class provided no convertible: " + Platform.GetTypeName(publicKey));
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/security/CipherUtilities.cs b/crypto/src/security/CipherUtilities.cs
index 8ed5d8d41..11bf45680 100644
--- a/crypto/src/security/CipherUtilities.cs
+++ b/crypto/src/security/CipherUtilities.cs
@@ -118,9 +118,9 @@ namespace Org.BouncyCastle.Security
         static CipherUtilities()
         {
             // Signal to obfuscation tools not to change enum constants
-            ((CipherAlgorithm)Enums.GetArbitraryValue(typeof(CipherAlgorithm))).ToString();
-            ((CipherMode)Enums.GetArbitraryValue(typeof(CipherMode))).ToString();
-            ((CipherPadding)Enums.GetArbitraryValue(typeof(CipherPadding))).ToString();
+            Enums.GetArbitraryValue<CipherAlgorithm>().ToString();
+            Enums.GetArbitraryValue<CipherMode>().ToString();
+            Enums.GetArbitraryValue<CipherPadding>().ToString();
 
             // TODO Flesh out the list of aliases
 
@@ -246,7 +246,7 @@ namespace Org.BouncyCastle.Security
 
             Algorithms["GOST"] = "GOST28147";
             Algorithms["GOST-28147"] = "GOST28147";
-            Algorithms[CryptoProObjectIdentifiers.GostR28147Cbc.Id] = "GOST28147/CBC/PKCS7PADDING";
+            Algorithms[CryptoProObjectIdentifiers.GostR28147Gcfb.Id] = "GOST28147/CBC/PKCS7PADDING";
 
             Algorithms["RC5-32"] = "RC5";
 
@@ -358,7 +358,7 @@ namespace Org.BouncyCastle.Security
             CipherAlgorithm cipherAlgorithm;
             try
             {
-                cipherAlgorithm = (CipherAlgorithm)Enums.GetEnumValue(typeof(CipherAlgorithm), algorithmName);
+                cipherAlgorithm = Enums.GetEnumValue<CipherAlgorithm>(algorithmName);
             }
             catch (ArgumentException)
             {
@@ -531,7 +531,7 @@ namespace Org.BouncyCastle.Security
                 {
                     try
                     {
-                        cipherPadding = (CipherPadding)Enums.GetEnumValue(typeof(CipherPadding), paddingName);
+                        cipherPadding = Enums.GetEnumValue<CipherPadding>(paddingName);
                     }
                     catch (ArgumentException)
                     {
@@ -620,6 +620,7 @@ namespace Org.BouncyCastle.Security
             }
 
             string mode = "";
+            IBlockCipherMode blockCipherMode = null;
             if (parts.Length > 1)
             {
                 mode = parts[1];
@@ -631,7 +632,7 @@ namespace Org.BouncyCastle.Security
                 {
                     CipherMode cipherMode = modeName == ""
                         ? CipherMode.NONE
-                        : (CipherMode)Enums.GetEnumValue(typeof(CipherMode), modeName);
+                        : Enums.GetEnumValue<CipherMode>(modeName);
 
                     switch (cipherMode)
                     {
@@ -639,7 +640,7 @@ namespace Org.BouncyCastle.Security
                     case CipherMode.NONE:
                         break;
                     case CipherMode.CBC:
-                        blockCipher = new CbcBlockCipher(blockCipher);
+                        blockCipherMode = new CbcBlockCipher(blockCipher);
                         break;
                     case CipherMode.CCM:
                         aeadBlockCipher = new CcmBlockCipher(blockCipher);
@@ -650,15 +651,15 @@ namespace Org.BouncyCastle.Security
                             ?	8 * blockCipher.GetBlockSize()
                             :	int.Parse(mode.Substring(di));
     
-                        blockCipher = new CfbBlockCipher(blockCipher, bits);
+                        blockCipherMode = new CfbBlockCipher(blockCipher, bits);
                         break;
                     }
                     case CipherMode.CTR:
-                        blockCipher = new SicBlockCipher(blockCipher);
+                        blockCipherMode = new SicBlockCipher(blockCipher);
                         break;
                     case CipherMode.CTS:
                         cts = true;
-                        blockCipher = new CbcBlockCipher(blockCipher);
+                        blockCipherMode = new CbcBlockCipher(blockCipher);
                         break;
                     case CipherMode.EAX:
                         aeadBlockCipher = new EaxBlockCipher(blockCipher);
@@ -667,7 +668,7 @@ namespace Org.BouncyCastle.Security
                         aeadBlockCipher = new GcmBlockCipher(blockCipher);
                         break;
                     case CipherMode.GOFB:
-                        blockCipher = new GOfbBlockCipher(blockCipher);
+                        blockCipherMode = new GOfbBlockCipher(blockCipher);
                         break;
                     case CipherMode.OCB:
                         aeadBlockCipher = new OcbBlockCipher(blockCipher, CreateBlockCipher(cipherAlgorithm));
@@ -678,18 +679,18 @@ namespace Org.BouncyCastle.Security
                             ?	8 * blockCipher.GetBlockSize()
                             :	int.Parse(mode.Substring(di));
     
-                        blockCipher = new OfbBlockCipher(blockCipher, bits);
+                        blockCipherMode = new OfbBlockCipher(blockCipher, bits);
                         break;
                     }
                     case CipherMode.OPENPGPCFB:
-                        blockCipher = new OpenPgpCfbBlockCipher(blockCipher);
+                        blockCipherMode = new OpenPgpCfbBlockCipher(blockCipher);
                         break;
                     case CipherMode.SIC:
                         if (blockCipher.GetBlockSize() < 16)
                         {
                             throw new ArgumentException("Warning: SIC-Mode can become a twotime-pad if the blocksize of the cipher is too small. Use a cipher with a block size of at least 128 bits (e.g. AES)");
                         }
-                        blockCipher = new SicBlockCipher(blockCipher);
+                        blockCipherMode = new SicBlockCipher(blockCipher);
                         break;
                     default:
                         throw new SecurityUtilityException("Cipher " + algorithm + " not recognised.");
@@ -713,22 +714,27 @@ namespace Org.BouncyCastle.Security
 
             if (blockCipher != null)
             {
+                if (blockCipherMode == null)
+                {
+                    blockCipherMode = EcbBlockCipher.GetBlockCipherMode(blockCipher);
+                }
+
                 if (cts)
                 {
-                    return new CtsBlockCipher(blockCipher);
+                    return new CtsBlockCipher(blockCipherMode);
                 }
 
                 if (padding != null)
                 {
-                    return new PaddedBufferedBlockCipher(blockCipher, padding);
+                    return new PaddedBufferedBlockCipher(blockCipherMode, padding);
                 }
 
-                if (!padded || blockCipher.IsPartialBlockOkay)
+                if (!padded || blockCipherMode.IsPartialBlockOkay)
                 {
-                    return new BufferedBlockCipher(blockCipher);
+                    return new BufferedBlockCipher(blockCipherMode);
                 }
 
-                return new PaddedBufferedBlockCipher(blockCipher);
+                return new PaddedBufferedBlockCipher(blockCipherMode);
             }
 
             if (asymBlockCipher != null)
diff --git a/crypto/src/security/DigestUtilities.cs b/crypto/src/security/DigestUtilities.cs
index 2c9e89277..8c175b056 100644
--- a/crypto/src/security/DigestUtilities.cs
+++ b/crypto/src/security/DigestUtilities.cs
@@ -26,6 +26,7 @@ namespace Org.BouncyCastle.Security
         private enum DigestAlgorithm {
             BLAKE2B_160, BLAKE2B_256, BLAKE2B_384, BLAKE2B_512,
             BLAKE2S_128, BLAKE2S_160, BLAKE2S_224, BLAKE2S_256,
+            BLAKE3_256,
             DSTU7564_256, DSTU7564_384, DSTU7564_512,
             GOST3411,
             GOST3411_2012_256, GOST3411_2012_512,
@@ -50,7 +51,7 @@ namespace Org.BouncyCastle.Security
         static DigestUtilities()
         {
             // Signal to obfuscation tools not to change enum constants
-            ((DigestAlgorithm)Enums.GetArbitraryValue(typeof(DigestAlgorithm))).ToString();
+            Enums.GetArbitraryValue<DigestAlgorithm>().ToString();
 
             Aliases[PkcsObjectIdentifiers.MD2.Id] = "MD2";
             Aliases[PkcsObjectIdentifiers.MD4.Id] = "MD4";
@@ -122,6 +123,7 @@ namespace Org.BouncyCastle.Security
             Aliases[MiscObjectIdentifiers.id_blake2s160.Id] = "BLAKE2S-160";
             Aliases[MiscObjectIdentifiers.id_blake2s224.Id] = "BLAKE2S-224";
             Aliases[MiscObjectIdentifiers.id_blake2s256.Id] = "BLAKE2S-256";
+            Aliases[MiscObjectIdentifiers.blake3_256.Id] = "BLAKE3-256";
 
             Aliases[RosstandartObjectIdentifiers.id_tc26_gost_3411_12_256.Id] = "GOST3411-2012-256";
             Aliases[RosstandartObjectIdentifiers.id_tc26_gost_3411_12_512.Id] = "GOST3411-2012-512";
@@ -159,6 +161,7 @@ namespace Org.BouncyCastle.Security
             Oids["BLAKE2S-160"] = MiscObjectIdentifiers.id_blake2s160;
             Oids["BLAKE2S-224"] = MiscObjectIdentifiers.id_blake2s224;
             Oids["BLAKE2S-256"] = MiscObjectIdentifiers.id_blake2s256;
+            Oids["BLAKE3-256"] = MiscObjectIdentifiers.blake3_256;
             Oids["GOST3411-2012-256"] = RosstandartObjectIdentifiers.id_tc26_gost_3411_12_256;
             Oids["GOST3411-2012-512"] = RosstandartObjectIdentifiers.id_tc26_gost_3411_12_512;
             Oids["DSTU7564-256"] = UAObjectIdentifiers.dstu7564digest_256;
@@ -196,8 +199,7 @@ namespace Org.BouncyCastle.Security
 
             try
             {
-                DigestAlgorithm digestAlgorithm = (DigestAlgorithm)Enums.GetEnumValue(
-                    typeof(DigestAlgorithm), mechanism);
+                DigestAlgorithm digestAlgorithm = Enums.GetEnumValue<DigestAlgorithm>(mechanism);
 
                 switch (digestAlgorithm)
                 {
@@ -209,6 +211,7 @@ namespace Org.BouncyCastle.Security
                 case DigestAlgorithm.BLAKE2S_160: return new Blake2sDigest(160);
                 case DigestAlgorithm.BLAKE2S_224: return new Blake2sDigest(224);
                 case DigestAlgorithm.BLAKE2S_256: return new Blake2sDigest(256);
+                case DigestAlgorithm.BLAKE3_256: return new Blake3Digest(256);
                 case DigestAlgorithm.DSTU7564_256: return new Dstu7564Digest(256);
                 case DigestAlgorithm.DSTU7564_384: return new Dstu7564Digest(384);
                 case DigestAlgorithm.DSTU7564_512: return new Dstu7564Digest(512);
@@ -266,9 +269,22 @@ namespace Org.BouncyCastle.Security
         public static byte[] CalculateDigest(string algorithm, byte[] input)
         {
             IDigest digest = GetDigest(algorithm);
-            digest.BlockUpdate(input, 0, input.Length);
-            return DoFinal(digest);
+            return DoFinal(digest, input);
+        }
+
+        public static byte[] CalculateDigest(string algorithm, byte[] buf, int off, int len)
+        {
+            IDigest digest = GetDigest(algorithm);
+            return DoFinal(digest, buf, off, len);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] CalculateDigest(string algorithm, ReadOnlySpan<byte> buffer)
+        {
+            IDigest digest = GetDigest(algorithm);
+            return DoFinal(digest, buffer);
         }
+#endif
 
         public static byte[] DoFinal(IDigest digest)
         {
@@ -282,5 +298,19 @@ namespace Org.BouncyCastle.Security
             digest.BlockUpdate(input, 0, input.Length);
             return DoFinal(digest);
         }
+
+        public static byte[] DoFinal(IDigest digest, byte[] buf, int off, int len)
+        {
+            digest.BlockUpdate(buf, off, len);
+            return DoFinal(digest);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] DoFinal(IDigest digest, ReadOnlySpan<byte> buffer)
+        {
+            digest.BlockUpdate(buffer);
+            return DoFinal(digest);
+        }
+#endif
     }
 }
diff --git a/crypto/src/security/DotNetUtilities.cs b/crypto/src/security/DotNetUtilities.cs
index ccfb2b2b8..3a7c5f0cb 100644
--- a/crypto/src/security/DotNetUtilities.cs
+++ b/crypto/src/security/DotNetUtilities.cs
@@ -18,9 +18,6 @@ namespace Org.BouncyCastle.Security
     /// <summary>
     /// A class containing methods to interface the BouncyCastle world to the .NET Crypto world.
     /// </summary>
-#if NET5_0_OR_GREATER
-    [SupportedOSPlatform("windows")]
-#endif
     public static class DotNetUtilities
     {
         /// <summary>
@@ -153,33 +150,51 @@ namespace Org.BouncyCastle.Security
             throw new ArgumentException("Unsupported algorithm specified", "privateKey");
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         public static RSA ToRSA(RsaKeyParameters rsaKey)
         {
             // TODO This appears to not work for private keys (when no CRT info)
             return CreateRSAProvider(ToRSAParameters(rsaKey));
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         public static RSA ToRSA(RsaKeyParameters rsaKey, CspParameters csp)
         {
             // TODO This appears to not work for private keys (when no CRT info)
             return CreateRSAProvider(ToRSAParameters(rsaKey), csp);
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey)
         {
             return CreateRSAProvider(ToRSAParameters(privKey));
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey, CspParameters csp)
         {
             return CreateRSAProvider(ToRSAParameters(privKey), csp);
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         public static RSA ToRSA(RsaPrivateKeyStructure privKey)
         {
             return CreateRSAProvider(ToRSAParameters(privKey));
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         public static RSA ToRSA(RsaPrivateKeyStructure privKey, CspParameters csp)
         {
             return CreateRSAProvider(ToRSAParameters(privKey), csp);
@@ -229,6 +244,9 @@ namespace Org.BouncyCastle.Security
             return BigIntegers.AsUnsignedByteArray(size, n);
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         private static RSACryptoServiceProvider CreateRSAProvider(RSAParameters rp)
         {
             CspParameters csp = new CspParameters();
@@ -236,6 +254,9 @@ namespace Org.BouncyCastle.Security
             return CreateRSAProvider(rp, csp);
         }
 
+#if NET5_0_OR_GREATER
+        [SupportedOSPlatform("windows")]
+#endif
         private static RSACryptoServiceProvider CreateRSAProvider(RSAParameters rp, CspParameters csp)
         {
             RSACryptoServiceProvider rsaCsp = new RSACryptoServiceProvider(csp);
diff --git a/crypto/src/security/GeneratorUtilities.cs b/crypto/src/security/GeneratorUtilities.cs
index c48a71f2e..26898aaf8 100644
--- a/crypto/src/security/GeneratorUtilities.cs
+++ b/crypto/src/security/GeneratorUtilities.cs
@@ -127,7 +127,7 @@ namespace Org.BouncyCastle.Security
             AddKgAlgorithm("GOST28147",
                 "GOST",
                 "GOST-28147",
-                CryptoProObjectIdentifiers.GostR28147Cbc);
+                CryptoProObjectIdentifiers.GostR28147Gcfb);
             AddKgAlgorithm("HC128");
             AddKgAlgorithm("HC256");
             AddKgAlgorithm("IDEA",
@@ -222,6 +222,8 @@ namespace Org.BouncyCastle.Security
             AddKpgAlgorithm("ECGOST3410",
                 "ECGOST-3410",
                 "GOST-3410-2001");
+            AddKpgAlgorithm("ECGOST3410-2012",
+                "GOST-3410-2012");
             AddKpgAlgorithm("Ed25519",
                 "Ed25519ctx",
                 "Ed25519ph",
@@ -358,7 +360,7 @@ namespace Org.BouncyCastle.Security
             if (canonicalName == "DSA")
                 return new DsaKeyPairGenerator();
 
-            // "EC", "ECDH", "ECDHC", "ECDSA", "ECGOST3410", "ECMQV"
+            // "EC", "ECDH", "ECDHC", "ECDSA", "ECGOST3410", "ECGOST3410-2012", "ECMQV"
             if (Platform.StartsWith(canonicalName, "EC"))
                 return new ECKeyPairGenerator(canonicalName);
 
diff --git a/crypto/src/security/JksStore.cs b/crypto/src/security/JksStore.cs
new file mode 100644
index 000000000..9f4aced96
--- /dev/null
+++ b/crypto/src/security/JksStore.cs
@@ -0,0 +1,596 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Pkcs;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+using Org.BouncyCastle.Utilities.Date;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Security
+{
+    public class JksStore
+    {
+        private static readonly int Magic = unchecked((int)0xFEEDFEED);
+
+        private static readonly AlgorithmIdentifier JksObfuscationAlg = new AlgorithmIdentifier(
+            new DerObjectIdentifier("1.3.6.1.4.1.42.2.17.1.1"), DerNull.Instance);
+
+        private readonly Dictionary<string, JksTrustedCertEntry> m_certificateEntries =
+            new Dictionary<string, JksTrustedCertEntry>(StringComparer.OrdinalIgnoreCase);
+        private readonly Dictionary<string, JksKeyEntry> m_keyEntries =
+            new Dictionary<string, JksKeyEntry>(StringComparer.OrdinalIgnoreCase);
+
+        public JksStore()
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        public bool Probe(Stream stream)
+        {
+            using (var br = new BinaryReader(stream))
+            try
+            {
+                return Magic == BinaryReaders.ReadInt32BigEndian(br);
+            }
+            catch (EndOfStreamException)
+            {
+                return false;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public AsymmetricKeyParameter GetKey(string alias, char[] password)
+        {
+            if (alias == null)
+                throw new ArgumentNullException(nameof(alias));
+            if (password == null)
+                throw new ArgumentNullException(nameof(password));
+
+            if (!m_keyEntries.TryGetValue(alias, out JksKeyEntry keyEntry))
+                return null;
+
+            if (!JksObfuscationAlg.Equals(keyEntry.keyData.EncryptionAlgorithm))
+                throw new IOException("unknown encryption algorithm");
+
+            byte[] encryptedData = keyEntry.keyData.GetEncryptedData();
+
+            // key length is encryptedData - salt - checksum
+            int pkcs8Len = encryptedData.Length - 40;
+
+            IDigest digest = DigestUtilities.GetDigest("SHA-1");
+
+            // key decryption
+            byte[] keyStream = CalculateKeyStream(digest, password, encryptedData, pkcs8Len);
+            byte[] pkcs8Key = new byte[pkcs8Len];
+            for (int i = 0; i < pkcs8Len; ++i)
+            {
+                pkcs8Key[i] = (byte)(encryptedData[20 + i] ^ keyStream[i]);
+            }
+            Array.Clear(keyStream, 0, keyStream.Length);
+
+            // integrity check
+            byte[] checksum = GetKeyChecksum(digest, password, pkcs8Key);
+
+            if (!Arrays.ConstantTimeAreEqual(20, encryptedData, pkcs8Len + 20, checksum, 0))
+                throw new IOException("cannot recover key");
+
+            return PrivateKeyFactory.CreateKey(pkcs8Key);
+        }
+
+        private byte[] GetKeyChecksum(IDigest digest, char[] password, byte[] pkcs8Key)
+        {
+            AddPassword(digest, password);
+
+            return DigestUtilities.DoFinal(digest, pkcs8Key);
+        }
+
+        private byte[] CalculateKeyStream(IDigest digest, char[] password, byte[] salt, int count)
+        {
+            byte[] keyStream = new byte[count];
+            byte[] hash = Arrays.CopyOf(salt, 20);
+
+            int index = 0;
+            while (index < count)
+            {
+                AddPassword(digest, password);
+
+                digest.BlockUpdate(hash, 0, hash.Length);
+                digest.DoFinal(hash, 0);
+
+                int length = System.Math.Min(hash.Length, keyStream.Length - index);
+                Array.Copy(hash, 0, keyStream, index, length);
+                index += length;
+            }
+
+            return keyStream;
+        }
+
+        public X509Certificate[] GetCertificateChain(string alias)
+        {
+            if (m_keyEntries.TryGetValue(alias, out var keyEntry))
+                return CloneChain(keyEntry.chain);
+
+            return null;
+        }
+
+        public X509Certificate GetCertificate(string alias)
+        {
+            if (m_certificateEntries.TryGetValue(alias, out var certEntry))
+                return certEntry.cert;
+
+            if (m_keyEntries.TryGetValue(alias, out var keyEntry))
+                return keyEntry.chain?[0];
+
+            return null;
+        }
+
+        public DateTime? GetCreationDate(string alias)
+        {
+            if (m_certificateEntries.TryGetValue(alias, out var certEntry))
+                return certEntry.date;
+
+            if (m_keyEntries.TryGetValue(alias, out var keyEntry))
+                return keyEntry.date;
+
+            return null;
+        }
+
+        /// <exception cref="IOException"/>
+        public void SetKeyEntry(string alias, AsymmetricKeyParameter key, char[] password, X509Certificate[] chain)
+        {
+            alias = ConvertAlias(alias);
+
+            if (ContainsAlias(alias))
+                throw new IOException("alias [" + alias + "] already in use");
+
+            byte[] pkcs8Key = PrivateKeyInfoFactory.CreatePrivateKeyInfo(key).GetEncoded();
+            byte[] protectedKey = new byte[pkcs8Key.Length + 40];
+
+            SecureRandom rnd = CryptoServicesRegistrar.GetSecureRandom();
+            rnd.NextBytes(protectedKey, 0, 20);
+
+            IDigest digest = DigestUtilities.GetDigest("SHA-1");
+
+            byte[] checksum = GetKeyChecksum(digest, password, pkcs8Key);
+            Array.Copy(checksum, 0, protectedKey, 20 + pkcs8Key.Length, 20);
+
+            byte[] keyStream = CalculateKeyStream(digest, password, protectedKey, pkcs8Key.Length);
+            for (int i = 0; i != keyStream.Length; i++)
+            {
+                protectedKey[20 + i] = (byte)(pkcs8Key[i] ^ keyStream[i]);
+            }
+            Array.Clear(keyStream, 0, keyStream.Length);
+
+            try
+            {
+                var epki = new EncryptedPrivateKeyInfo(JksObfuscationAlg, protectedKey);
+                m_keyEntries.Add(alias, new JksKeyEntry(DateTime.UtcNow, epki.GetEncoded(), CloneChain(chain)));
+            }
+            catch (Exception e)
+            {
+                throw new IOException("unable to encode encrypted private key", e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public void SetKeyEntry(string alias, byte[] key, X509Certificate[] chain)
+        {
+            alias = ConvertAlias(alias);
+
+            if (ContainsAlias(alias))
+                throw new IOException("alias [" + alias + "] already in use");
+
+            m_keyEntries.Add(alias, new JksKeyEntry(DateTime.UtcNow, key, CloneChain(chain)));
+        }
+
+        /// <exception cref="IOException"/>
+        public void SetCertificateEntry(string alias, X509Certificate cert)
+        {
+            alias = ConvertAlias(alias);
+
+            if (ContainsAlias(alias))
+                throw new IOException("alias [" + alias + "] already in use");
+
+            m_certificateEntries.Add(alias, new JksTrustedCertEntry(DateTime.UtcNow, cert));
+        }
+
+        public void DeleteEntry(string alias)
+        {
+            if (!m_keyEntries.Remove(alias))
+            {
+                m_certificateEntries.Remove(alias);
+            }
+        }
+
+        public IEnumerable<string> Aliases
+        {
+            get
+            {
+                var aliases = new HashSet<string>(m_certificateEntries.Keys);
+                aliases.UnionWith(m_keyEntries.Keys);
+                return CollectionUtilities.Proxy(aliases);
+            }
+        }
+
+        public bool ContainsAlias(string alias)
+        {
+            return IsCertificateEntry(alias) || IsKeyEntry(alias);
+        }
+
+        public int Count
+        {
+            get { return m_certificateEntries.Count + m_keyEntries.Count; }
+        }
+
+        public bool IsKeyEntry(string alias)
+        {
+            return m_keyEntries.ContainsKey(alias);
+        }
+
+        public bool IsCertificateEntry(string alias)
+        {
+            return m_certificateEntries.ContainsKey(alias);
+        }
+
+        public string GetCertificateAlias(X509Certificate cert)
+        {
+            foreach (var entry in m_certificateEntries)
+            {
+                if (entry.Value.cert.Equals(cert))
+                    return entry.Key;
+            }
+            return null;
+        }
+
+        /// <exception cref="IOException"/>
+        public void Save(Stream stream, char[] password)
+        {
+            if (stream == null)
+                throw new ArgumentNullException(nameof(stream));
+            if (password == null)
+                throw new ArgumentNullException(nameof(password));
+
+            IDigest checksumDigest = CreateChecksumDigest(password);
+            BinaryWriter bw = new BinaryWriter(new DigestStream(stream, null, checksumDigest));
+
+            BinaryWriters.WriteInt32BigEndian(bw, Magic);
+            BinaryWriters.WriteInt32BigEndian(bw, 2);
+
+            BinaryWriters.WriteInt32BigEndian(bw, Count);
+
+            foreach (var entry in m_keyEntries)
+            {
+                string alias = entry.Key;
+                JksKeyEntry keyEntry = entry.Value;
+
+                BinaryWriters.WriteInt32BigEndian(bw, 1);
+                WriteUtf(bw, alias);
+                WriteDateTime(bw, keyEntry.date);
+                WriteBufferWithInt32Length(bw, keyEntry.keyData.GetEncoded());
+
+                X509Certificate[] chain = keyEntry.chain;
+                int chainLength = chain == null ? 0 : chain.Length;
+                BinaryWriters.WriteInt32BigEndian(bw, chainLength);
+                for (int i = 0; i < chainLength; ++i)
+                {
+                    WriteTypedCertificate(bw, chain[i]);
+                }
+            }
+
+            foreach (var entry in m_certificateEntries) 
+            {
+                string alias = entry.Key;
+                JksTrustedCertEntry certEntry = entry.Value;
+
+                BinaryWriters.WriteInt32BigEndian(bw, 2);
+                WriteUtf(bw, alias);
+                WriteDateTime(bw, certEntry.date);
+                WriteTypedCertificate(bw, certEntry.cert);
+            }
+
+            byte[] checksum = DigestUtilities.DoFinal(checksumDigest);
+            bw.Write(checksum);
+            bw.Flush();
+        }
+
+        /// <exception cref="IOException"/>
+        public void Load(Stream stream, char[] password)
+        {
+            if (stream == null)
+                throw new ArgumentNullException(nameof(stream));
+
+            m_certificateEntries.Clear();
+            m_keyEntries.Clear();
+
+            using (var storeStream = ValidateStream(stream, password))
+            {
+                BinaryReader br = new BinaryReader(storeStream);
+
+                int magic = BinaryReaders.ReadInt32BigEndian(br);
+                int storeVersion = BinaryReaders.ReadInt32BigEndian(br);
+
+                if (!(magic == Magic && (storeVersion == 1 || storeVersion == 2)))
+                    throw new IOException("Invalid keystore format");
+
+                int numEntries = BinaryReaders.ReadInt32BigEndian(br);
+
+                for (int t = 0; t < numEntries; t++)
+                {
+                    int tag = BinaryReaders.ReadInt32BigEndian(br);
+
+                    switch (tag)
+                    {
+                    case 1: // keys
+                    {
+                        string alias = ReadUtf(br);
+                        DateTime date = ReadDateTime(br);
+
+                        // encrypted key data
+                        byte[] keyData = ReadBufferWithInt32Length(br);
+
+                        // certificate chain
+                        int chainLength = BinaryReaders.ReadInt32BigEndian(br);
+                        X509Certificate[] chain = null;
+                        if (chainLength > 0)
+                        {
+                            var certs = new List<X509Certificate>(System.Math.Min(10, chainLength));
+                            for (int certNo = 0; certNo != chainLength; certNo++)
+                            {
+                                certs.Add(ReadTypedCertificate(br, storeVersion));
+                            }
+                            chain = certs.ToArray();
+                        }
+                        m_keyEntries.Add(alias, new JksKeyEntry(date, keyData, chain));
+                        break;
+                    }
+                    case 2: // certificate
+                    {
+                        string alias = ReadUtf(br);
+                        DateTime date = ReadDateTime(br);
+
+                        X509Certificate cert = ReadTypedCertificate(br, storeVersion);
+
+                        m_certificateEntries.Add(alias, new JksTrustedCertEntry(date, cert));
+                        break;
+                    }
+                    default:
+                        throw new IOException("unable to discern entry type");
+                    }
+                }
+
+                if (storeStream.Position != storeStream.Length)
+                    throw new IOException("password incorrect or store tampered with");
+            }
+        }
+
+        /*
+         * Validate password takes the checksum of the store and will either.
+         * 1. If password is null, load the store into memory, return the result.
+         * 2. If password is not null, load the store into memory, test the checksum, and if successful return
+         * a new input stream instance of the store.
+         * 3. Fail if there is a password and an invalid checksum.
+         *
+         * @param inputStream The input stream.
+         * @param password    the password.
+         * @return Either the passed in input stream or a new input stream.
+         */
+        /// <exception cref="IOException"/>
+        private ErasableByteStream ValidateStream(Stream inputStream, char[] password)
+        {
+            byte[] rawStore = Streams.ReadAll(inputStream);
+            int checksumPos = rawStore.Length - 20;
+
+            if (password != null)
+            {
+                byte[] checksum = CalculateChecksum(password, rawStore, 0, checksumPos);
+
+                if (!Arrays.ConstantTimeAreEqual(20, checksum, 0, rawStore, checksumPos))
+                {
+                    Array.Clear(rawStore, 0, rawStore.Length);
+                    throw new IOException("password incorrect or store tampered with");
+                }
+            }
+
+            return new ErasableByteStream(rawStore, 0, checksumPos);
+        }
+
+        private static void AddPassword(IDigest digest, char[] password)
+        {
+            // Encoding.BigEndianUnicode
+            for (int i = 0; i < password.Length; ++i)
+            {
+                digest.Update((byte)(password[i] >> 8));
+                digest.Update((byte)password[i]);
+            }
+        }
+
+        private static byte[] CalculateChecksum(char[] password, byte[] buffer, int offset, int length)
+        {
+            IDigest checksumDigest = CreateChecksumDigest(password);
+            checksumDigest.BlockUpdate(buffer, offset, length);
+            return DigestUtilities.DoFinal(checksumDigest);
+        }
+
+        private static X509Certificate[] CloneChain(X509Certificate[] chain)
+        {
+            return (X509Certificate[])chain?.Clone();
+        }
+
+        private static string ConvertAlias(string alias)
+        {
+            return alias.ToLowerInvariant();
+        }
+
+        private static IDigest CreateChecksumDigest(char[] password)
+        {
+            IDigest digest = DigestUtilities.GetDigest("SHA-1");
+            AddPassword(digest, password);
+
+            //
+            // This "Mighty Aphrodite" string goes all the way back to the
+            // first java betas in the mid 90's, why who knows? But see
+            // https://cryptosense.com/mighty-aphrodite-dark-secrets-of-the-java-keystore/
+            //
+            byte[] prefix = Encoding.UTF8.GetBytes("Mighty Aphrodite");
+            digest.BlockUpdate(prefix, 0, prefix.Length);
+            return digest;
+        }
+
+        private static byte[] ReadBufferWithInt16Length(BinaryReader br)
+        {
+            int length = BinaryReaders.ReadInt16BigEndian(br);
+            return BinaryReaders.ReadBytesFully(br, length);
+        }
+
+        private static byte[] ReadBufferWithInt32Length(BinaryReader br)
+        {
+            int length = BinaryReaders.ReadInt32BigEndian(br);
+            return BinaryReaders.ReadBytesFully(br, length);
+        }
+
+        private static DateTime ReadDateTime(BinaryReader br)
+        {
+            long unixMS = BinaryReaders.ReadInt64BigEndian(br);
+            return DateTimeUtilities.UnixMsToDateTime(unixMS);
+        }
+
+        private static X509Certificate ReadTypedCertificate(BinaryReader br, int storeVersion)
+        {
+            if (storeVersion == 2)
+            {
+                string certFormat = ReadUtf(br);
+                if ("X.509" != certFormat)
+                    throw new IOException("Unsupported certificate format: " + certFormat);
+            }
+
+            byte[] certData = ReadBufferWithInt32Length(br);
+            try
+            {
+                return new X509Certificate(certData);
+            }
+            finally
+            {
+                Array.Clear(certData, 0, certData.Length);
+            }
+        }
+
+        private static string ReadUtf(BinaryReader br)
+        {
+            byte[] utfBytes = ReadBufferWithInt16Length(br);
+
+            /*
+             * FIXME JKS actually uses a "modified UTF-8" format. For the moment we will just support single-byte
+             * encodings that aren't null bytes.
+             */
+            for (int i = 0; i < utfBytes.Length; ++i)
+            {
+                byte utfByte = utfBytes[i];
+                if (utfByte == 0 || (utfByte & 0x80) != 0)
+                    throw new NotSupportedException("Currently missing support for modified UTF-8 encoding in JKS");
+            }
+
+            return Encoding.UTF8.GetString(utfBytes);
+        }
+
+        private static void WriteBufferWithInt16Length(BinaryWriter bw, byte[] buffer)
+        {
+            BinaryWriters.WriteInt16BigEndian(bw, Convert.ToInt16(buffer.Length));
+            bw.Write(buffer);
+        }
+
+        private static void WriteBufferWithInt32Length(BinaryWriter bw, byte[] buffer)
+        {
+            BinaryWriters.WriteInt32BigEndian(bw, buffer.Length);
+            bw.Write(buffer);
+        }
+
+        private static void WriteDateTime(BinaryWriter bw, DateTime dateTime)
+        {
+            long unixMS = DateTimeUtilities.DateTimeToUnixMs(dateTime);
+            BinaryWriters.WriteInt64BigEndian(bw, unixMS);
+        }
+
+        private static void WriteTypedCertificate(BinaryWriter bw, X509Certificate cert)
+        {
+            WriteUtf(bw, "X.509");
+            WriteBufferWithInt32Length(bw, cert.GetEncoded());
+        }
+
+        private static void WriteUtf(BinaryWriter bw, string s)
+        {
+            byte[] utfBytes = Encoding.UTF8.GetBytes(s);
+
+            /*
+             * FIXME JKS actually uses a "modified UTF-8" format. For the moment we will just support single-byte
+             * encodings that aren't null bytes.
+             */
+            for (int i = 0; i < utfBytes.Length; ++i)
+            {
+                byte utfByte = utfBytes[i];
+                if (utfByte == 0 || (utfByte & 0x80) != 0)
+                    throw new NotSupportedException("Currently missing support for modified UTF-8 encoding in JKS");
+            }
+
+            WriteBufferWithInt16Length(bw, utfBytes);
+        }
+
+        /**
+         * JksTrustedCertEntry is a internal container for the certificate entry.
+         */
+        private sealed class JksTrustedCertEntry
+        {
+            internal readonly DateTime date;
+            internal readonly X509Certificate cert;
+
+            internal JksTrustedCertEntry(DateTime date, X509Certificate cert)
+            {
+                this.date = date;
+                this.cert = cert;
+            }
+        }
+
+        private sealed class JksKeyEntry
+        {
+            internal readonly DateTime date;
+            internal readonly EncryptedPrivateKeyInfo keyData;
+            internal readonly X509Certificate[] chain;
+
+            internal JksKeyEntry(DateTime date, byte[] keyData, X509Certificate[] chain)
+            {
+                this.date = date;
+                this.keyData = EncryptedPrivateKeyInfo.GetInstance(Asn1Sequence.GetInstance(keyData));
+                this.chain = chain;
+            }
+        }
+
+        private sealed class ErasableByteStream
+            : MemoryStream
+        {
+            internal ErasableByteStream(byte[] buffer, int index, int count)
+                : base(buffer, index, count, false, true)
+            {
+            }
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    Position = 0L;
+
+                    byte[] rawStore = GetBuffer();
+                    Array.Clear(rawStore, 0, rawStore.Length);
+                }
+                base.Dispose(disposing);
+            }
+        }
+    }
+}
diff --git a/crypto/src/security/ParameterUtilities.cs b/crypto/src/security/ParameterUtilities.cs
index 5a407fc9d..d79f15359 100644
--- a/crypto/src/security/ParameterUtilities.cs
+++ b/crypto/src/security/ParameterUtilities.cs
@@ -118,7 +118,7 @@ namespace Org.BouncyCastle.Security
             AddAlgorithm("GOST28147",
                 "GOST",
                 "GOST-28147",
-                CryptoProObjectIdentifiers.GostR28147Cbc);
+                CryptoProObjectIdentifiers.GostR28147Gcfb);
             AddAlgorithm("HC128");
             AddAlgorithm("HC256");
             AddAlgorithm("IDEA",
diff --git a/crypto/src/security/PrivateKeyFactory.cs b/crypto/src/security/PrivateKeyFactory.cs
index 3c57297fe..c4fabae2d 100644
--- a/crypto/src/security/PrivateKeyFactory.cs
+++ b/crypto/src/security/PrivateKeyFactory.cs
@@ -2,8 +2,10 @@ using System;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
 using Org.BouncyCastle.Asn1.CryptoPro;
 using Org.BouncyCastle.Asn1.EdEC;
+using Org.BouncyCastle.Asn1.Gnu;
 using Org.BouncyCastle.Asn1.Oiw;
 using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.Rosstandart;
@@ -170,7 +172,8 @@ namespace Org.BouncyCastle.Security
 
                 return new Gost3410PrivateKeyParameters(x, gostParams.PublicKeyParamSet);
             }
-            else if (algOid.Equals(EdECObjectIdentifiers.id_X25519))
+            else if (algOid.Equals(EdECObjectIdentifiers.id_X25519)
+                || algOid.Equals(CryptlibObjectIdentifiers.curvey25519))
             {
                 return new X25519PrivateKeyParameters(GetRawKey(keyInfo));
             }
@@ -178,7 +181,8 @@ namespace Org.BouncyCastle.Security
             {
                 return new X448PrivateKeyParameters(GetRawKey(keyInfo));
             }
-            else if (algOid.Equals(EdECObjectIdentifiers.id_Ed25519))
+            else if (algOid.Equals(EdECObjectIdentifiers.id_Ed25519)
+                || algOid.Equals(GnuObjectIdentifiers.Ed25519))
             {
                 return new Ed25519PrivateKeyParameters(GetRawKey(keyInfo));
             }
@@ -186,16 +190,18 @@ namespace Org.BouncyCastle.Security
             {
                 return new Ed448PrivateKeyParameters(GetRawKey(keyInfo));
             }
-            else if (algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512)
-                     || algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256))
+            else if (algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256)
+                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512)
+                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_agreement_gost_3410_12_256)
+                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_agreement_gost_3410_12_512))
             {
-                Gost3410PublicKeyAlgParameters gostParams = Gost3410PublicKeyAlgParameters.GetInstance(keyInfo.PrivateKeyAlgorithm.Parameters);
+                Gost3410PublicKeyAlgParameters gostParams = Gost3410PublicKeyAlgParameters.GetInstance(
+                    keyInfo.PrivateKeyAlgorithm.Parameters);
                 ECGost3410Parameters ecSpec;
                 BigInteger d;
                 Asn1Object p = keyInfo.PrivateKeyAlgorithm.Parameters.ToAsn1Object();
                 if (p is Asn1Sequence && (Asn1Sequence.GetInstance(p).Count == 2 || Asn1Sequence.GetInstance(p).Count == 3))
                 {
-
                     X9ECParameters ecP = ECGost3410NamedCurves.GetByOid(gostParams.PublicKeyParamSet);
 
                     ecSpec = new ECGost3410Parameters(
diff --git a/crypto/src/security/PublicKeyFactory.cs b/crypto/src/security/PublicKeyFactory.cs
index 49ad49dd0..8c3e9db99 100644
--- a/crypto/src/security/PublicKeyFactory.cs
+++ b/crypto/src/security/PublicKeyFactory.cs
@@ -2,8 +2,10 @@ using System;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
 using Org.BouncyCastle.Asn1.CryptoPro;
 using Org.BouncyCastle.Asn1.EdEC;
+using Org.BouncyCastle.Asn1.Gnu;
 using Org.BouncyCastle.Asn1.Oiw;
 using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.Rosstandart;
@@ -211,7 +213,8 @@ namespace Org.BouncyCastle.Security
 
                 return new Gost3410PublicKeyParameters(y, algParams.PublicKeyParamSet);
             }
-            else if (algOid.Equals(EdECObjectIdentifiers.id_X25519))
+            else if (algOid.Equals(EdECObjectIdentifiers.id_X25519)
+                || algOid.Equals(CryptlibObjectIdentifiers.curvey25519))
             {
                 return new X25519PublicKeyParameters(GetRawKey(keyInfo));
             }
@@ -219,7 +222,8 @@ namespace Org.BouncyCastle.Security
             {
                 return new X448PublicKeyParameters(GetRawKey(keyInfo));
             }
-            else if (algOid.Equals(EdECObjectIdentifiers.id_Ed25519))
+            else if (algOid.Equals(EdECObjectIdentifiers.id_Ed25519)
+                || algOid.Equals(GnuObjectIdentifiers.Ed25519))
             {
                 return new Ed25519PublicKeyParameters(GetRawKey(keyInfo));
             }
@@ -228,7 +232,9 @@ namespace Org.BouncyCastle.Security
                 return new Ed448PublicKeyParameters(GetRawKey(keyInfo));
             }
             else if (algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256)
-                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512))
+                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512)
+                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_agreement_gost_3410_12_256)
+                ||   algOid.Equals(RosstandartObjectIdentifiers.id_tc26_agreement_gost_3410_12_512))
             {
                 Gost3410PublicKeyAlgParameters gostParams = Gost3410PublicKeyAlgParameters.GetInstance(algID.Parameters);
                 DerObjectIdentifier publicKeyParamSet = gostParams.PublicKeyParamSet;
diff --git a/crypto/src/security/SecureRandom.cs b/crypto/src/security/SecureRandom.cs
index e8cac56f5..521e7db0e 100644
--- a/crypto/src/security/SecureRandom.cs
+++ b/crypto/src/security/SecureRandom.cs
@@ -4,25 +4,21 @@ using System.Threading;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Prng;
 using Org.BouncyCastle.Crypto.Utilities;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Security
 {
     public class SecureRandom
         : Random
     {
-        private static long counter = Times.NanoTime();
+        private static long counter = DateTime.UtcNow.Ticks;
 
         private static long NextCounterValue()
         {
             return Interlocked.Increment(ref counter);
         }
 
-        private static readonly SecureRandom master = new SecureRandom(new CryptoApiRandomGenerator());
-        private static SecureRandom Master
-        {
-            get { return master; }
-        }
+        private static readonly SecureRandom MasterRandom = new SecureRandom(new CryptoApiRandomGenerator());
+        internal static readonly SecureRandom ArbitraryRandom = new SecureRandom(new VmpcRandomGenerator(), 16);
 
         private static DigestRandomGenerator CreatePrng(string digestName, bool autoSeed)
         {
@@ -32,8 +28,7 @@ namespace Org.BouncyCastle.Security
             DigestRandomGenerator prng = new DigestRandomGenerator(digest);
             if (autoSeed)
             {
-                prng.AddSeedMaterial(NextCounterValue());
-                prng.AddSeedMaterial(GetNextBytes(Master, digest.GetDigestSize()));
+                AutoSeed(prng, digest.GetDigestSize());
             }
             return prng;
         }
@@ -98,16 +93,38 @@ namespace Org.BouncyCastle.Security
             this.generator = generator;
         }
 
+        public SecureRandom(IRandomGenerator generator, int autoSeedLengthInBytes)
+            : base(0)
+        {
+            AutoSeed(generator, autoSeedLengthInBytes);
+
+            this.generator = generator;
+        }
+
         public virtual byte[] GenerateSeed(int length)
         {
-            return GetNextBytes(Master, length);
+            return GetNextBytes(MasterRandom, length);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void GenerateSeed(Span<byte> seed)
+        {
+            MasterRandom.NextBytes(seed);
         }
+#endif
 
         public virtual void SetSeed(byte[] seed)
         {
             generator.AddSeedMaterial(seed);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void SetSeed(Span<byte> seed)
+        {
+            generator.AddSeedMaterial(seed);
+        }
+#endif
+
         public virtual void SetSeed(long seed)
         {
             generator.AddSeedMaterial(seed);
@@ -181,6 +198,22 @@ namespace Org.BouncyCastle.Security
             generator.NextBytes(buf, off, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void NextBytes(Span<byte> buffer)
+        {
+            if (generator != null)
+            {
+                generator.NextBytes(buffer);
+            }
+            else
+            {
+                byte[] tmp = new byte[buffer.Length];
+                NextBytes(tmp);
+                tmp.CopyTo(buffer);
+            }
+        }
+#endif
+
         private static readonly double DoubleScale = 1.0 / Convert.ToDouble(1L << 53);
 
         public override double NextDouble()
@@ -192,16 +225,39 @@ namespace Org.BouncyCastle.Security
 
         public virtual int NextInt()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = stackalloc byte[4];
+#else
             byte[] bytes = new byte[4];
+#endif
             NextBytes(bytes);
-            return (int)Pack.BE_To_UInt32(bytes, 0);
+            return (int)Pack.BE_To_UInt32(bytes);
         }
 
         public virtual long NextLong()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> bytes = stackalloc byte[8];
+#else
             byte[] bytes = new byte[8];
+#endif
             NextBytes(bytes);
-            return (long)Pack.BE_To_UInt64(bytes, 0);
+            return (long)Pack.BE_To_UInt64(bytes);
+        }
+
+        private static void AutoSeed(IRandomGenerator generator, int seedLength)
+        {
+            generator.AddSeedMaterial(NextCounterValue());
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> seed = seedLength <= 128
+                ? stackalloc byte[seedLength]
+                : new byte[seedLength];
+#else
+                byte[] seed = new byte[seedLength];
+#endif
+            MasterRandom.NextBytes(seed);
+            generator.AddSeedMaterial(seed);
         }
     }
 }
diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs
index e42e217cc..e6210dad7 100644
--- a/crypto/src/security/SignerUtilities.cs
+++ b/crypto/src/security/SignerUtilities.cs
@@ -19,6 +19,7 @@ using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Signers;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
+using Org.BouncyCastle.Asn1.Rosstandart;
 
 namespace Org.BouncyCastle.Security
 {
@@ -367,13 +368,29 @@ namespace Org.BouncyCastle.Security
             AlgorithmMap["GOST-3410"] = "GOST3410";
             AlgorithmMap["GOST-3410-94"] = "GOST3410";
             AlgorithmMap["GOST3411WITHGOST3410"] = "GOST3410";
+            AlgorithmMap["GOST3411/GOST3410"] = "GOST3410";
             AlgorithmMap[CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94.Id] = "GOST3410";
 
             AlgorithmMap["ECGOST-3410"] = "ECGOST3410";
-            AlgorithmMap["ECGOST-3410-2001"] = "ECGOST3410";
+            AlgorithmMap["GOST-3410-2001"] = "ECGOST3410";
             AlgorithmMap["GOST3411WITHECGOST3410"] = "ECGOST3410";
+            AlgorithmMap["GOST3411/ECGOST3410"] = "ECGOST3410";
             AlgorithmMap[CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001.Id] = "ECGOST3410";
 
+            AlgorithmMap["GOST-3410-2012-256"] = "ECGOST3410-2012-256";
+            AlgorithmMap["GOST3411WITHECGOST3410-2012-256"] = "ECGOST3410-2012-256";
+            AlgorithmMap["GOST3411-2012-256WITHECGOST3410-2012-256"] = "ECGOST3410-2012-256";
+            AlgorithmMap["GOST3411-2012-256/ECGOST3410-2012-256"] = "ECGOST3410-2012-256";
+            AlgorithmMap[RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_256.Id] =
+                "ECGOST3410-2012-256";
+
+            AlgorithmMap["GOST-3410-2012-512"] = "ECGOST3410-2012-512";
+            AlgorithmMap["GOST3411WITHECGOST3410-2012-512"] = "ECGOST3410-2012-512";
+            AlgorithmMap["GOST3411-2012-512WITHECGOST3410-2012-512"] = "ECGOST3410-2012-512";
+            AlgorithmMap["GOST3411-2012-512/ECGOST3410-2012-512"] = "ECGOST3410-2012-512";
+            AlgorithmMap[RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512.Id] =
+                "ECGOST3410-2012-512";
+
             AlgorithmMap["ED25519"] = "Ed25519";
             AlgorithmMap[EdECObjectIdentifiers.id_Ed25519.Id] = "Ed25519";
             AlgorithmMap["ED25519CTX"] = "Ed25519ctx";
@@ -439,6 +456,9 @@ namespace Org.BouncyCastle.Security
             Oids["GOST3410"] = CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x94;
             Oids["ECGOST3410"] = CryptoProObjectIdentifiers.GostR3411x94WithGostR3410x2001;
 
+            Oids["ECGOST3410-2012-256"] = RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_256;
+            Oids["ECGOST3410-2012-512"] = RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512;
+
             Oids["Ed25519"] = EdECObjectIdentifiers.id_Ed25519;
             Oids["Ed448"] = EdECObjectIdentifiers.id_Ed448;
 
@@ -618,6 +638,14 @@ namespace Org.BouncyCastle.Security
             {
                 return new Gost3410DigestSigner(new ECGost3410Signer(), new Gost3411Digest());
             }
+            if (mechanism.Equals("ECGOST3410-2012-256"))
+            {
+                return new Gost3410DigestSigner(new ECGost3410Signer(), new Gost3411_2012_256Digest());
+            }
+            if (mechanism.Equals("ECGOST3410-2012-512"))
+            {
+                return new Gost3410DigestSigner(new ECGost3410Signer(), new Gost3411_2012_512Digest());
+            }
 
             if (mechanism.Equals("SHA1WITHRSA/ISO9796-2"))
             {
diff --git a/crypto/src/security/WrapperUtilities.cs b/crypto/src/security/WrapperUtilities.cs
index 48ffe3a96..74e13f121 100644
--- a/crypto/src/security/WrapperUtilities.cs
+++ b/crypto/src/security/WrapperUtilities.cs
@@ -27,7 +27,7 @@ namespace Org.BouncyCastle.Security
         static WrapperUtilities()
         {
             // Signal to obfuscation tools not to change enum constants
-            ((WrapAlgorithm)Enums.GetArbitraryValue(typeof(WrapAlgorithm))).ToString();
+            Enums.GetArbitraryValue<WrapAlgorithm>().ToString();
 
             Algorithms[NistObjectIdentifiers.IdAes128Wrap.Id] = "AESWRAP";
             Algorithms[NistObjectIdentifiers.IdAes192Wrap.Id] = "AESWRAP";
@@ -56,8 +56,7 @@ namespace Org.BouncyCastle.Security
 
             try
             {
-                WrapAlgorithm wrapAlgorithm = (WrapAlgorithm)Enums.GetEnumValue(
-                    typeof(WrapAlgorithm), mechanism);
+                WrapAlgorithm wrapAlgorithm = Enums.GetEnumValue<WrapAlgorithm>(mechanism);
 
                 switch (wrapAlgorithm)
                 {
diff --git a/crypto/src/tls/AbstractTlsClient.cs b/crypto/src/tls/AbstractTlsClient.cs
index 8bfd828f1..3061f3642 100644
--- a/crypto/src/tls/AbstractTlsClient.cs
+++ b/crypto/src/tls/AbstractTlsClient.cs
@@ -174,6 +174,16 @@ namespace Org.BouncyCastle.Tls
             return null;
         }
 
+        protected virtual short[] GetAllowedClientCertificateTypes()
+        {
+            return null;
+        }
+
+        protected virtual short[] GetAllowedServerCertificateTypes()
+        {
+            return null;
+        }
+
         public virtual void Init(TlsClientContext context)
         {
             this.m_context = context;
@@ -334,6 +344,33 @@ namespace Org.BouncyCastle.Tls
                 }
             }
 
+            /*
+             * RFC 7250 4.1:
+             *
+             * If the client has no remaining certificate types to send in
+             * the client hello, other than the default X.509 type, it MUST omit the
+             * client_certificate_type extension in the client hello.
+             */
+            short[] clientCertTypes = GetAllowedClientCertificateTypes();
+            if (clientCertTypes != null && (clientCertTypes.Length > 1 || clientCertTypes[0] != CertificateType.X509))
+            {
+                TlsExtensionsUtilities.AddClientCertificateTypeExtensionClient(clientExtensions, clientCertTypes);
+            }
+
+            /*
+             * RFC 7250 4.1:
+             *
+             * If the client has no remaining certificate types to send in
+             * the client hello, other than the default X.509 certificate type, it
+             * MUST omit the entire server_certificate_type extension from the
+             * client hello.
+             */
+            short[] serverCertTypes = GetAllowedServerCertificateTypes();
+            if (serverCertTypes != null && (serverCertTypes.Length > 1 || serverCertTypes[0] != CertificateType.X509))
+            {
+                TlsExtensionsUtilities.AddServerCertificateTypeExtensionClient(clientExtensions, serverCertTypes);
+            }
+
             return clientExtensions;
         }
 
diff --git a/crypto/src/tls/AbstractTlsContext.cs b/crypto/src/tls/AbstractTlsContext.cs
index be7a67dfc..fa9bc80cd 100644
--- a/crypto/src/tls/AbstractTlsContext.cs
+++ b/crypto/src/tls/AbstractTlsContext.cs
@@ -11,7 +11,7 @@ namespace Org.BouncyCastle.Tls
     internal abstract class AbstractTlsContext
         : TlsContext
     {
-        private static long counter = Times.NanoTime();
+        private static long counter = DateTime.UtcNow.Ticks;
 
         private static long NextCounterValue()
         {
@@ -20,9 +20,15 @@ namespace Org.BouncyCastle.Tls
 
         private static TlsNonceGenerator CreateNonceGenerator(TlsCrypto crypto, int connectionEnd)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> additionalSeedMaterial = stackalloc byte[16];
+            Pack.UInt64_To_BE((ulong)NextCounterValue(), additionalSeedMaterial);
+            Pack.UInt64_To_BE((ulong)DateTime.UtcNow.Ticks, additionalSeedMaterial[8..]);
+#else
             byte[] additionalSeedMaterial = new byte[16];
             Pack.UInt64_To_BE((ulong)NextCounterValue(), additionalSeedMaterial, 0);
-            Pack.UInt64_To_BE((ulong)Times.NanoTime(), additionalSeedMaterial, 8);
+            Pack.UInt64_To_BE((ulong)DateTime.UtcNow.Ticks, additionalSeedMaterial, 8);
+#endif
             additionalSeedMaterial[0] &= 0x7F;
             additionalSeedMaterial[0] |= (byte)(connectionEnd << 7);
 
@@ -170,6 +176,9 @@ namespace Org.BouncyCastle.Tls
 
             SecurityParameters securityParameters = SecurityParameters;
 
+            if (ChannelBinding.tls_exporter == channelBinding)
+                return ExportKeyingMaterial("EXPORTER-Channel-Binding", TlsUtilities.EmptyBytes, 32);
+
             if (TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
                 return null;
 
diff --git a/crypto/src/tls/AbstractTlsServer.cs b/crypto/src/tls/AbstractTlsServer.cs
index a41bc4710..3c62793b6 100644
--- a/crypto/src/tls/AbstractTlsServer.cs
+++ b/crypto/src/tls/AbstractTlsServer.cs
@@ -207,6 +207,16 @@ namespace Org.BouncyCastle.Tls
             return true;
         }
 
+        protected virtual bool PreferLocalClientCertificateTypes()
+        {
+            return false;
+        }
+
+        protected virtual short[] GetAllowedClientCertificateTypes()
+        {
+            return null;
+        }
+
         public virtual void Init(TlsServerContext context)
         {
             this.m_context = context;
@@ -491,6 +501,66 @@ namespace Org.BouncyCastle.Tls
                 TlsExtensionsUtilities.AddMaxFragmentLengthExtension(m_serverExtensions, m_maxFragmentLengthOffered);
             }
 
+            // RFC 7250 4.2 for server_certificate_type
+            short[] serverCertTypes = TlsExtensionsUtilities.GetServerCertificateTypeExtensionClient(
+                m_clientExtensions);
+            if (serverCertTypes != null)
+            {
+                TlsCredentials credentials = GetCredentials();
+
+                if (credentials == null || !Arrays.Contains(serverCertTypes, credentials.Certificate.CertificateType))
+                {
+                    // outcome 2: we support the extension but have no common types
+                    throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+                }
+
+                // outcome 3: we support the extension and have a common type
+                TlsExtensionsUtilities.AddServerCertificateTypeExtensionServer(m_serverExtensions,
+                    credentials.Certificate.CertificateType);
+            }
+
+            // RFC 7250 4.2 for client_certificate_type
+            short[] remoteClientCertTypes = TlsExtensionsUtilities.GetClientCertificateTypeExtensionClient(
+                m_clientExtensions);
+            if (remoteClientCertTypes != null)
+            {
+                short[] localClientCertTypes = GetAllowedClientCertificateTypes();
+                if (localClientCertTypes != null)
+                {
+                    short[] preferredTypes;
+                    short[] nonPreferredTypes;
+                    if (PreferLocalClientCertificateTypes())
+                    {
+                        preferredTypes = localClientCertTypes;
+                        nonPreferredTypes = remoteClientCertTypes;
+                    }
+                    else
+                    {
+                        preferredTypes = remoteClientCertTypes;
+                        nonPreferredTypes = localClientCertTypes;
+                    }
+
+                    short selectedType = -1;
+                    for (int i = 0; i < preferredTypes.Length; i++)
+                    {
+                        if (Arrays.Contains(nonPreferredTypes, preferredTypes[i]))
+                        {
+                            selectedType = preferredTypes[i];
+                            break;
+                        }
+                    }
+
+                    if (selectedType == -1)
+                    {
+                        // outcome 2: we support the extension but have no common types
+                        throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+                    }
+
+                    // outcome 3: we support the extension and have a common type
+                    TlsExtensionsUtilities.AddClientCertificateTypeExtensionServer(m_serverExtensions, selectedType);
+                } // else outcome 1: we don't support the extension
+            }
+
             return m_serverExtensions;
         }
 
diff --git a/crypto/src/tls/ByteQueue.cs b/crypto/src/tls/ByteQueue.cs
index 45bec8be4..e06ad6346 100644
--- a/crypto/src/tls/ByteQueue.cs
+++ b/crypto/src/tls/ByteQueue.cs
@@ -1,24 +1,17 @@
 using System;
 using System.IO;
 
+using Org.BouncyCastle.Utilities;
+
 namespace Org.BouncyCastle.Tls
 {
     /// <summary>A queue for bytes. This file could be more optimized.</summary>
     public sealed class ByteQueue
     {
         /// <returns>The smallest number which can be written as 2^x which is bigger than i.</returns>
-        public static int NextTwoPow(int i)
+        private static int GetAllocationSize(int i)
         {
-            /*
-             * This code is based of a lot of code I found on the Internet which mostly
-             * referenced a book called "Hacking delight".
-             */
-            i |= i >> 1;
-            i |= i >> 2;
-            i |= i >> 4;
-            i |= i >> 8;
-            i |= i >> 16;
-            return i + 1;
+            return Integers.HighestOneBit((256 | i) << 1);
         }
 
         /// <summary>The buffer where we store our data.</summary>
@@ -56,6 +49,9 @@ namespace Org.BouncyCastle.Tls
         /// <param name="len">How many bytes to read from the array.</param>
         public void AddData(byte[] buf, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            AddData(buf.AsSpan(off, len));
+#else
             if (m_readOnlyBuf)
                 throw new InvalidOperationException("Cannot add data to read-only buffer");
 
@@ -63,14 +59,14 @@ namespace Org.BouncyCastle.Tls
             {
                 if (len > m_databuf.Length)
                 {
-                    int desiredSize = NextTwoPow(len | 256);
+                    int desiredSize = GetAllocationSize(len);
                     m_databuf = new byte[desiredSize];
                 }
                 m_skipped = 0;
             }
             else if ((m_skipped + m_available + len) > m_databuf.Length)
             {
-                int desiredSize = NextTwoPow(m_available + len);
+                int desiredSize = GetAllocationSize(m_available + len);
                 if (desiredSize > m_databuf.Length)
                 {
                     byte[] tmp = new byte[desiredSize];
@@ -86,8 +82,46 @@ namespace Org.BouncyCastle.Tls
 
             Array.Copy(buf, off, m_databuf, m_skipped + m_available, len);
             m_available += len;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void AddData(ReadOnlySpan<byte> buffer)
+        {
+            if (m_readOnlyBuf)
+                throw new InvalidOperationException("Cannot add data to read-only buffer");
+
+            int len = buffer.Length;
+            if (m_available == 0)
+            {
+                if (len > m_databuf.Length)
+                {
+                    int desiredSize = GetAllocationSize(len);
+                    m_databuf = new byte[desiredSize];
+                }
+                m_skipped = 0;
+            }
+            else if ((m_skipped + m_available + len) > m_databuf.Length)
+            {
+                int desiredSize = GetAllocationSize(m_available + len);
+                if (desiredSize > m_databuf.Length)
+                {
+                    byte[] tmp = new byte[desiredSize];
+                    Array.Copy(m_databuf, m_skipped, tmp, 0, m_available);
+                    m_databuf = tmp;
+                }
+                else
+                {
+                    Array.Copy(m_databuf, m_skipped, m_databuf, 0, m_available);
+                }
+                m_skipped = 0;
+            }
+
+            buffer.CopyTo(m_databuf.AsSpan(m_skipped + m_available));
+            m_available += len;
+        }
+#endif
+
         /// <returns>The number of bytes which are available in this buffer.</returns>
         public int Available
         {
@@ -124,6 +158,16 @@ namespace Org.BouncyCastle.Tls
             Array.Copy(m_databuf, m_skipped + skip, buf, offset, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Read(Span<byte> buffer, int skip)
+        {
+            if ((m_available - skip) < buffer.Length)
+                throw new InvalidOperationException("Not enough data to read");
+
+            buffer.CopyFrom(m_databuf.AsSpan(m_skipped + skip));
+        }
+#endif
+
         /// <summary>Return a <see cref="HandshakeMessageInput"/> over some bytes at the beginning of the data.
         /// </summary>
         /// <param name="length">How many bytes will be readable.</param>
@@ -182,6 +226,14 @@ namespace Org.BouncyCastle.Tls
             RemoveData(skip + len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void RemoveData(Span<byte> buffer, int skip)
+        {
+            Read(buffer, skip);
+            RemoveData(skip + buffer.Length);
+        }
+#endif
+
         public byte[] RemoveData(int len, int skip)
         {
             byte[] buf = new byte[len];
@@ -198,7 +250,7 @@ namespace Org.BouncyCastle.Tls
             }
             else
             {
-                int desiredSize = NextTwoPow(m_available);
+                int desiredSize = GetAllocationSize(m_available);
                 if (desiredSize < m_databuf.Length)
                 {
                     byte[] tmp = new byte[desiredSize];
diff --git a/crypto/src/tls/ByteQueueInputStream.cs b/crypto/src/tls/ByteQueueInputStream.cs
index 0b15071ad..ab26faa98 100644
--- a/crypto/src/tls/ByteQueueInputStream.cs
+++ b/crypto/src/tls/ByteQueueInputStream.cs
@@ -40,6 +40,15 @@ namespace Org.BouncyCastle.Tls
             return bytesToRead;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int bytesToRead = System.Math.Min(m_buffer.Available, buffer.Length);
+            m_buffer.RemoveData(buffer[..bytesToRead], 0);
+            return bytesToRead;
+        }
+#endif
+
         public override int ReadByte()
         {
             if (m_buffer.Available == 0)
@@ -59,16 +68,5 @@ namespace Org.BouncyCastle.Tls
         {
             get { return m_buffer.Available; }
         }
-
-#if PORTABLE
-        //protected override void Dispose(bool disposing)
-        //{
-        //    base.Dispose(disposing);
-        //}
-#else
-        public override void Close()
-        {
-        }
-#endif
     }
 }
diff --git a/crypto/src/tls/ByteQueueOutputStream.cs b/crypto/src/tls/ByteQueueOutputStream.cs
index 441a3773a..ecb1f865d 100644
--- a/crypto/src/tls/ByteQueueOutputStream.cs
+++ b/crypto/src/tls/ByteQueueOutputStream.cs
@@ -27,6 +27,13 @@ namespace Org.BouncyCastle.Tls
             m_buffer.AddData(buffer, offset, count);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_buffer.AddData(buffer);
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             m_buffer.AddData(new byte[]{ value }, 0, 1);
diff --git a/crypto/src/tls/Certificate.cs b/crypto/src/tls/Certificate.cs
index c7f08b2aa..30b14368b 100644
--- a/crypto/src/tls/Certificate.cs
+++ b/crypto/src/tls/Certificate.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-
+using Org.BouncyCastle.Pqc.Crypto.Lms;
 using Org.BouncyCastle.Tls.Crypto;
 
 namespace Org.BouncyCastle.Tls
@@ -25,18 +25,8 @@ namespace Org.BouncyCastle.Tls
 
         public sealed class ParseOptions
         {
-            private int m_maxChainLength = int.MaxValue;
-
-            public int MaxChainLength
-            {
-                get { return m_maxChainLength; }
-            }
-
-            public ParseOptions SetMaxChainLength(int maxChainLength)
-            {
-                this.m_maxChainLength = maxChainLength;
-                return this;
-            }
+            public short CertificateType { get; set; } = Tls.CertificateType.X509;
+            public int MaxChainLength { get; set; } = int.MaxValue;
         }
 
         private static CertificateEntry[] Convert(TlsCertificate[] certificateList)
@@ -55,22 +45,29 @@ namespace Org.BouncyCastle.Tls
 
         private readonly byte[] m_certificateRequestContext;
         private readonly CertificateEntry[] m_certificateEntryList;
+        private readonly short m_certificateType;
 
         public Certificate(TlsCertificate[] certificateList)
             : this(null, Convert(certificateList))
         {
         }
 
-        // TODO[tls13] Prefer to manage the certificateRequestContext internally only? 
         public Certificate(byte[] certificateRequestContext, CertificateEntry[] certificateEntryList)
+            : this(Tls.CertificateType.X509, certificateRequestContext, certificateEntryList)
+        {
+        }
+
+        // TODO[tls13] Prefer to manage the certificateRequestContext internally only?
+        public Certificate(short certificateType, byte[] certificateRequestContext, CertificateEntry[] certificateEntryList)
         {
             if (null != certificateRequestContext && !TlsUtilities.IsValidUint8(certificateRequestContext.Length))
                 throw new ArgumentException("cannot be longer than 255", "certificateRequestContext");
             if (TlsUtilities.IsNullOrContainsNull(certificateEntryList))
                 throw new ArgumentException("cannot be null or contain any nulls", "certificateEntryList");
 
-            this.m_certificateRequestContext = TlsUtilities.Clone(certificateRequestContext);
-            this.m_certificateEntryList = certificateEntryList;
+            m_certificateRequestContext = TlsUtilities.Clone(certificateRequestContext);
+            m_certificateEntryList = certificateEntryList;
+            m_certificateType = certificateType;
         }
 
         public byte[] GetCertificateRequestContext()
@@ -99,22 +96,13 @@ namespace Org.BouncyCastle.Tls
             return CloneCertificateEntryList();
         }
 
-        public short CertificateType
-        {
-            get { return Tls.CertificateType.X509; }
-        }
+        public short CertificateType => m_certificateType;
 
-        public int Length
-        {
-            get { return m_certificateEntryList.Length; }
-        }
+        public int Length => m_certificateEntryList.Length;
 
         /// <returns><c>true</c> if this certificate chain contains no certificates, or <c>false</c> otherwise.
         /// </returns>
-        public bool IsEmpty
-        {
-            get { return m_certificateEntryList.Length == 0; }
-        }
+        public bool IsEmpty => m_certificateEntryList.Length == 0;
 
         /// <summary>Encode this <see cref="Certificate"/> to a <see cref="Stream"/>, and optionally calculate the
         /// "end point hash" (per RFC 5929's tls-server-end-point binding).</summary>
@@ -168,8 +156,13 @@ namespace Org.BouncyCastle.Tls
                 }
             }
 
-            TlsUtilities.CheckUint24(totalLength);
-            TlsUtilities.WriteUint24((int)totalLength, messageOutput);
+            // RFC 7250 indicates the raw key is not wrapped in a cert list like X509 is
+            // but RFC 8446 wraps it in a CertificateEntry, which is inside certificate_list
+            if (isTlsV13 || m_certificateType != Tls.CertificateType.RawPublicKey)
+            {
+                TlsUtilities.CheckUint24(totalLength);
+                TlsUtilities.WriteUint24((int)totalLength, messageOutput);
+            }
 
             for (int i = 0; i < count; ++i)
             {
@@ -195,6 +188,7 @@ namespace Org.BouncyCastle.Tls
         {
             SecurityParameters securityParameters = context.SecurityParameters;
             bool isTlsV13 = TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion);
+            short certType = options.CertificateType;
 
             byte[] certificateRequestContext = null;
             if (isTlsV13)
@@ -207,7 +201,7 @@ namespace Org.BouncyCastle.Tls
             {
                 return !isTlsV13 ? EmptyChain
                     :  certificateRequestContext.Length < 1 ? EmptyChainTls13
-                    :  new Certificate(certificateRequestContext, EmptyCertEntries);
+                    :  new Certificate(certType, certificateRequestContext, EmptyCertEntries);
             }
 
             byte[] certListData = TlsUtilities.ReadFully(totalLength, messageInput);
@@ -225,8 +219,20 @@ namespace Org.BouncyCastle.Tls
                         "Certificate chain longer than maximum (" + maxChainLength + ")");
                 }
 
-                byte[] derEncoding = TlsUtilities.ReadOpaque24(buf, 1);
-                TlsCertificate cert = crypto.CreateCertificate(derEncoding);
+                // RFC 7250 indicates the raw key is not wrapped in a cert list like X509 is
+                // but RFC 8446 wraps it in a CertificateEntry, which is inside certificate_list
+                byte[] derEncoding;
+                if (isTlsV13 || certType != Tls.CertificateType.RawPublicKey)
+                {
+                    derEncoding = TlsUtilities.ReadOpaque24(buf, 1);
+                }
+                else
+                {
+                    derEncoding = certListData;
+                    buf.Seek(totalLength, SeekOrigin.Current);
+                }
+
+                TlsCertificate cert = crypto.CreateCertificate(certType, derEncoding);
 
                 if (certificate_list.Count < 1 && endPointHashOutput != null)
                 {
@@ -250,7 +256,7 @@ namespace Org.BouncyCastle.Tls
                 certificateList[i] = (CertificateEntry)certificate_list[i];
             }
 
-            return new Certificate(certificateRequestContext, certificateList);
+            return new Certificate(certType, certificateRequestContext, certificateList);
         }
 
         private static void CalculateEndPointHash(TlsContext context, TlsCertificate cert, byte[] encoding,
diff --git a/crypto/src/tls/ChannelBinding.cs b/crypto/src/tls/ChannelBinding.cs
index 84f8bc4df..d6e0cbe27 100644
--- a/crypto/src/tls/ChannelBinding.cs
+++ b/crypto/src/tls/ChannelBinding.cs
@@ -15,5 +15,10 @@ namespace Org.BouncyCastle.Tls
         public const int tls_server_end_point = 0;
         public const int tls_unique = 1;
         public const int tls_unique_for_telnet = 2;
+
+        /*
+         * RFC 9266
+         */
+        public const int tls_exporter = 3;
     }
 }
diff --git a/crypto/src/tls/CombinedHash.cs b/crypto/src/tls/CombinedHash.cs
index 71151d2a5..360b9d426 100644
--- a/crypto/src/tls/CombinedHash.cs
+++ b/crypto/src/tls/CombinedHash.cs
@@ -43,6 +43,14 @@ namespace Org.BouncyCastle.Tls
             m_sha1.Update(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            m_md5.Update(input);
+            m_sha1.Update(input);
+        }
+#endif
+
         public virtual byte[] CalculateHash()
         {
             if (null != m_context && TlsUtilities.IsSsl(m_context))
diff --git a/crypto/src/tls/DeferredHash.cs b/crypto/src/tls/DeferredHash.cs
index 82f7899a5..e6397ab1e 100644
--- a/crypto/src/tls/DeferredHash.cs
+++ b/crypto/src/tls/DeferredHash.cs
@@ -176,6 +176,22 @@ namespace Org.BouncyCastle.Tls
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            if (m_buf != null)
+            {
+                m_buf.Write(input);
+                return;
+            }
+
+            foreach (TlsHash hash in m_hashes.Values)
+            {
+                hash.Update(input);
+            }
+        }
+#endif
+
         public byte[] CalculateHash()
         {
             throw new InvalidOperationException("Use 'ForkPrfHash' to get a definite hash");
diff --git a/crypto/src/tls/DtlsClientProtocol.cs b/crypto/src/tls/DtlsClientProtocol.cs
index 3e3aab662..b8c09617a 100644
--- a/crypto/src/tls/DtlsClientProtocol.cs
+++ b/crypto/src/tls/DtlsClientProtocol.cs
@@ -579,6 +579,10 @@ namespace Org.BouncyCastle.Tls
             TlsProtocol.AssertEmpty(buf);
 
             state.certificateRequest = TlsUtilities.ValidateCertificateRequest(certificateRequest, state.keyExchange);
+
+            state.clientContext.SecurityParameters.m_clientCertificateType =
+                TlsExtensionsUtilities.GetClientCertificateTypeExtensionServer(state.serverExtensions,
+                    CertificateType.X509);
         }
 
         /// <exception cref="IOException"/>
@@ -633,7 +637,7 @@ namespace Org.BouncyCastle.Tls
         protected virtual void ProcessServerCertificate(ClientHandshakeState state, byte[] body)
         {
             state.authentication = TlsUtilities.ReceiveServerCertificate(state.clientContext, state.client,
-                new MemoryStream(body, false));
+                new MemoryStream(body, false), state.serverExtensions);
         }
 
         /// <exception cref="IOException"/>
diff --git a/crypto/src/tls/DtlsReliableHandshake.cs b/crypto/src/tls/DtlsReliableHandshake.cs
index 32c5c7851..8d6eb7b84 100644
--- a/crypto/src/tls/DtlsReliableHandshake.cs
+++ b/crypto/src/tls/DtlsReliableHandshake.cs
@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.IO;
 
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Date;
 
 namespace Org.BouncyCastle.Tls
diff --git a/crypto/src/tls/DtlsServerProtocol.cs b/crypto/src/tls/DtlsServerProtocol.cs
index 51e013290..b42f97b64 100644
--- a/crypto/src/tls/DtlsServerProtocol.cs
+++ b/crypto/src/tls/DtlsServerProtocol.cs
@@ -637,7 +637,11 @@ namespace Org.BouncyCastle.Tls
             MemoryStream buf = new MemoryStream(body, false);
 
             Certificate.ParseOptions options = new Certificate.ParseOptions()
-                .SetMaxChainLength(state.server.GetMaxCertificateChainLength());
+            {
+                CertificateType = TlsExtensionsUtilities.GetClientCertificateTypeExtensionServer(
+                    state.clientExtensions, CertificateType.X509),
+                MaxChainLength = state.server.GetMaxCertificateChainLength(),
+            };
 
             Certificate clientCertificate = Certificate.Parse(options, state.serverContext, buf, null);
 
@@ -667,7 +671,7 @@ namespace Org.BouncyCastle.Tls
         protected virtual void ProcessClientHello(ServerHandshakeState state, byte[] body)
         {
             MemoryStream buf = new MemoryStream(body, false);
-            ClientHello clientHello = ClientHello.Parse(buf, new NullOutputStream());
+            ClientHello clientHello = ClientHello.Parse(buf, Stream.Null);
             ProcessClientHello(state, clientHello);
         }
 
diff --git a/crypto/src/tls/RecordStream.cs b/crypto/src/tls/RecordStream.cs
index a97d34698..a5926d05b 100644
--- a/crypto/src/tls/RecordStream.cs
+++ b/crypto/src/tls/RecordStream.cs
@@ -258,6 +258,9 @@ namespace Org.BouncyCastle.Tls
         /// <exception cref="IOException"/>
         internal void WriteRecord(short contentType, byte[] plaintext, int plaintextOffset, int plaintextLength)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            WriteRecord(contentType, plaintext.AsSpan(plaintextOffset, plaintextLength));
+#else
             // Never send anything until a valid ClientHello has been received
             if (m_writeVersion == null)
                 return;
@@ -298,8 +301,56 @@ namespace Org.BouncyCastle.Tls
             //}
 
             m_output.Flush();
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        internal void WriteRecord(short contentType, ReadOnlySpan<byte> plaintext)
+        {
+            // Never send anything until a valid ClientHello has been received
+            if (m_writeVersion == null)
+                return;
+
+            /*
+             * RFC 5246 6.2.1 The length should not exceed 2^14.
+             */
+            CheckLength(plaintext.Length, m_plaintextLimit, AlertDescription.internal_error);
+
+            /*
+             * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (plaintext.Length < 1 && contentType != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            long seqNo = m_writeSeqNo.NextValue(AlertDescription.internal_error);
+            ProtocolVersion recordVersion = m_writeVersion;
+
+            TlsEncodeResult encoded = m_writeCipher.EncodePlaintext(seqNo, contentType, recordVersion,
+                RecordFormat.FragmentOffset, plaintext);
+
+            int ciphertextLength = encoded.len - RecordFormat.FragmentOffset;
+            TlsUtilities.CheckUint16(ciphertextLength);
+
+            TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + RecordFormat.TypeOffset);
+            TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + RecordFormat.VersionOffset);
+            TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + RecordFormat.LengthOffset);
+
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //try
+            //{
+                m_output.Write(encoded.buf, encoded.off, encoded.len);
+            //}
+            //catch (InterruptedIOException e)
+            //{
+            //    throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            //}
+
+            m_output.Flush();
+        }
+#endif
+
         /// <exception cref="IOException"/>
         internal void Close()
         {
diff --git a/crypto/src/tls/SecurityParameters.cs b/crypto/src/tls/SecurityParameters.cs
index 7891549b6..7775ca7c7 100644
--- a/crypto/src/tls/SecurityParameters.cs
+++ b/crypto/src/tls/SecurityParameters.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 
 using Org.BouncyCastle.Tls.Crypto;
 
@@ -52,6 +51,7 @@ namespace Org.BouncyCastle.Tls
         internal Certificate m_peerCertificate = null;
         internal ProtocolVersion m_negotiatedVersion = null;
         internal int m_statusRequestVersion = 0;
+        internal short m_clientCertificateType = -1;
 
         // TODO[tls-ops] Investigate whether we can handle verify data using TlsSecret
         internal byte[] m_localVerifyData = null;
@@ -100,6 +100,11 @@ namespace Org.BouncyCastle.Tls
             get { return m_cipherSuite; }
         }
 
+        public short ClientCertificateType
+        {
+            get { return m_clientCertificateType; }
+        }
+
         public short[] ClientCertTypes
         {
             get { return m_clientCertTypes; }
diff --git a/crypto/src/tls/TlsClientProtocol.cs b/crypto/src/tls/TlsClientProtocol.cs
index fc3894710..b7295bcc5 100644
--- a/crypto/src/tls/TlsClientProtocol.cs
+++ b/crypto/src/tls/TlsClientProtocol.cs
@@ -380,8 +380,8 @@ namespace Org.BouncyCastle.Tls
                      * NOTE: Certificate processing (including authentication) is delayed to allow for a
                      * possible CertificateStatus message.
                      */
-                    this.m_authentication = TlsUtilities.ReceiveServerCertificate(m_tlsClientContext, m_tlsClient,
-                        buf);
+                    m_authentication = TlsUtilities.ReceiveServerCertificate(m_tlsClientContext, m_tlsClient, buf,
+                        m_serverExtensions);
                     break;
                 }
                 default:
@@ -1364,6 +1364,10 @@ namespace Org.BouncyCastle.Tls
 
             this.m_certificateRequest = certificateRequest;
 
+            m_tlsClientContext.SecurityParameters.m_clientCertificateType =
+                TlsExtensionsUtilities.GetClientCertificateTypeExtensionServer(m_serverExtensions,
+                    CertificateType.X509);
+
             TlsUtilities.EstablishServerSigAlgs(m_tlsClientContext.SecurityParameters, certificateRequest);
         }
 
@@ -1467,7 +1471,8 @@ namespace Org.BouncyCastle.Tls
             if (m_selectedPsk13)
                 throw new TlsFatalAlert(AlertDescription.unexpected_message);
 
-            this.m_authentication = TlsUtilities.Receive13ServerCertificate(m_tlsClientContext, m_tlsClient, buf);
+            m_authentication = TlsUtilities.Receive13ServerCertificate(m_tlsClientContext, m_tlsClient, buf,
+                m_serverExtensions);
 
             // NOTE: In TLS 1.3 we don't have to wait for a possible CertificateStatus message.
             HandleServerCertificate();
@@ -1509,7 +1514,11 @@ namespace Org.BouncyCastle.Tls
 
             AssertEmpty(buf);
 
-            this.m_certificateRequest = TlsUtilities.ValidateCertificateRequest(certificateRequest, m_keyExchange);
+            m_certificateRequest = TlsUtilities.ValidateCertificateRequest(certificateRequest, m_keyExchange);
+
+            m_tlsClientContext.SecurityParameters.m_clientCertificateType =
+                TlsExtensionsUtilities.GetClientCertificateTypeExtensionServer(m_serverExtensions,
+                    CertificateType.X509);
         }
 
         /// <exception cref="IOException"/>
diff --git a/crypto/src/tls/TlsExtensionsUtilities.cs b/crypto/src/tls/TlsExtensionsUtilities.cs
index 5a97e1efc..46d42417c 100644
--- a/crypto/src/tls/TlsExtensionsUtilities.cs
+++ b/crypto/src/tls/TlsExtensionsUtilities.cs
@@ -302,10 +302,11 @@ namespace Org.BouncyCastle.Tls
         }
 
         /// <exception cref="IOException"/>
-        public static short GetClientCertificateTypeExtensionServer(IDictionary<int, byte[]> extensions)
+        public static short GetClientCertificateTypeExtensionServer(IDictionary<int, byte[]> extensions,
+            short defaultValue)
         {
             byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type);
-            return extensionData == null ? (short)-1 : ReadCertificateTypeExtensionServer(extensionData);
+            return extensionData == null ? defaultValue : ReadCertificateTypeExtensionServer(extensionData);
         }
 
         /// <exception cref="IOException"/>
@@ -415,10 +416,11 @@ namespace Org.BouncyCastle.Tls
         }
 
         /// <exception cref="IOException"/>
-        public static short GetServerCertificateTypeExtensionServer(IDictionary<int, byte[]> extensions)
+        public static short GetServerCertificateTypeExtensionServer(IDictionary<int, byte[]> extensions,
+            short defaultValue)
         {
             byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type);
-            return extensionData == null ? (short)-1 : ReadCertificateTypeExtensionServer(extensionData);
+            return extensionData == null ? defaultValue : ReadCertificateTypeExtensionServer(extensionData);
         }
 
         /// <exception cref="IOException"/>
diff --git a/crypto/src/tls/TlsProtocol.cs b/crypto/src/tls/TlsProtocol.cs
index dbbb6e0f5..437a51447 100644
--- a/crypto/src/tls/TlsProtocol.cs
+++ b/crypto/src/tls/TlsProtocol.cs
@@ -707,6 +707,9 @@ namespace Org.BouncyCastle.Tls
         {
             Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ReadApplicationData(buffer.AsSpan(offset, count));
+#else
             if (!m_appDataReady)
                 throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
 
@@ -717,7 +720,7 @@ namespace Org.BouncyCastle.Tls
                     if (this.m_failedWithError)
                         throw new IOException("Cannot read application data on failed TLS connection");
 
-                    return -1;
+                    return 0;
                 }
 
                 /*
@@ -733,8 +736,42 @@ namespace Org.BouncyCastle.Tls
                 m_applicationDataQueue.RemoveData(buffer, offset, count, 0);
             }
             return count;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ReadApplicationData(Span<byte> buffer)
+        {
+            if (!m_appDataReady)
+                throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
+
+            while (m_applicationDataQueue.Available < 1)
+            {
+                if (this.m_closed)
+                {
+                    if (this.m_failedWithError)
+                        throw new IOException("Cannot read application data on failed TLS connection");
+
+                    return 0;
+                }
+
+                /*
+                 * NOTE: Only called more than once when empty records are received, so no special
+                 * InterruptedIOException handling is necessary.
+                 */
+                SafeReadRecord();
+            }
+
+            int count = buffer.Length;
+            if (count > 0)
+            {
+                count = System.Math.Min(count, m_applicationDataQueue.Available);
+                m_applicationDataQueue.RemoveData(buffer[..count], 0);
+            }
+            return count;
+        }
+#endif
+
         /// <exception cref="IOException"/>
         protected virtual RecordPreview SafePreviewRecordHeader(byte[] recordHeader)
         {
@@ -850,6 +887,32 @@ namespace Org.BouncyCastle.Tls
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        protected virtual void SafeWriteRecord(short type, ReadOnlySpan<byte> buffer)
+        {
+            try
+            {
+                m_recordStream.WriteRecord(type, buffer);
+            }
+            catch (TlsFatalAlert e)
+            {
+                HandleException(e.AlertDescription, "Failed to write record", e);
+                throw e;
+            }
+            catch (IOException e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to write record", e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to write record", e);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+#endif
+
         /// <summary>Write some application data.</summary>
         /// <remarks>
         /// Fragmentation is handled internally. Usable in both blocking/non-blocking modes.<br/><br/>
@@ -869,6 +932,9 @@ namespace Org.BouncyCastle.Tls
         {
             Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            WriteApplicationData(buffer.AsSpan(offset, count));
+#else
             if (!m_appDataReady)
                 throw new InvalidOperationException(
                     "Cannot write application data until initial handshake completed.");
@@ -938,7 +1004,82 @@ namespace Org.BouncyCastle.Tls
                     count -= toWrite;
                 }
             }
+#endif
+        }
+
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void WriteApplicationData(ReadOnlySpan<byte> buffer)
+        {
+            if (!m_appDataReady)
+                throw new InvalidOperationException(
+                    "Cannot write application data until initial handshake completed.");
+
+            lock (m_recordWriteLock)
+            {
+                while (!buffer.IsEmpty)
+                {
+                    if (m_closed)
+                        throw new IOException("Cannot write application data on closed/failed TLS connection");
+
+                    /*
+                     * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
+                     * potentially useful as a traffic analysis countermeasure.
+                     * 
+                     * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
+                     */
+                    if (m_appDataSplitEnabled)
+                    {
+                        /*
+                         * Protect against known IV attack!
+                         * 
+                         * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+                         */
+                        switch (m_appDataSplitMode)
+                        {
+                        case ADS_MODE_0_N_FIRSTONLY:
+                        {
+                            this.m_appDataSplitEnabled = false;
+                            SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
+                            break;
+                        }
+                        case ADS_MODE_0_N:
+                        {
+                            SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
+                            break;
+                        }
+                        case ADS_MODE_1_Nsub1:
+                        default:
+                        {
+                            if (buffer.Length > 1)
+                            {
+                                SafeWriteRecord(ContentType.application_data, buffer[..1]);
+                                buffer = buffer[1..];
+                            }
+                            break;
+                        }
+                        }
+                    }
+                    else if (m_keyUpdateEnabled)
+                    {
+                        if (m_keyUpdatePendingSend)
+                        {
+                            Send13KeyUpdate(false);
+                        }
+                        else if (m_recordStream.NeedsKeyUpdate())
+                        {
+                            Send13KeyUpdate(true);
+                        }
+                    }
+
+                    // Fragment data according to the current fragment limit.
+                    int toWrite = System.Math.Min(buffer.Length, m_recordStream.PlaintextLimit);
+                    SafeWriteRecord(ContentType.application_data, buffer[..toWrite]);
+                    buffer = buffer[toWrite..];
+                }
+            }
         }
+#endif
 
         public virtual int AppDataSplitMode
         {
@@ -1362,7 +1503,12 @@ namespace Org.BouncyCastle.Tls
             SecurityParameters securityParameters = context.SecurityParameters;
             bool isServerContext = context.IsServer;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> verify_data = stackalloc byte[securityParameters.VerifyDataLength];
+            TlsUtilities.ReadFully(verify_data, buf);
+#else
             byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
+#endif
 
             AssertEmpty(buf);
 
@@ -1397,7 +1543,12 @@ namespace Org.BouncyCastle.Tls
             SecurityParameters securityParameters = context.SecurityParameters;
             bool isServerContext = context.IsServer;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> verify_data = stackalloc byte[securityParameters.VerifyDataLength];
+            TlsUtilities.ReadFully(verify_data, buf);
+#else
             byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
+#endif
 
             AssertEmpty(buf);
 
diff --git a/crypto/src/tls/TlsServerProtocol.cs b/crypto/src/tls/TlsServerProtocol.cs
index 3acbe90df..bf4b9119a 100644
--- a/crypto/src/tls/TlsServerProtocol.cs
+++ b/crypto/src/tls/TlsServerProtocol.cs
@@ -1290,7 +1290,11 @@ namespace Org.BouncyCastle.Tls
                 throw new TlsFatalAlert(AlertDescription.unexpected_message);
 
             Certificate.ParseOptions options = new Certificate.ParseOptions()
-                .SetMaxChainLength(m_tlsServer.GetMaxCertificateChainLength());
+            {
+                CertificateType = TlsExtensionsUtilities.GetClientCertificateTypeExtensionServer(m_serverExtensions,
+                    CertificateType.X509),
+                MaxChainLength = m_tlsServer.GetMaxCertificateChainLength(),
+            };
 
             Certificate clientCertificate = Certificate.Parse(options, m_tlsServerContext, buf, null);
 
@@ -1326,7 +1330,11 @@ namespace Org.BouncyCastle.Tls
                 throw new TlsFatalAlert(AlertDescription.unexpected_message);
 
             Certificate.ParseOptions options = new Certificate.ParseOptions()
-                .SetMaxChainLength(m_tlsServer.GetMaxCertificateChainLength());
+            {
+                CertificateType = TlsExtensionsUtilities.GetClientCertificateTypeExtensionServer(m_serverExtensions,
+                    CertificateType.X509),
+                MaxChainLength = m_tlsServer.GetMaxCertificateChainLength(),
+            };
 
             Certificate clientCertificate = Certificate.Parse(options, m_tlsServerContext, buf, null);
 
diff --git a/crypto/src/tls/TlsStream.cs b/crypto/src/tls/TlsStream.cs
index f3dea1574..5c07da2bf 100644
--- a/crypto/src/tls/TlsStream.cs
+++ b/crypto/src/tls/TlsStream.cs
@@ -28,7 +28,6 @@ namespace Org.BouncyCastle.Tls
             get { return true; }
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -37,13 +36,6 @@ namespace Org.BouncyCastle.Tls
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            m_handler.Close();
-            base.Close();
-        }
-#endif
 
         public override void Flush()
         {
@@ -66,10 +58,17 @@ namespace Org.BouncyCastle.Tls
             return m_handler.ReadApplicationData(buffer, offset, count);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            return m_handler.ReadApplicationData(buffer);
+        }
+#endif
+
         public override int ReadByte()
         {
             byte[] buf = new byte[1];
-            int ret = Read(buf, 0, 1);
+            int ret = m_handler.ReadApplicationData(buf, 0, 1);
             return ret <= 0 ? -1 : buf[0];
         }
 
@@ -88,9 +87,16 @@ namespace Org.BouncyCastle.Tls
             m_handler.WriteApplicationData(buffer, offset, count);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_handler.WriteApplicationData(buffer);
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
-            Write(new byte[]{ value }, 0, 1);
+            m_handler.WriteApplicationData(new byte[]{ value }, 0, 1);
         }
     }
 }
diff --git a/crypto/src/tls/TlsUtilities.cs b/crypto/src/tls/TlsUtilities.cs
index 97895e8f2..f12198082 100644
--- a/crypto/src/tls/TlsUtilities.cs
+++ b/crypto/src/tls/TlsUtilities.cs
@@ -309,6 +309,13 @@ namespace Org.BouncyCastle.Tls
             buf[offset] = (byte)i;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void WriteUint8(int i, Span<byte> buf)
+        {
+            buf[0] = (byte)i;
+        }
+#endif
+
         public static void WriteUint16(int i, Stream output)
         {
             output.WriteByte((byte)(i >> 8));
@@ -321,6 +328,14 @@ namespace Org.BouncyCastle.Tls
             buf[offset + 1] = (byte)i;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void WriteUint16(int i, Span<byte> buf)
+        {
+            buf[0] = (byte)(i >> 8);
+            buf[1] = (byte)i;
+        }
+#endif
+
         public static void WriteUint24(int i, Stream output)
         {
             output.WriteByte((byte)(i >> 16));
@@ -409,6 +424,15 @@ namespace Org.BouncyCastle.Tls
             Array.Copy(data, 0, buf, off + 1, data.Length);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void WriteOpaque8(ReadOnlySpan<byte> data, Span<byte> buf)
+        {
+            CheckUint8(data.Length);
+            WriteUint8(data.Length, buf);
+            data.CopyTo(buf[1..]);
+        }
+#endif
+
         public static void WriteOpaque16(byte[] buf, Stream output)
         {
             CheckUint16(buf.Length);
@@ -826,6 +850,15 @@ namespace Org.BouncyCastle.Tls
                 throw new EndOfStreamException();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ReadFully(Span<byte> buf, Stream input)
+        {
+            int length = buf.Length;
+            if (length > 0 && length != Streams.ReadFully(input, buf))
+                throw new EndOfStreamException();
+        }
+#endif
+
         public static byte[] ReadOpaque8(Stream input)
         {
             short length = ReadUint8(input);
@@ -1382,6 +1415,14 @@ namespace Org.BouncyCastle.Tls
             return secret.DeriveUsingPrf(securityParameters.PrfAlgorithm, asciiLabel, seed, length);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static TlsSecret Prf(SecurityParameters securityParameters, TlsSecret secret,
+            ReadOnlySpan<char> asciiLabel, ReadOnlySpan<byte> seed, int length)
+        {
+            return secret.DeriveUsingPrf(securityParameters.PrfAlgorithm, asciiLabel, seed, length);
+        }
+#endif
+
         public static byte[] Clone(byte[] data)
         {
             return null == data ? null : data.Length == 0 ? EmptyBytes : (byte[])data.Clone();
@@ -4756,7 +4797,7 @@ namespace Org.BouncyCastle.Tls
         }
 
         internal static TlsAuthentication ReceiveServerCertificate(TlsClientContext clientContext, TlsClient client,
-            MemoryStream buf)
+            MemoryStream buf, IDictionary<int, byte[]> serverExtensions)
         {
             SecurityParameters securityParameters = clientContext.SecurityParameters;
             if (KeyExchangeAlgorithm.IsAnonymous(securityParameters.KeyExchangeAlgorithm)
@@ -4768,7 +4809,11 @@ namespace Org.BouncyCastle.Tls
             MemoryStream endPointHash = new MemoryStream();
 
             Certificate.ParseOptions options = new Certificate.ParseOptions()
-                .SetMaxChainLength(client.GetMaxCertificateChainLength());
+            {
+                CertificateType = TlsExtensionsUtilities.GetServerCertificateTypeExtensionServer(serverExtensions,
+                    CertificateType.X509),
+                MaxChainLength = client.GetMaxCertificateChainLength(),
+            };
 
             Certificate serverCertificate = Certificate.Parse(options, clientContext, buf, endPointHash);
 
@@ -4788,14 +4833,18 @@ namespace Org.BouncyCastle.Tls
         }
 
         internal static TlsAuthentication Receive13ServerCertificate(TlsClientContext clientContext, TlsClient client,
-            MemoryStream buf)
+            MemoryStream buf, IDictionary<int, byte[]> serverExtensions)
         {
             SecurityParameters securityParameters = clientContext.SecurityParameters;
             if (null != securityParameters.PeerCertificate)
                 throw new TlsFatalAlert(AlertDescription.unexpected_message);
 
             Certificate.ParseOptions options = new Certificate.ParseOptions()
-                .SetMaxChainLength(client.GetMaxCertificateChainLength());
+            {
+                CertificateType = TlsExtensionsUtilities.GetServerCertificateTypeExtensionServer(serverExtensions,
+                    CertificateType.X509),
+                MaxChainLength = client.GetMaxCertificateChainLength(),
+            };
 
             Certificate serverCertificate = Certificate.Parse(options, clientContext, buf, null);
 
diff --git a/crypto/src/tls/crypto/TlsCipher.cs b/crypto/src/tls/crypto/TlsCipher.cs
index 4c2147bf7..53a8141fd 100644
--- a/crypto/src/tls/crypto/TlsCipher.cs
+++ b/crypto/src/tls/crypto/TlsCipher.cs
@@ -38,6 +38,11 @@ namespace Org.BouncyCastle.Tls.Crypto
         TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
             int headerAllocation, byte[] plaintext, int offset, int len);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext);
+#endif
+
         /// <summary>Decode the passed in ciphertext using the current bulk cipher.</summary>
         /// <param name="seqNo">sequence number of the message represented by ciphertext.</param>
         /// <param name="recordType">content type used in the record for this message.</param>
diff --git a/crypto/src/tls/crypto/TlsCrypto.cs b/crypto/src/tls/crypto/TlsCrypto.cs
index c9d00cbb0..f515a752b 100644
--- a/crypto/src/tls/crypto/TlsCrypto.cs
+++ b/crypto/src/tls/crypto/TlsCrypto.cs
@@ -112,6 +112,13 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <exception cref="IOException">if there is an issue on decoding or constructing the certificate.</exception>
         TlsCertificate CreateCertificate(byte[] encoding);
 
+        /// <summary>Create a TlsCertificate from an ASN.1 binary encoding of a certificate.</summary>
+        /// <param name="type">Certificate type as per IANA TLS Certificate Types registry.</param>
+        /// <param name="encoding">DER/BER encoding of the certificate of interest.</param>
+        /// <returns>a TlsCertificate.</returns>
+        /// <exception cref="IOException">if there is an issue on decoding or constructing the certificate.</exception>
+        TlsCertificate CreateCertificate(short type, byte[] encoding);
+
         /// <summary>Create a cipher for the specified encryption and MAC algorithms.</summary>
         /// <remarks>
         /// See enumeration classes <see cref="EncryptionAlgorithm"/>, <see cref="MacAlgorithm"/> for appropriate
@@ -173,6 +180,10 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <returns>a <see cref="TlsNonceGenerator"/>.</returns>
         TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        TlsNonceGenerator CreateNonceGenerator(ReadOnlySpan<byte> additionalSeedMaterial);
+#endif
+
         /// <summary>Create an SRP-6 client.</summary>
         /// <param name="srpConfig">client config.</param>
         /// <returns>an initialised SRP6 client object.</returns>
diff --git a/crypto/src/tls/crypto/TlsCryptoUtilities.cs b/crypto/src/tls/crypto/TlsCryptoUtilities.cs
index 98ac87a83..1903065f1 100644
--- a/crypto/src/tls/crypto/TlsCryptoUtilities.cs
+++ b/crypto/src/tls/crypto/TlsCryptoUtilities.cs
@@ -183,6 +183,9 @@ namespace Org.BouncyCastle.Tls.Crypto
         public static TlsSecret HkdfExpandLabel(TlsSecret secret, int cryptoHashAlgorithm, string label,
             byte[] context, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return HkdfExpandLabel(secret, cryptoHashAlgorithm, label.AsSpan(), context.AsSpan(), length);
+#else
             int labelLength = label.Length;
             if (labelLength < 1)
                 throw new TlsFatalAlert(AlertDescription.internal_error);
@@ -219,6 +222,53 @@ namespace Org.BouncyCastle.Tls.Crypto
             }
 
             return secret.HkdfExpand(cryptoHashAlgorithm, hkdfLabel, length);
+#endif
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public static TlsSecret HkdfExpandLabel(TlsSecret secret, int cryptoHashAlgorithm, ReadOnlySpan<char> label,
+            ReadOnlySpan<byte> context, int length)
+        {
+            int labelLength = label.Length;
+            if (labelLength < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            int contextLength = context.Length;
+            int expandedLabelLength = Tls13Prefix.Length + labelLength;
+
+            int hkdfLabelLength = 2 + (1 + expandedLabelLength) + (1 + contextLength);
+            Span<byte> hkdfLabel = hkdfLabelLength <= 512
+                ? stackalloc byte[hkdfLabelLength]
+                : new byte[hkdfLabelLength];
+
+            // uint16 length
+            {
+                TlsUtilities.CheckUint16(length);
+                TlsUtilities.WriteUint16(length, hkdfLabel);
+            }
+
+            // opaque label<7..255>
+            {
+                TlsUtilities.CheckUint8(expandedLabelLength);
+                TlsUtilities.WriteUint8(expandedLabelLength, hkdfLabel[2..]);
+
+                Tls13Prefix.CopyTo(hkdfLabel[3..]);
+
+                int labelPos = 2 + (1 + Tls13Prefix.Length);
+                for (int i = 0; i < labelLength; ++i)
+                {
+                    hkdfLabel[labelPos + i] = (byte)label[i];
+                }
+            }
+
+            // context
+            {
+                TlsUtilities.WriteOpaque8(context, hkdfLabel.Slice(2 + (1 + expandedLabelLength)));
+            }
+
+            return secret.HkdfExpand(cryptoHashAlgorithm, hkdfLabel, length);
+        }
+#endif
     }
 }
diff --git a/crypto/src/tls/crypto/TlsHash.cs b/crypto/src/tls/crypto/TlsHash.cs
index 4732fc280..6fbaeceb9 100644
--- a/crypto/src/tls/crypto/TlsHash.cs
+++ b/crypto/src/tls/crypto/TlsHash.cs
@@ -11,6 +11,10 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <param name="length">the length of the input data.</param>
         void Update(byte[] input, int inOff, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void Update(ReadOnlySpan<byte> input);
+#endif
+
         /// <summary>Return calculated hash for any input passed in.</summary>
         /// <returns>the hash value.</returns>
         byte[] CalculateHash();
diff --git a/crypto/src/tls/crypto/TlsHashSink.cs b/crypto/src/tls/crypto/TlsHashSink.cs
index a1681b0c8..3401eb60e 100644
--- a/crypto/src/tls/crypto/TlsHashSink.cs
+++ b/crypto/src/tls/crypto/TlsHashSink.cs
@@ -29,6 +29,16 @@ namespace Org.BouncyCastle.Tls.Crypto
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (!buffer.IsEmpty)
+            {
+                m_hash.Update(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             m_hash.Update(new byte[]{ value }, 0, 1);
diff --git a/crypto/src/tls/crypto/TlsMac.cs b/crypto/src/tls/crypto/TlsMac.cs
index f92d946f1..511e29d10 100644
--- a/crypto/src/tls/crypto/TlsMac.cs
+++ b/crypto/src/tls/crypto/TlsMac.cs
@@ -11,12 +11,20 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <param name="keyLen">length of the key in the array.</param>
         void SetKey(byte[] key, int keyOff, int keyLen);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void SetKey(ReadOnlySpan<byte> key);
+#endif
+
         /// <summary>Update the MAC with the passed in input.</summary>
         /// <param name="input">input array containing the data.</param>
         /// <param name="inOff">offset into the input array the input starts at.</param>
         /// <param name="length">the length of the input data.</param>
         void Update(byte[] input, int inOff, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void Update(ReadOnlySpan<byte> input);
+#endif
+
         /// <summary>Return calculated MAC for any input passed in.</summary>
         /// <returns>the MAC value.</returns>
         byte[] CalculateMac();
diff --git a/crypto/src/tls/crypto/TlsMacSink.cs b/crypto/src/tls/crypto/TlsMacSink.cs
index e7d5c70d7..fbb2e5893 100644
--- a/crypto/src/tls/crypto/TlsMacSink.cs
+++ b/crypto/src/tls/crypto/TlsMacSink.cs
@@ -29,6 +29,16 @@ namespace Org.BouncyCastle.Tls.Crypto
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (!buffer.IsEmpty)
+            {
+                m_mac.Update(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             m_mac.Update(new byte[]{ value }, 0, 1);
diff --git a/crypto/src/tls/crypto/TlsNullNullCipher.cs b/crypto/src/tls/crypto/TlsNullNullCipher.cs
index 082dff358..13fe092f7 100644
--- a/crypto/src/tls/crypto/TlsNullNullCipher.cs
+++ b/crypto/src/tls/crypto/TlsNullNullCipher.cs
@@ -31,6 +31,16 @@ namespace Org.BouncyCastle.Tls.Crypto
             return new TlsEncodeResult(result, 0, result.Length, contentType);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            byte[] result = new byte[headerAllocation + plaintext.Length];
+            plaintext.CopyTo(result.AsSpan(headerAllocation));
+            return new TlsEncodeResult(result, 0, result.Length, contentType);
+        }
+#endif
+
         public TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int offset, int len)
         {
diff --git a/crypto/src/tls/crypto/TlsSecret.cs b/crypto/src/tls/crypto/TlsSecret.cs
index 8aea34bcf..a404b9901 100644
--- a/crypto/src/tls/crypto/TlsSecret.cs
+++ b/crypto/src/tls/crypto/TlsSecret.cs
@@ -23,6 +23,10 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <returns>the new secret.</returns>
         TlsSecret DeriveUsingPrf(int prfAlgorithm, string label, byte[] seed, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        TlsSecret DeriveUsingPrf(int prfAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<byte> seed, int length);
+#endif
+
         /// <summary>Destroy the internal state of the secret.</summary>
         /// <remarks>
         /// After this call, any attempt to use the <see cref="TlsSecret"/> will result in an
@@ -44,6 +48,10 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <returns>the secret's internal data.</returns>
         byte[] Extract();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void ExtractTo(Span<byte> output);
+#endif
+
         /// <summary>RFC 5869 HKDF-Expand function, with this secret's data as the pseudo-random key ('prk').</summary>
         /// <param name="cryptoHashAlgorithm">the hash algorithm to instantiate HMAC with. See
         /// <see cref="CryptoHashAlgorithm"/> for values.</param>
@@ -52,6 +60,10 @@ namespace Org.BouncyCastle.Tls.Crypto
         /// <returns> output keying material (of 'length' octets).</returns>
         TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        TlsSecret HkdfExpand(int cryptoHashAlgorithm, ReadOnlySpan<byte> info, int length);
+#endif
+
         /// <summary>RFC 5869 HKDF-Extract function, with this secret's data as the 'salt'.</summary>
         /// <remarks>
         /// The <see cref="TlsSecret"/> does not keep a copy of the data. After this call, any attempt to use
@@ -64,5 +76,7 @@ namespace Org.BouncyCastle.Tls.Crypto
         TlsSecret HkdfExtract(int cryptoHashAlgorithm, TlsSecret ikm);
 
         bool IsAlive();
+
+        int Length { get; }
     }
 }
diff --git a/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs b/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs
index b2e1e7fe0..a8fb26697 100644
--- a/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs
+++ b/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs
@@ -48,7 +48,12 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
 
         public abstract SecureRandom SecureRandom { get; }
 
-        public abstract TlsCertificate CreateCertificate(byte[] encoding);
+        public virtual TlsCertificate CreateCertificate(byte[] encoding)
+        {
+            return CreateCertificate(CertificateType.X509, encoding);
+        }
+
+        public abstract TlsCertificate CreateCertificate(short type, byte[] encoding);
 
         public abstract TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm, int macAlgorithm);
 
@@ -77,6 +82,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
 
         public abstract TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract TlsNonceGenerator CreateNonceGenerator(ReadOnlySpan<byte> additionalSeedMaterial);
+#endif
+
         public abstract TlsSrp6Client CreateSrp6Client(TlsSrpConfig srpConfig);
 
         public abstract TlsSrp6Server CreateSrp6Server(TlsSrpConfig srpConfig, BigInteger srpVerifier);
diff --git a/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs b/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs
index cc07f978f..2a7ffe116 100644
--- a/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs
+++ b/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs
@@ -20,7 +20,7 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         /// <param name="data">the byte[] making up the secret value.</param>
         protected AbstractTlsSecret(byte[] data)
         {
-            this.m_data = data;
+            m_data = data;
         }
 
         protected virtual void CheckAlive()
@@ -46,6 +46,11 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
 
         public abstract TlsSecret DeriveUsingPrf(int prfAlgorithm, string label, byte[] seed, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract TlsSecret DeriveUsingPrf(int prfAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<byte> seed,
+            int length);
+#endif
+
         public virtual void Destroy()
         {
             lock (this)
@@ -54,7 +59,7 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
                 {
                     // TODO Is there a way to ensure the data is really overwritten?
                     Array.Clear(m_data, 0, m_data.Length);
-                    this.m_data = null;
+                    m_data = null;
                 }
             }
         }
@@ -77,13 +82,30 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
                 CheckAlive();
 
                 byte[] result = m_data;
-                this.m_data = null;
+                m_data = null;
                 return result;
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void ExtractTo(Span<byte> output)
+        {
+            lock (this)
+            {
+                CheckAlive();
+
+                m_data.CopyTo(output);
+                m_data = null;
+            }
+        }
+#endif
+
         public abstract TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract TlsSecret HkdfExpand(int cryptoHashAlgorithm, ReadOnlySpan<byte> info, int length);
+#endif
+
         public abstract TlsSecret HkdfExtract(int cryptoHashAlgorithm, TlsSecret ikm);
 
         public virtual bool IsAlive()
@@ -94,6 +116,19 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             }
         }
 
+        public virtual int Length
+        {
+            get
+            {
+                lock (this)
+                {
+                    CheckAlive();
+
+                    return m_data.Length;
+                }
+            }
+        }
+
         internal virtual byte[] CopyData()
         {
             lock (this)
diff --git a/crypto/src/tls/crypto/impl/TlsAeadCipher.cs b/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
index 04f9ce80f..a53e1e835 100644
--- a/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
+++ b/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
@@ -72,6 +72,33 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             }
 
             int keyBlockSize = (2 * keySize) + (2 * m_fixed_iv_length);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> keyBlock = keyBlockSize <= 512
+                ? stackalloc byte[keyBlockSize]
+                : new byte[keyBlockSize];
+            TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlock);
+
+            if (isServer)
+            {
+                decryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
+                encryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
+
+                keyBlock[..m_fixed_iv_length].CopyTo(m_decryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
+                keyBlock[..m_fixed_iv_length].CopyTo(m_encryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
+            }
+            else
+            {
+                encryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
+                decryptCipher.SetKey(keyBlock[..keySize]); keyBlock = keyBlock[keySize..];
+
+                keyBlock[..m_fixed_iv_length].CopyTo(m_encryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
+                keyBlock[..m_fixed_iv_length].CopyTo(m_decryptNonce); keyBlock = keyBlock[m_fixed_iv_length..];
+            }
+
+            if (!keyBlock.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+#else
             byte[] keyBlock = TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlockSize);
             int pos = 0;
 
@@ -92,8 +119,9 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
                 Array.Copy(keyBlock, pos, m_decryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
             }
 
-            if (keyBlockSize != pos)
+            if (pos != keyBlockSize)
                 throw new TlsFatalAlert(AlertDescription.internal_error);
+#endif
 
             int nonceLength = m_fixed_iv_length + m_record_iv_length;
 
@@ -133,6 +161,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
             int headerAllocation, byte[] plaintext, int plaintextOffset, int plaintextLength)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodePlaintext(seqNo, contentType, recordVersion, headerAllocation,
+                plaintext.AsSpan(plaintextOffset, plaintextLength));
+#else
             byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
 
             switch (m_nonceMode)
@@ -201,7 +233,83 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             }
 
             return new TlsEncodeResult(output, 0, output.Length, recordType);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
+
+            switch (m_nonceMode)
+            {
+            case NONCE_RFC5288:
+                Array.Copy(m_encryptNonce, 0, nonce, 0, m_encryptNonce.Length);
+                // RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number.
+                TlsUtilities.WriteUint64(seqNo, nonce, m_encryptNonce.Length);
+                break;
+            case NONCE_RFC7905:
+                TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
+                for (int i = 0; i < m_encryptNonce.Length; ++i)
+                {
+                    nonce[i] ^= m_encryptNonce[i];
+                }
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int extraLength = m_isTlsV13 ? 1 : 0;
+
+            // TODO[tls13] If we support adding padding to TLSInnerPlaintext, this will need review
+            int encryptionLength = m_encryptCipher.GetOutputSize(plaintext.Length + extraLength);
+            int ciphertextLength = m_record_iv_length + encryptionLength;
+
+            byte[] output = new byte[headerAllocation + ciphertextLength];
+            int outputPos = headerAllocation;
+
+            if (m_record_iv_length != 0)
+            {
+                Array.Copy(nonce, nonce.Length - m_record_iv_length, output, outputPos, m_record_iv_length);
+                outputPos += m_record_iv_length;
+            }
+
+            short recordType = m_isTlsV13 ? ContentType.application_data : contentType;
+
+            byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
+                plaintext.Length);
+
+            try
+            {
+                plaintext.CopyTo(output.AsSpan(outputPos));
+                if (m_isTlsV13)
+                {
+                    output[outputPos + plaintext.Length] = (byte)contentType;
+                }
+
+                m_encryptCipher.Init(nonce, m_macSize, additionalData);
+                outputPos += m_encryptCipher.DoFinal(output, outputPos, plaintext.Length + extraLength, output,
+                    outputPos);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+
+            if (outputPos != output.Length)
+            {
+                // NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            return new TlsEncodeResult(output, 0, output.Length, recordType);
         }
+#endif
 
         public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int ciphertextOffset, int ciphertextLength)
diff --git a/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs b/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs
index 44e6fda84..4c69c0b72 100644
--- a/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs
+++ b/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs
@@ -13,6 +13,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         /// <exception cref="IOException"/>
         void SetKey(byte[] key, int keyOff, int keyLen);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void SetKey(ReadOnlySpan<byte> key);
+#endif
+
         /// <summary>Initialise the parameters for the AEAD operator.</summary>
         /// <param name="nonce">the nonce.</param>
         /// <param name="macSize">MAC size in bytes.</param>
diff --git a/crypto/src/tls/crypto/impl/TlsBlockCipher.cs b/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
index 64cfc752a..64a73bfea 100644
--- a/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
+++ b/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
@@ -65,27 +65,55 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
                 serverCipher = decryptCipher;
             }
 
-            int key_block_size = (2 * cipherKeySize) + clientMac.MacLength + serverMac.MacLength;
+            int keyBlockSize = (2 * cipherKeySize) + clientMac.MacLength + serverMac.MacLength;
 
             // From TLS 1.1 onwards, block ciphers don't need IVs from the key_block
             if (!m_useExplicitIV)
             {
-                key_block_size += clientCipher.GetBlockSize() + serverCipher.GetBlockSize();
+                keyBlockSize += clientCipher.GetBlockSize() + serverCipher.GetBlockSize();
             }
 
-            byte[] key_block = TlsImplUtilities.CalculateKeyBlock(cryptoParams, key_block_size);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> keyBlock = keyBlockSize <= 512
+                ? stackalloc byte[keyBlockSize]
+                : new byte[keyBlockSize];
+            TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlock);
 
-            int offset = 0;
+            clientMac.SetKey(keyBlock[..clientMac.MacLength]); keyBlock = keyBlock[clientMac.MacLength..];
+            serverMac.SetKey(keyBlock[..serverMac.MacLength]); keyBlock = keyBlock[serverMac.MacLength..];
 
-            clientMac.SetKey(key_block, offset, clientMac.MacLength);
-            offset += clientMac.MacLength;
-            serverMac.SetKey(key_block, offset, serverMac.MacLength);
-            offset += serverMac.MacLength;
+            clientCipher.SetKey(keyBlock[..cipherKeySize]); keyBlock = keyBlock[cipherKeySize..];
+            serverCipher.SetKey(keyBlock[..cipherKeySize]); keyBlock = keyBlock[cipherKeySize..];
 
-            clientCipher.SetKey(key_block, offset, cipherKeySize);
-            offset += cipherKeySize;
-            serverCipher.SetKey(key_block, offset, cipherKeySize);
-            offset += cipherKeySize;
+            int clientIVLength = clientCipher.GetBlockSize();
+            int serverIVLength = serverCipher.GetBlockSize();
+
+            if (m_useExplicitIV)
+            {
+                clientCipher.Init(clientIVLength <= 64 ? stackalloc byte[clientIVLength] : new byte[clientIVLength]);
+                serverCipher.Init(serverIVLength <= 64 ? stackalloc byte[serverIVLength] : new byte[serverIVLength]);
+            }
+            else
+            {
+                clientCipher.Init(keyBlock[..clientIVLength]); keyBlock = keyBlock[clientIVLength..];
+                serverCipher.Init(keyBlock[..serverIVLength]); keyBlock = keyBlock[serverIVLength..];
+            }
+
+            if (!keyBlock.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+#else
+            byte[] keyBlock = TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlockSize);
+            int pos = 0;
+
+            clientMac.SetKey(keyBlock, pos, clientMac.MacLength);
+            pos += clientMac.MacLength;
+            serverMac.SetKey(keyBlock, pos, serverMac.MacLength);
+            pos += serverMac.MacLength;
+
+            clientCipher.SetKey(keyBlock, pos, cipherKeySize);
+            pos += cipherKeySize;
+            serverCipher.SetKey(keyBlock, pos, cipherKeySize);
+            pos += cipherKeySize;
 
             int clientIVLength = clientCipher.GetBlockSize();
             int serverIVLength = serverCipher.GetBlockSize();
@@ -97,14 +125,15 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             }
             else
             {
-                clientCipher.Init(key_block, offset, clientIVLength);
-                offset += clientIVLength;
-                serverCipher.Init(key_block, offset, serverIVLength);
-                offset += serverIVLength;
+                clientCipher.Init(keyBlock, pos, clientIVLength);
+                pos += clientIVLength;
+                serverCipher.Init(keyBlock, pos, serverIVLength);
+                pos += serverIVLength;
             }
 
-            if (offset != key_block_size)
+            if (pos != keyBlockSize)
                 throw new TlsFatalAlert(AlertDescription.internal_error);
+#endif
 
             if (cryptoParams.IsServer)
             {
@@ -170,6 +199,9 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
             int headerAllocation, byte[] plaintext, int offset, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodePlaintext(seqNo, contentType, recordVersion, headerAllocation, plaintext.AsSpan(offset, len));
+#else
             int blockSize = m_encryptCipher.GetBlockSize();
             int macSize = m_writeMac.Size;
 
@@ -235,7 +267,80 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
                 throw new TlsFatalAlert(AlertDescription.internal_error);
 
             return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            int blockSize = m_encryptCipher.GetBlockSize();
+            int macSize = m_writeMac.Size;
+
+            int enc_input_length = plaintext.Length;
+            if (!m_encryptThenMac)
+            {
+                enc_input_length += macSize;
+            }
+
+            int padding_length = blockSize - (enc_input_length % blockSize);
+            if (m_useExtraPadding)
+            {
+                // Add a random number of extra blocks worth of padding
+                int maxExtraPadBlocks = (256 - padding_length) / blockSize;
+                int actualExtraPadBlocks = ChooseExtraPadBlocks(maxExtraPadBlocks);
+                padding_length += actualExtraPadBlocks * blockSize;
+            }
+
+            int totalSize = plaintext.Length + macSize + padding_length;
+            if (m_useExplicitIV)
+            {
+                totalSize += blockSize;
+            }
+
+            byte[] outBuf = new byte[headerAllocation + totalSize];
+            int outOff = headerAllocation;
+
+            if (m_useExplicitIV)
+            {
+                // Technically the explicit IV will be the encryption of this nonce
+                byte[] explicitIV = m_cryptoParams.NonceGenerator.GenerateNonce(blockSize);
+                Array.Copy(explicitIV, 0, outBuf, outOff, blockSize);
+                outOff += blockSize;
+            }
+
+            plaintext.CopyTo(outBuf.AsSpan(outOff));
+            outOff += plaintext.Length;
+
+            if (!m_encryptThenMac)
+            {
+                byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext);
+                mac.CopyTo(outBuf.AsSpan(outOff));
+                outOff += mac.Length;
+            }
+
+            byte padByte = (byte)(padding_length - 1);
+            for (int i = 0; i < padding_length; ++i)
+            {
+                outBuf[outOff++] = padByte;
+            }
+
+            m_encryptCipher.DoFinal(outBuf, headerAllocation, outOff - headerAllocation, outBuf, headerAllocation);
+
+            if (m_encryptThenMac)
+            {
+                byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, outBuf, headerAllocation,
+                    outOff - headerAllocation);
+                Array.Copy(mac, 0, outBuf, outOff, mac.Length);
+                outOff += mac.Length;
+            }
+
+            if (outOff != outBuf.Length)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
         }
+#endif
 
         public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int offset, int len)
diff --git a/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs b/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs
index 7df2ed70f..41fd9b9bf 100644
--- a/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs
+++ b/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs
@@ -13,6 +13,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         /// <exception cref="IOException"/>
         void SetKey(byte[] key, int keyOff, int keyLen);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void SetKey(ReadOnlySpan<byte> key);
+#endif
+
         /// <summary>Initialise the parameters for operator.</summary>
         /// <param name="iv">array holding the initialization vector (IV).</param>
         /// <param name="ivOff">offset into the array the IV starts at.</param>
@@ -20,6 +24,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         /// <exception cref="IOException">if the parameters are inappropriate.</exception>
         void Init(byte[] iv, int ivOff, int ivLen);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        void Init(ReadOnlySpan<byte> iv);
+#endif
+
         /// <summary>Perform the cipher encryption/decryption returning the output in output.</summary>
         /// <remarks>
         /// Note: we have to use DoFinal() here as it is the only way to guarantee output from the underlying cipher.
diff --git a/crypto/src/tls/crypto/impl/TlsImplUtilities.cs b/crypto/src/tls/crypto/impl/TlsImplUtilities.cs
index dc5a96288..8f7b75cc6 100644
--- a/crypto/src/tls/crypto/impl/TlsImplUtilities.cs
+++ b/crypto/src/tls/crypto/impl/TlsImplUtilities.cs
@@ -60,5 +60,23 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             byte[] seed = Arrays.Concatenate(securityParameters.ServerRandom, securityParameters.ClientRandom);
             return master_secret.DeriveUsingPrf(prfAlgorithm, ExporterLabel.key_expansion, seed, length).Extract();
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CalculateKeyBlock(TlsCryptoParameters cryptoParams, Span<byte> keyBlock)
+        {
+            SecurityParameters securityParameters = cryptoParams.SecurityParameters;
+            TlsSecret master_secret = securityParameters.MasterSecret;
+            int prfAlgorithm = securityParameters.PrfAlgorithm;
+
+            Span<byte> cr = securityParameters.ClientRandom, sr = securityParameters.ServerRandom;
+            Span<byte> seed = stackalloc byte[sr.Length + cr.Length];
+            sr.CopyTo(seed);
+            cr.CopyTo(seed[sr.Length..]);
+
+            TlsSecret derived = master_secret.DeriveUsingPrf(prfAlgorithm, ExporterLabel.key_expansion, seed,
+                keyBlock.Length);
+            derived.ExtractTo(keyBlock);
+        }
+#endif
     }
 }
diff --git a/crypto/src/tls/crypto/impl/TlsNullCipher.cs b/crypto/src/tls/crypto/impl/TlsNullCipher.cs
index 3ca4951a6..9bb08110a 100644
--- a/crypto/src/tls/crypto/impl/TlsNullCipher.cs
+++ b/crypto/src/tls/crypto/impl/TlsNullCipher.cs
@@ -16,20 +16,33 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             if (TlsImplUtilities.IsTlsV13(cryptoParams))
                 throw new TlsFatalAlert(AlertDescription.internal_error);
 
-            this.m_cryptoParams = cryptoParams;
+            m_cryptoParams = cryptoParams;
 
-            int key_block_size = clientMac.MacLength + serverMac.MacLength;
-            byte[] key_block = TlsImplUtilities.CalculateKeyBlock(cryptoParams, key_block_size);
+            int keyBlockSize = clientMac.MacLength + serverMac.MacLength;
 
-            int offset = 0;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> keyBlock = keyBlockSize <= 512
+                ? stackalloc byte[keyBlockSize]
+                : new byte[keyBlockSize];
+            TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlock);
 
-            clientMac.SetKey(key_block, offset, clientMac.MacLength);
-            offset += clientMac.MacLength;
-            serverMac.SetKey(key_block, offset, serverMac.MacLength);
-            offset += serverMac.MacLength;
+            clientMac.SetKey(keyBlock[..clientMac.MacLength]); keyBlock = keyBlock[clientMac.MacLength..];
+            serverMac.SetKey(keyBlock[..serverMac.MacLength]); keyBlock = keyBlock[serverMac.MacLength..];
 
-            if (offset != key_block_size)
+            if (!keyBlock.IsEmpty)
                 throw new TlsFatalAlert(AlertDescription.internal_error);
+#else
+            byte[] keyBlock = TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlockSize);
+            int pos = 0;
+
+            clientMac.SetKey(keyBlock, pos, clientMac.MacLength);
+            pos += clientMac.MacLength;
+            serverMac.SetKey(keyBlock, pos, serverMac.MacLength);
+            pos += serverMac.MacLength;
+
+            if (pos != keyBlockSize)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+#endif
 
             if (cryptoParams.IsServer)
             {
@@ -68,6 +81,18 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             return new TlsEncodeResult(ciphertext, 0, ciphertext.Length, contentType);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, ReadOnlySpan<byte> plaintext)
+        {
+            byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext);
+            byte[] ciphertext = new byte[headerAllocation + plaintext.Length + mac.Length];
+            plaintext.CopyTo(ciphertext.AsSpan(headerAllocation));
+            mac.CopyTo(ciphertext.AsSpan(headerAllocation + plaintext.Length));
+            return new TlsEncodeResult(ciphertext, 0, ciphertext.Length, contentType);
+        }
+#endif
+
         public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
             byte[] ciphertext, int offset, int len)
         {
diff --git a/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs b/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
index 9f43f4382..b4edde760 100644
--- a/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
+++ b/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
@@ -55,6 +55,9 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
 
         public virtual byte[] CalculateMac(long seqNo, short type, byte[] msg, int msgOff, int msgLen)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return CalculateMac(seqNo, type, msg.AsSpan(msgOff, msgLen));
+#else
             ProtocolVersion serverVersion = m_cryptoParams.ServerVersion;
             bool isSsl = serverVersion.IsSsl;
 
@@ -71,8 +74,31 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
             m_mac.Update(msg, msgOff, msgLen);
 
             return Truncate(m_mac.CalculateMac());
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual byte[] CalculateMac(long seqNo, short type, ReadOnlySpan<byte> message)
+        {
+            ProtocolVersion serverVersion = m_cryptoParams.ServerVersion;
+            bool isSsl = serverVersion.IsSsl;
+
+            byte[] macHeader = new byte[isSsl ? 11 : 13];
+            TlsUtilities.WriteUint64(seqNo, macHeader, 0);
+            TlsUtilities.WriteUint8(type, macHeader, 8);
+            if (!isSsl)
+            {
+                TlsUtilities.WriteVersion(serverVersion, macHeader, 9);
+            }
+            TlsUtilities.WriteUint16(message.Length, macHeader, macHeader.Length - 2);
+
+            m_mac.Update(macHeader);
+            m_mac.Update(message);
+
+            return Truncate(m_mac.CalculateMac());
+        }
+#endif
+
         public virtual byte[] CalculateMacConstantTime(long seqNo, short type, byte[] msg, int msgOff, int msgLen,
             int fullLength, byte[] dummyData)
         {
diff --git a/crypto/src/tls/crypto/impl/TlsSuiteMac.cs b/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
index 6e4942928..1a28eba81 100644
--- a/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
+++ b/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
@@ -18,6 +18,10 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl
         /// <returns>A new byte array containing the MAC value.</returns>
         byte[] CalculateMac(long seqNo, short type, byte[] message, int offset, int length);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        byte[] CalculateMac(long seqNo, short type, ReadOnlySpan<byte> message);
+#endif
+
         /// <summary>Constant time calculation of the MAC for some given data with a given expected length.</summary>
         /// <param name="seqNo">The sequence number of the record.</param>
         /// <param name="type">The content type of the message.</param>
diff --git a/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs b/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs
index ab78d0ce2..f8e36a245 100644
--- a/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs
@@ -102,12 +102,27 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             m_cipher.Init(m_isEncrypting, new ParametersWithIV(cipherKey, Zeroes, 0, 12));
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void SetKey(ReadOnlySpan<byte> key)
+        {
+            KeyParameter cipherKey = new KeyParameter(key);
+            m_cipher.Init(m_isEncrypting, new ParametersWithIV(cipherKey, Zeroes.AsSpan(0, 12)));
+        }
+#endif
+
         private void InitMac()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> firstBlock = stackalloc byte[64];
+            m_cipher.ProcessBytes(firstBlock, firstBlock);
+            m_mac.Init(new KeyParameter(firstBlock[..32]));
+            firstBlock.Fill(0x00);
+#else
             byte[] firstBlock = new byte[64];
             m_cipher.ProcessBytes(firstBlock, 0, 64, firstBlock, 0);
             m_mac.Init(new KeyParameter(firstBlock, 0, 32));
             Array.Clear(firstBlock, 0, firstBlock.Length);
+#endif
         }
 
         private void UpdateMac(byte[] buf, int off, int len)
diff --git a/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs b/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs
index df2ccd2c1..a0378e334 100644
--- a/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
@@ -51,11 +52,27 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             Reset();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void SetKey(ReadOnlySpan<byte> key)
+        {
+            this.m_secret = key.ToArray();
+
+            Reset();
+        }
+#endif
+
         public virtual void Update(byte[] input, int inOff, int len)
         {
             m_digest.BlockUpdate(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            m_digest.BlockUpdate(input);
+        }
+#endif
+
         public virtual byte[] CalculateMac()
         {
             byte[] result = new byte[m_digest.GetDigestSize()];
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs b/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs
index ae05a1664..0b2781326 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs
@@ -10,11 +10,11 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
         : TlsAeadCipherImpl
     {
         private readonly bool m_isEncrypting;
-        private readonly IAeadBlockCipher m_cipher;
+        private readonly IAeadCipher m_cipher;
 
         private KeyParameter key;
 
-        internal BcTlsAeadCipherImpl(IAeadBlockCipher cipher, bool isEncrypting)
+        internal BcTlsAeadCipherImpl(IAeadCipher cipher, bool isEncrypting)
         {
             this.m_cipher = cipher;
             this.m_isEncrypting = isEncrypting;
@@ -25,6 +25,13 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             this.key = new KeyParameter(key, keyOff, keyLen);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void SetKey(ReadOnlySpan<byte> key)
+        {
+            this.key = new KeyParameter(key);
+        }
+#endif
+
         public void Init(byte[] nonce, int macSize, byte[] additionalData)
         {
             m_cipher.Init(m_isEncrypting, new AeadParameters(key, macSize * 8, nonce, additionalData));
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs b/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs
index b51d38c0d..b7421dfb9 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs
@@ -24,11 +24,25 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             this.key = new KeyParameter(key, keyOff, keyLen);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void SetKey(ReadOnlySpan<byte> key)
+        {
+            this.key = new KeyParameter(key);
+        }
+#endif
+
         public void Init(byte[] iv, int ivOff, int ivLen)
         {
             m_cipher.Init(m_isEncrypting, new ParametersWithIV(key, iv, ivOff, ivLen));
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Init(ReadOnlySpan<byte> iv)
+        {
+            m_cipher.Init(m_isEncrypting, new ParametersWithIV(key, iv));
+        }
+#endif
+
         public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
         {
             int blockSize = m_cipher.GetBlockSize();
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs b/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs
index 7e946ce23..f64d8332d 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs
@@ -3,19 +3,14 @@ using System.IO;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Engines;
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Crypto.Signers;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
 {
     /// <summary>Implementation class for a single X.509 certificate based on the BC light-weight API.</summary>
     public class BcTlsCertificate
-        : TlsCertificate
+        : BcTlsRawKeyCertificate
     {
         /// <exception cref="IOException"/>
         public static BcTlsCertificate Convert(BcTlsCrypto crypto, TlsCertificate certificate)
@@ -40,15 +35,8 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             }
         }
 
-        protected readonly BcTlsCrypto m_crypto;
         protected readonly X509CertificateStructure m_certificate;
 
-        protected DHPublicKeyParameters m_pubKeyDH = null;
-        protected ECPublicKeyParameters m_pubKeyEC = null;
-        protected Ed25519PublicKeyParameters m_pubKeyEd25519 = null;
-        protected Ed448PublicKeyParameters m_pubKeyEd448 = null;
-        protected RsaKeyParameters m_pubKeyRsa = null;
-
         /// <exception cref="IOException"/>
         public BcTlsCertificate(BcTlsCrypto crypto, byte[] encoding)
             : this(crypto, ParseCertificate(encoding))
@@ -56,204 +44,21 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
         }
 
         public BcTlsCertificate(BcTlsCrypto crypto, X509CertificateStructure certificate)
+            : base(crypto, certificate.SubjectPublicKeyInfo)
         {
-            this.m_crypto = crypto;
-            this.m_certificate = certificate;
+            m_certificate = certificate;
         }
 
-        /// <exception cref="IOException"/>
-        public virtual TlsEncryptor CreateEncryptor(int tlsCertificateRole)
-        {
-            ValidateKeyUsage(KeyUsage.KeyEncipherment);
-
-            switch (tlsCertificateRole)
-            {
-            case TlsCertificateRole.RsaEncryption:
-            {
-                this.m_pubKeyRsa = GetPubKeyRsa();
-                return new BcTlsRsaEncryptor(m_crypto, m_pubKeyRsa);
-            }
-            // TODO[gmssl]
-            //case TlsCertificateRole.Sm2Encryption:
-            //{
-            //    this.m_pubKeyEC = GetPubKeyEC();
-            //    return new BcTlsSM2Encryptor(m_crypto, m_pubKeyEC);
-            //}
-            }
-
-            throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-        }
+        public virtual X509CertificateStructure X509CertificateStructure => m_certificate;
 
         /// <exception cref="IOException"/>
-        public virtual TlsVerifier CreateVerifier(short signatureAlgorithm)
-        {
-            switch (signatureAlgorithm)
-            {
-            case SignatureAlgorithm.ed25519:
-            case SignatureAlgorithm.ed448:
-            {
-                int signatureScheme = SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm);
-                Tls13Verifier tls13Verifier = CreateVerifier(signatureScheme);
-                return new LegacyTls13Verifier(signatureScheme, tls13Verifier);
-            }
-            }
-
-            ValidateKeyUsage(KeyUsage.DigitalSignature);
-
-            switch (signatureAlgorithm)
-            {
-            case SignatureAlgorithm.dsa:
-                return new BcTlsDsaVerifier(m_crypto, GetPubKeyDss());
-
-            case SignatureAlgorithm.ecdsa:
-                return new BcTlsECDsaVerifier(m_crypto, GetPubKeyEC());
-
-            case SignatureAlgorithm.rsa:
-            {
-                ValidateRsa_Pkcs1();
-                return new BcTlsRsaVerifier(m_crypto, GetPubKeyRsa());
-            }
-
-            case SignatureAlgorithm.rsa_pss_pss_sha256:
-            case SignatureAlgorithm.rsa_pss_pss_sha384:
-            case SignatureAlgorithm.rsa_pss_pss_sha512:
-            {
-                ValidateRsa_Pss_Pss(signatureAlgorithm);
-                int signatureScheme = SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm);
-                return new BcTlsRsaPssVerifier(m_crypto, GetPubKeyRsa(), signatureScheme);
-            }
-
-            case SignatureAlgorithm.rsa_pss_rsae_sha256:
-            case SignatureAlgorithm.rsa_pss_rsae_sha384:
-            case SignatureAlgorithm.rsa_pss_rsae_sha512:
-            {
-                ValidateRsa_Pss_Rsae();
-                int signatureScheme = SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm);
-                return new BcTlsRsaPssVerifier(m_crypto, GetPubKeyRsa(), signatureScheme);
-            }
-
-            default:
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual Tls13Verifier CreateVerifier(int signatureScheme)
-        {
-            ValidateKeyUsage(KeyUsage.DigitalSignature);
-
-            switch (signatureScheme)
-            {
-            case SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256:
-            case SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384:
-            case SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512:
-            case SignatureScheme.ecdsa_secp256r1_sha256:
-            case SignatureScheme.ecdsa_secp384r1_sha384:
-            case SignatureScheme.ecdsa_secp521r1_sha512:
-            case SignatureScheme.ecdsa_sha1:
-            {
-                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
-                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
-
-                ISigner verifier = new DsaDigestSigner(new ECDsaSigner(), digest);
-                verifier.Init(false, GetPubKeyEC());
-
-                return new BcTls13Verifier(verifier);
-            }
-
-            case SignatureScheme.ed25519:
-            {
-                Ed25519Signer verifier = new Ed25519Signer();
-                verifier.Init(false, GetPubKeyEd25519());
-
-                return new BcTls13Verifier(verifier);
-            }
-
-            case SignatureScheme.ed448:
-            {
-                Ed448Signer verifier = new Ed448Signer(TlsUtilities.EmptyBytes);
-                verifier.Init(false, GetPubKeyEd448());
-
-                return new BcTls13Verifier(verifier);
-            }
-
-            case SignatureScheme.rsa_pkcs1_sha1:
-            case SignatureScheme.rsa_pkcs1_sha256:
-            case SignatureScheme.rsa_pkcs1_sha384:
-            case SignatureScheme.rsa_pkcs1_sha512:
-            {
-                ValidateRsa_Pkcs1();
-
-                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
-                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
-
-                RsaDigestSigner verifier = new RsaDigestSigner(digest,
-                    TlsCryptoUtilities.GetOidForHash(cryptoHashAlgorithm));
-                verifier.Init(false, GetPubKeyRsa());
-
-                return new BcTls13Verifier(verifier);
-            }
-
-            case SignatureScheme.rsa_pss_pss_sha256:
-            case SignatureScheme.rsa_pss_pss_sha384:
-            case SignatureScheme.rsa_pss_pss_sha512:
-            {
-                ValidateRsa_Pss_Pss(SignatureScheme.GetSignatureAlgorithm(signatureScheme));
-
-                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
-                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
-
-                PssSigner verifier = new PssSigner(new RsaEngine(), digest, digest.GetDigestSize());
-                verifier.Init(false, GetPubKeyRsa());
-
-                return new BcTls13Verifier(verifier);
-            }
-
-            case SignatureScheme.rsa_pss_rsae_sha256:
-            case SignatureScheme.rsa_pss_rsae_sha384:
-            case SignatureScheme.rsa_pss_rsae_sha512:
-            {
-                ValidateRsa_Pss_Rsae();
-
-                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
-                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
-
-                PssSigner verifier = new PssSigner(new RsaEngine(), digest, digest.GetDigestSize());
-                verifier.Init(false, GetPubKeyRsa());
-
-                return new BcTls13Verifier(verifier);
-            }
-
-            // TODO[RFC 8998]
-            //case SignatureScheme.sm2sig_sm3:
-            //{
-            //    ParametersWithID parametersWithID = new ParametersWithID(GetPubKeyEC(),
-            //        Strings.ToByteArray("TLSv1.3+GM+Cipher+Suite"));
-    
-            //    SM2Signer verifier = new SM2Signer();
-            //    verifier.Init(false, parametersWithID);
-
-            //    return new BcTls13Verifier(verifier);
-            //}
-
-            default:
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-            }
-        }
-
-        public virtual X509CertificateStructure X509CertificateStructure
-        {
-            get { return m_certificate; }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual byte[] GetEncoded()
+        public override byte[] GetEncoded()
         {
             return m_certificate.GetEncoded(Asn1Encodable.Der);
         }
 
         /// <exception cref="IOException"/>
-        public virtual byte[] GetExtension(DerObjectIdentifier extensionOid)
+        public override byte[] GetExtension(DerObjectIdentifier extensionOid)
         {
             X509Extensions extensions = m_certificate.TbsCertificate.Extensions;
             if (extensions != null)
@@ -267,191 +72,13 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             return null;
         }
 
-        public virtual BigInteger SerialNumber
-        {
-            get { return m_certificate.SerialNumber.Value; }
-        }
-
-        public virtual string SigAlgOid
-        {
-            get { return m_certificate.SignatureAlgorithm.Algorithm.Id; }
-        }
-
-        public virtual Asn1Encodable GetSigAlgParams()
-        {
-            return m_certificate.SignatureAlgorithm.Parameters;
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual short GetLegacySignatureAlgorithm()
-        {
-            AsymmetricKeyParameter publicKey = GetPublicKey();
-            if (publicKey.IsPrivate)
-                throw new TlsFatalAlert(AlertDescription.internal_error);
-
-            if (!SupportsKeyUsage(KeyUsage.DigitalSignature))
-                return -1;
-
-            /*
-             * RFC 5246 7.4.6. Client Certificate
-             */
+        public override BigInteger SerialNumber => m_certificate.SerialNumber.Value;
 
-            /*
-             * RSA public key; the certificate MUST allow the key to be used for signing with the
-             * signature scheme and hash algorithm that will be employed in the certificate verify
-             * message.
-             */
-            if (publicKey is RsaKeyParameters)
-                return SignatureAlgorithm.rsa;
+        public override string SigAlgOid => m_certificate.SignatureAlgorithm.Algorithm.Id;
 
-            /*
-                * DSA public key; the certificate MUST allow the key to be used for signing with the
-                * hash algorithm that will be employed in the certificate verify message.
-                */
-            if (publicKey is DsaPublicKeyParameters)
-                return SignatureAlgorithm.dsa;
+        public override Asn1Encodable GetSigAlgParams() => m_certificate.SignatureAlgorithm.Parameters;
 
-            /*
-             * ECDSA-capable public key; the certificate MUST allow the key to be used for signing
-             * with the hash algorithm that will be employed in the certificate verify message; the
-             * public key MUST use a curve and point format supported by the server.
-             */
-            if (publicKey is ECPublicKeyParameters)
-            {
-                // TODO Check the curve and point format
-                return SignatureAlgorithm.ecdsa;
-            }
-
-            return -1;
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual DHPublicKeyParameters GetPubKeyDH()
-        {
-            try
-            {
-                return (DHPublicKeyParameters)GetPublicKey();
-            }
-            catch (InvalidCastException e)
-            {
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual DsaPublicKeyParameters GetPubKeyDss()
-        {
-            try
-            {
-                return (DsaPublicKeyParameters)GetPublicKey();
-            }
-            catch (InvalidCastException e)
-            {
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual ECPublicKeyParameters GetPubKeyEC()
-        {
-            try
-            {
-                return (ECPublicKeyParameters)GetPublicKey();
-            }
-            catch (InvalidCastException e)
-            {
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual Ed25519PublicKeyParameters GetPubKeyEd25519()
-        {
-            try
-            {
-                return (Ed25519PublicKeyParameters)GetPublicKey();
-            }
-            catch (InvalidCastException e)
-            {
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual Ed448PublicKeyParameters GetPubKeyEd448()
-        {
-            try
-            {
-                return (Ed448PublicKeyParameters)GetPublicKey();
-            }
-            catch (InvalidCastException e)
-            {
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual RsaKeyParameters GetPubKeyRsa()
-        {
-            try
-            {
-                return (RsaKeyParameters)GetPublicKey();
-            }
-            catch (InvalidCastException e)
-            {
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm)
-        {
-            return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.DigitalSignature);
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual bool SupportsSignatureAlgorithmCA(short signatureAlgorithm)
-        {
-            return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.KeyCertSign);
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual TlsCertificate CheckUsageInRole(int tlsCertificateRole)
-        {
-            switch (tlsCertificateRole)
-            {
-            case TlsCertificateRole.DH:
-            {
-                ValidateKeyUsage(KeyUsage.KeyAgreement);
-                this.m_pubKeyDH = GetPubKeyDH();
-                return this;
-            }
-            case TlsCertificateRole.ECDH:
-            {
-                ValidateKeyUsage(KeyUsage.KeyAgreement);
-                this.m_pubKeyEC = GetPubKeyEC();
-                return this;
-            }
-            }
-
-            throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-        }
-
-        /// <exception cref="IOException"/>
-        protected virtual AsymmetricKeyParameter GetPublicKey()
-        {
-            SubjectPublicKeyInfo keyInfo = m_certificate.SubjectPublicKeyInfo;
-            try
-            {
-                return PublicKeyFactory.CreateKey(keyInfo);
-            }
-            catch (Exception e)
-            {
-                throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e);
-            }
-        }
-
-        protected virtual bool SupportsKeyUsage(int keyUsageBits)
+        protected override bool SupportsKeyUsage(int keyUsageBits)
         {
             X509Extensions exts = m_certificate.TbsCertificate.Extensions;
             if (exts != null)
@@ -466,97 +93,5 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             }
             return true;
         }
-
-        protected virtual bool SupportsRsa_Pkcs1()
-        {
-            AlgorithmIdentifier pubKeyAlgID = m_certificate.SubjectPublicKeyInfo.AlgorithmID;
-            return RsaUtilities.SupportsPkcs1(pubKeyAlgID);
-        }
-
-        protected virtual bool SupportsRsa_Pss_Pss(short signatureAlgorithm)
-        {
-            AlgorithmIdentifier pubKeyAlgID = m_certificate.SubjectPublicKeyInfo.AlgorithmID;
-            return RsaUtilities.SupportsPss_Pss(signatureAlgorithm, pubKeyAlgID);
-        }
-
-        protected virtual bool SupportsRsa_Pss_Rsae()
-        {
-            AlgorithmIdentifier pubKeyAlgID = m_certificate.SubjectPublicKeyInfo.AlgorithmID;
-            return RsaUtilities.SupportsPss_Rsae(pubKeyAlgID);
-        }
-
-        /// <exception cref="IOException"/>
-        protected virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm, int keyUsage)
-        {
-            if (!SupportsKeyUsage(keyUsage))
-                return false;
-
-            AsymmetricKeyParameter publicKey = GetPublicKey();
-
-            switch (signatureAlgorithm)
-            {
-            case SignatureAlgorithm.rsa:
-                return SupportsRsa_Pkcs1()
-                    && publicKey is RsaKeyParameters;
-
-            case SignatureAlgorithm.dsa:
-                return publicKey is DsaPublicKeyParameters;
-
-            case SignatureAlgorithm.ecdsa:
-            case SignatureAlgorithm.ecdsa_brainpoolP256r1tls13_sha256:
-            case SignatureAlgorithm.ecdsa_brainpoolP384r1tls13_sha384:
-            case SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512:
-                return publicKey is ECPublicKeyParameters;
-
-            case SignatureAlgorithm.ed25519:
-                return publicKey is Ed25519PublicKeyParameters;
-
-            case SignatureAlgorithm.ed448:
-                return publicKey is Ed448PublicKeyParameters;
-
-            case SignatureAlgorithm.rsa_pss_rsae_sha256:
-            case SignatureAlgorithm.rsa_pss_rsae_sha384:
-            case SignatureAlgorithm.rsa_pss_rsae_sha512:
-                return SupportsRsa_Pss_Rsae()
-                    && publicKey is RsaKeyParameters;
-
-            case SignatureAlgorithm.rsa_pss_pss_sha256:
-            case SignatureAlgorithm.rsa_pss_pss_sha384:
-            case SignatureAlgorithm.rsa_pss_pss_sha512:
-                return SupportsRsa_Pss_Pss(signatureAlgorithm)
-                    && publicKey is RsaKeyParameters;
-
-            default:
-                return false;
-            }
-        }
-
-        /// <exception cref="IOException"/>
-        public virtual void ValidateKeyUsage(int keyUsageBits)
-        {
-            if (!SupportsKeyUsage(keyUsageBits))
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-        }
-
-        /// <exception cref="IOException"/>
-        protected virtual void ValidateRsa_Pkcs1()
-        {
-            if (!SupportsRsa_Pkcs1())
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-        }
-
-        /// <exception cref="IOException"/>
-        protected virtual void ValidateRsa_Pss_Pss(short signatureAlgorithm)
-        {
-            if (!SupportsRsa_Pss_Pss(signatureAlgorithm))
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-        }
-
-        /// <exception cref="IOException"/>
-        protected virtual void ValidateRsa_Pss_Rsae()
-        {
-            if (!SupportsRsa_Pss_Rsae())
-                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-        }
     }
 }
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs b/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs
index 0375950c3..8e193f187 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs
@@ -27,8 +27,16 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
     {
         private readonly SecureRandom m_entropySource;
 
+        public BcTlsCrypto()
+            : this(CryptoServicesRegistrar.GetSecureRandom())
+        {
+        }
+
         public BcTlsCrypto(SecureRandom entropySource)
         {
+            if (entropySource == null)
+                throw new ArgumentNullException(nameof(entropySource));
+
             this.m_entropySource = entropySource;
         }
 
@@ -42,9 +50,17 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             get { return m_entropySource; }
         }
 
-        public override TlsCertificate CreateCertificate(byte[] encoding)
+        public override TlsCertificate CreateCertificate(short type, byte[] encoding)
         {
-            return new BcTlsCertificate(this, encoding);
+            switch (type)
+            {
+            case CertificateType.X509:
+                return new BcTlsCertificate(this, encoding);
+            case CertificateType.RawPublicKey:
+                return new BcTlsRawKeyCertificate(this, encoding);
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
         }
 
         public override TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm,
@@ -140,10 +156,34 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
 
         public override TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return CreateNonceGenerator(Spans.FromNullableReadOnly(additionalSeedMaterial));
+#else
+            int cryptoHashAlgorithm = CryptoHashAlgorithm.sha256;
+            IDigest digest = CreateDigest(cryptoHashAlgorithm);
+
+            int seedLength = TlsCryptoUtilities.GetHashOutputSize(cryptoHashAlgorithm);
+            byte[] seed = new byte[seedLength];
+            SecureRandom.NextBytes(seed);
+
+            DigestRandomGenerator randomGenerator = new DigestRandomGenerator(digest);
+            randomGenerator.AddSeedMaterial(additionalSeedMaterial);
+            randomGenerator.AddSeedMaterial(seed);
+
+            return new BcTlsNonceGenerator(randomGenerator);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override TlsNonceGenerator CreateNonceGenerator(ReadOnlySpan<byte> additionalSeedMaterial)
+        {
             int cryptoHashAlgorithm = CryptoHashAlgorithm.sha256;
             IDigest digest = CreateDigest(cryptoHashAlgorithm);
 
-            byte[] seed = new byte[TlsCryptoUtilities.GetHashOutputSize(cryptoHashAlgorithm)];
+            int seedLength = TlsCryptoUtilities.GetHashOutputSize(cryptoHashAlgorithm);
+            Span<byte> seed = seedLength <= 128
+                ? stackalloc byte[seedLength]
+                : new byte[seedLength];
             SecureRandom.NextBytes(seed);
 
             DigestRandomGenerator randomGenerator = new DigestRandomGenerator(digest);
@@ -152,6 +192,7 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
 
             return new BcTlsNonceGenerator(randomGenerator);
         }
+#endif
 
         public override bool HasAnyStreamVerifiers(IList<SignatureAndHashAlgorithm> signatureAndHashAlgorithms)
         {
@@ -499,8 +540,8 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
         protected virtual TlsAeadCipher CreateCipher_Aes_Ccm(TlsCryptoParameters cryptoParams, int cipherKeySize,
             int macSize)
         {
-            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Ccm(), true);
-            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Ccm(), false);
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Aes_Ccm(), false);
 
             return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_CCM);
         }
@@ -508,8 +549,8 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
         protected virtual TlsAeadCipher CreateCipher_Aes_Gcm(TlsCryptoParameters cryptoParams, int cipherKeySize,
             int macSize)
         {
-            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Gcm(), true);
-            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Gcm(), false);
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Aes_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Aes_Gcm(), false);
 
             return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_GCM);
         }
@@ -517,8 +558,8 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
         protected virtual TlsAeadCipher CreateCipher_Aria_Gcm(TlsCryptoParameters cryptoParams, int cipherKeySize,
             int macSize)
         {
-            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aria_Gcm(), true);
-            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aria_Gcm(), false);
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Aria_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Aria_Gcm(), false);
 
             return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_GCM);
         }
@@ -526,8 +567,8 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
         protected virtual TlsAeadCipher CreateCipher_Camellia_Gcm(TlsCryptoParameters cryptoParams, int cipherKeySize,
             int macSize)
         {
-            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Camellia_Gcm(), true);
-            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Camellia_Gcm(), false);
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Camellia_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_Camellia_Gcm(), false);
 
             return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_GCM);
         }
@@ -546,16 +587,16 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
 
         protected virtual TlsAeadCipher CreateCipher_SM4_Ccm(TlsCryptoParameters cryptoParams)
         {
-            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Ccm(), true);
-            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Ccm(), false);
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_SM4_Ccm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_SM4_Ccm(), false);
 
             return new TlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 16, TlsAeadCipher.AEAD_CCM);
         }
 
         protected virtual TlsAeadCipher CreateCipher_SM4_Gcm(TlsCryptoParameters cryptoParams)
         {
-            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Gcm(), true);
-            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Gcm(), false);
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_SM4_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadCipher_SM4_Gcm(), false);
 
             return new TlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 16, TlsAeadCipher.AEAD_GCM);
         }
@@ -596,43 +637,43 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             return new SM4Engine();
         }
 
-        protected virtual IAeadBlockCipher CreateCcmMode(IBlockCipher engine)
+        protected virtual IAeadCipher CreateCcmMode(IBlockCipher engine)
         {
             return new CcmBlockCipher(engine);
         }
 
-        protected virtual IAeadBlockCipher CreateGcmMode(IBlockCipher engine)
+        protected virtual IAeadCipher CreateGcmMode(IBlockCipher engine)
         {
             // TODO Consider allowing custom configuration of multiplier
             return new GcmBlockCipher(engine);
         }
 
-        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Ccm()
+        protected virtual IAeadCipher CreateAeadCipher_Aes_Ccm()
         {
             return CreateCcmMode(CreateAesEngine());
         }
 
-        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Gcm()
+        protected virtual IAeadCipher CreateAeadCipher_Aes_Gcm()
         {
             return CreateGcmMode(CreateAesEngine());
         }
 
-        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aria_Gcm()
+        protected virtual IAeadCipher CreateAeadCipher_Aria_Gcm()
         {
             return CreateGcmMode(CreateAriaEngine());
         }
 
-        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Camellia_Gcm()
+        protected virtual IAeadCipher CreateAeadCipher_Camellia_Gcm()
         {
             return CreateGcmMode(CreateCamelliaEngine());
         }
 
-        protected virtual IAeadBlockCipher CreateAeadBlockCipher_SM4_Ccm()
+        protected virtual IAeadCipher CreateAeadCipher_SM4_Ccm()
         {
             return CreateCcmMode(CreateSM4Engine());
         }
 
-        protected virtual IAeadBlockCipher CreateAeadBlockCipher_SM4_Gcm()
+        protected virtual IAeadCipher CreateAeadCipher_SM4_Gcm()
         {
             return CreateGcmMode(CreateSM4Engine());
         }
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs
index faf6b4576..6a947c23b 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs
@@ -24,7 +24,7 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
 
         private static int GetValueLength(DHParameters dh)
         {
-            return (dh.P.BitLength + 7) / 8;
+            return BigIntegers.GetUnsignedByteLength(dh.P);
         }
 
         public static BcTlsSecret CalculateDHAgreement(BcTlsCrypto crypto, DHPrivateKeyParameters privateKey,
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs b/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs
index 0b35831f3..0ad2576cb 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs
@@ -28,6 +28,13 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             m_digest.BlockUpdate(data, offSet, length);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            m_digest.BlockUpdate(input);
+        }
+#endif
+
         public byte[] CalculateHash()
         {
             byte[] rv = new byte[m_digest.GetDigestSize()];
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs b/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs
index 485a3f744..dbe7f4c69 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs
@@ -20,11 +20,25 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             m_hmac.Init(new KeyParameter(key, keyOff, keyLen));
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void SetKey(ReadOnlySpan<byte> key)
+        {
+            m_hmac.Init(new KeyParameter(key));
+        }
+#endif
+
         public void Update(byte[] input, int inOff, int length)
         {
             m_hmac.BlockUpdate(input, inOff, length);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            m_hmac.BlockUpdate(input);
+        }
+#endif
+
         public byte[] CalculateMac()
         {
             byte[] rv = new byte[m_hmac.GetMacSize()];
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsRawKeyCertificate.cs b/crypto/src/tls/crypto/impl/bc/BcTlsRawKeyCertificate.cs
new file mode 100644
index 000000000..4d208b35a
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsRawKeyCertificate.cs
@@ -0,0 +1,507 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cmp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for a single X.509 certificate based on the BC light-weight API.</summary>
+    public class BcTlsRawKeyCertificate
+        : TlsCertificate
+    {
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly SubjectPublicKeyInfo m_keyInfo;
+
+        protected DHPublicKeyParameters m_pubKeyDH = null;
+        protected ECPublicKeyParameters m_pubKeyEC = null;
+        protected Ed25519PublicKeyParameters m_pubKeyEd25519 = null;
+        protected Ed448PublicKeyParameters m_pubKeyEd448 = null;
+        protected RsaKeyParameters m_pubKeyRsa = null;
+
+        /// <exception cref="IOException"/>
+        public BcTlsRawKeyCertificate(BcTlsCrypto crypto, byte[] encoding)
+            : this(crypto, SubjectPublicKeyInfo.GetInstance(encoding))
+        {
+        }
+
+        public BcTlsRawKeyCertificate(BcTlsCrypto crypto, SubjectPublicKeyInfo keyInfo)
+        {
+            m_crypto = crypto;
+            m_keyInfo = keyInfo;
+        }
+
+        public virtual SubjectPublicKeyInfo SubjectPublicKeyInfo => m_keyInfo;
+
+        /// <exception cref="IOException"/>
+        public virtual TlsEncryptor CreateEncryptor(int tlsCertificateRole)
+        {
+            ValidateKeyUsage(KeyUsage.KeyEncipherment);
+
+            switch (tlsCertificateRole)
+            {
+            case TlsCertificateRole.RsaEncryption:
+            {
+                this.m_pubKeyRsa = GetPubKeyRsa();
+                return new BcTlsRsaEncryptor(m_crypto, m_pubKeyRsa);
+            }
+            // TODO[gmssl]
+            //case TlsCertificateRole.Sm2Encryption:
+            //{
+            //    this.m_pubKeyEC = GetPubKeyEC();
+            //    return new BcTlsSM2Encryptor(m_crypto, m_pubKeyEC);
+            //}
+            }
+
+            throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsVerifier CreateVerifier(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.ed25519:
+            case SignatureAlgorithm.ed448:
+            {
+                int signatureScheme = SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm);
+                Tls13Verifier tls13Verifier = CreateVerifier(signatureScheme);
+                return new LegacyTls13Verifier(signatureScheme, tls13Verifier);
+            }
+            }
+
+            ValidateKeyUsage(KeyUsage.DigitalSignature);
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.dsa:
+                return new BcTlsDsaVerifier(m_crypto, GetPubKeyDss());
+
+            case SignatureAlgorithm.ecdsa:
+                return new BcTlsECDsaVerifier(m_crypto, GetPubKeyEC());
+
+            case SignatureAlgorithm.rsa:
+            {
+                ValidateRsa_Pkcs1();
+                return new BcTlsRsaVerifier(m_crypto, GetPubKeyRsa());
+            }
+
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+            {
+                ValidateRsa_Pss_Pss(signatureAlgorithm);
+                int signatureScheme = SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm);
+                return new BcTlsRsaPssVerifier(m_crypto, GetPubKeyRsa(), signatureScheme);
+            }
+
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+            {
+                ValidateRsa_Pss_Rsae();
+                int signatureScheme = SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm);
+                return new BcTlsRsaPssVerifier(m_crypto, GetPubKeyRsa(), signatureScheme);
+            }
+
+            default:
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual Tls13Verifier CreateVerifier(int signatureScheme)
+        {
+            ValidateKeyUsage(KeyUsage.DigitalSignature);
+
+            switch (signatureScheme)
+            {
+            case SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512:
+            case SignatureScheme.ecdsa_secp256r1_sha256:
+            case SignatureScheme.ecdsa_secp384r1_sha384:
+            case SignatureScheme.ecdsa_secp521r1_sha512:
+            case SignatureScheme.ecdsa_sha1:
+            {
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
+
+                ISigner verifier = new DsaDigestSigner(new ECDsaSigner(), digest);
+                verifier.Init(false, GetPubKeyEC());
+
+                return new BcTls13Verifier(verifier);
+            }
+
+            case SignatureScheme.ed25519:
+            {
+                Ed25519Signer verifier = new Ed25519Signer();
+                verifier.Init(false, GetPubKeyEd25519());
+
+                return new BcTls13Verifier(verifier);
+            }
+
+            case SignatureScheme.ed448:
+            {
+                Ed448Signer verifier = new Ed448Signer(TlsUtilities.EmptyBytes);
+                verifier.Init(false, GetPubKeyEd448());
+
+                return new BcTls13Verifier(verifier);
+            }
+
+            case SignatureScheme.rsa_pkcs1_sha1:
+            case SignatureScheme.rsa_pkcs1_sha256:
+            case SignatureScheme.rsa_pkcs1_sha384:
+            case SignatureScheme.rsa_pkcs1_sha512:
+            {
+                ValidateRsa_Pkcs1();
+
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
+
+                RsaDigestSigner verifier = new RsaDigestSigner(digest,
+                    TlsCryptoUtilities.GetOidForHash(cryptoHashAlgorithm));
+                verifier.Init(false, GetPubKeyRsa());
+
+                return new BcTls13Verifier(verifier);
+            }
+
+            case SignatureScheme.rsa_pss_pss_sha256:
+            case SignatureScheme.rsa_pss_pss_sha384:
+            case SignatureScheme.rsa_pss_pss_sha512:
+            {
+                ValidateRsa_Pss_Pss(SignatureScheme.GetSignatureAlgorithm(signatureScheme));
+
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
+
+                PssSigner verifier = new PssSigner(new RsaEngine(), digest, digest.GetDigestSize());
+                verifier.Init(false, GetPubKeyRsa());
+
+                return new BcTls13Verifier(verifier);
+            }
+
+            case SignatureScheme.rsa_pss_rsae_sha256:
+            case SignatureScheme.rsa_pss_rsae_sha384:
+            case SignatureScheme.rsa_pss_rsae_sha512:
+            {
+                ValidateRsa_Pss_Rsae();
+
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+                IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
+
+                PssSigner verifier = new PssSigner(new RsaEngine(), digest, digest.GetDigestSize());
+                verifier.Init(false, GetPubKeyRsa());
+
+                return new BcTls13Verifier(verifier);
+            }
+
+            // TODO[RFC 8998]
+            //case SignatureScheme.sm2sig_sm3:
+            //{
+            //    ParametersWithID parametersWithID = new ParametersWithID(GetPubKeyEC(),
+            //        Strings.ToByteArray("TLSv1.3+GM+Cipher+Suite"));
+    
+            //    SM2Signer verifier = new SM2Signer();
+            //    verifier.Init(false, parametersWithID);
+
+            //    return new BcTls13Verifier(verifier);
+            //}
+
+            default:
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] GetEncoded()
+        {
+            return m_keyInfo.GetEncoded(Asn1Encodable.Der);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] GetExtension(DerObjectIdentifier extensionOid)
+        {
+            return null;
+        }
+
+        public virtual BigInteger SerialNumber => null;
+
+        public virtual string SigAlgOid => null;
+
+        public virtual Asn1Encodable GetSigAlgParams() => null;
+
+        /// <exception cref="IOException"/>
+        public virtual short GetLegacySignatureAlgorithm()
+        {
+            AsymmetricKeyParameter publicKey = GetPublicKey();
+            if (publicKey.IsPrivate)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (!SupportsKeyUsage(KeyUsage.DigitalSignature))
+                return -1;
+
+            /*
+             * RFC 5246 7.4.6. Client Certificate
+             */
+
+            /*
+             * RSA public key; the certificate MUST allow the key to be used for signing with the
+             * signature scheme and hash algorithm that will be employed in the certificate verify
+             * message.
+             */
+            if (publicKey is RsaKeyParameters)
+                return SignatureAlgorithm.rsa;
+
+            /*
+                * DSA public key; the certificate MUST allow the key to be used for signing with the
+                * hash algorithm that will be employed in the certificate verify message.
+                */
+            if (publicKey is DsaPublicKeyParameters)
+                return SignatureAlgorithm.dsa;
+
+            /*
+             * ECDSA-capable public key; the certificate MUST allow the key to be used for signing
+             * with the hash algorithm that will be employed in the certificate verify message; the
+             * public key MUST use a curve and point format supported by the server.
+             */
+            if (publicKey is ECPublicKeyParameters)
+            {
+                // TODO Check the curve and point format
+                return SignatureAlgorithm.ecdsa;
+            }
+
+            return -1;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DHPublicKeyParameters GetPubKeyDH()
+        {
+            try
+            {
+                return (DHPublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DsaPublicKeyParameters GetPubKeyDss()
+        {
+            try
+            {
+                return (DsaPublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual ECPublicKeyParameters GetPubKeyEC()
+        {
+            try
+            {
+                return (ECPublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual Ed25519PublicKeyParameters GetPubKeyEd25519()
+        {
+            try
+            {
+                return (Ed25519PublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual Ed448PublicKeyParameters GetPubKeyEd448()
+        {
+            try
+            {
+                return (Ed448PublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual RsaKeyParameters GetPubKeyRsa()
+        {
+            try
+            {
+                return (RsaKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm)
+        {
+            return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.DigitalSignature);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual bool SupportsSignatureAlgorithmCA(short signatureAlgorithm)
+        {
+            return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.KeyCertSign);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsCertificate CheckUsageInRole(int tlsCertificateRole)
+        {
+            switch (tlsCertificateRole)
+            {
+            case TlsCertificateRole.DH:
+            {
+                ValidateKeyUsage(KeyUsage.KeyAgreement);
+                this.m_pubKeyDH = GetPubKeyDH();
+                return this;
+            }
+            case TlsCertificateRole.ECDH:
+            {
+                ValidateKeyUsage(KeyUsage.KeyAgreement);
+                this.m_pubKeyEC = GetPubKeyEC();
+                return this;
+            }
+            }
+
+            throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual AsymmetricKeyParameter GetPublicKey()
+        {
+            try
+            {
+                return PublicKeyFactory.CreateKey(m_keyInfo);
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e);
+            }
+        }
+
+        protected virtual bool SupportsKeyUsage(int keyUsageBits)
+        {
+            return true;
+        }
+
+        protected virtual bool SupportsRsa_Pkcs1()
+        {
+            AlgorithmIdentifier pubKeyAlgID = m_keyInfo.AlgorithmID;
+            return RsaUtilities.SupportsPkcs1(pubKeyAlgID);
+        }
+
+        protected virtual bool SupportsRsa_Pss_Pss(short signatureAlgorithm)
+        {
+            AlgorithmIdentifier pubKeyAlgID = m_keyInfo.AlgorithmID;
+            return RsaUtilities.SupportsPss_Pss(signatureAlgorithm, pubKeyAlgID);
+        }
+
+        protected virtual bool SupportsRsa_Pss_Rsae()
+        {
+            AlgorithmIdentifier pubKeyAlgID = m_keyInfo.AlgorithmID;
+            return RsaUtilities.SupportsPss_Rsae(pubKeyAlgID);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm, int keyUsage)
+        {
+            if (!SupportsKeyUsage(keyUsage))
+                return false;
+
+            AsymmetricKeyParameter publicKey = GetPublicKey();
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+                return SupportsRsa_Pkcs1()
+                    && publicKey is RsaKeyParameters;
+
+            case SignatureAlgorithm.dsa:
+                return publicKey is DsaPublicKeyParameters;
+
+            case SignatureAlgorithm.ecdsa:
+            case SignatureAlgorithm.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureAlgorithm.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512:
+                return publicKey is ECPublicKeyParameters;
+
+            case SignatureAlgorithm.ed25519:
+                return publicKey is Ed25519PublicKeyParameters;
+
+            case SignatureAlgorithm.ed448:
+                return publicKey is Ed448PublicKeyParameters;
+
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return SupportsRsa_Pss_Rsae()
+                    && publicKey is RsaKeyParameters;
+
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return SupportsRsa_Pss_Pss(signatureAlgorithm)
+                    && publicKey is RsaKeyParameters;
+
+            default:
+                return false;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void ValidateKeyUsage(int keyUsageBits)
+        {
+            if (!SupportsKeyUsage(keyUsageBits))
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ValidateRsa_Pkcs1()
+        {
+            if (!SupportsRsa_Pkcs1())
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ValidateRsa_Pss_Pss(short signatureAlgorithm)
+        {
+            if (!SupportsRsa_Pss_Pss(signatureAlgorithm))
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ValidateRsa_Pss_Rsae()
+        {
+            if (!SupportsRsa_Pss_Rsae())
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs b/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs
index 9cd060d18..683806347 100644
--- a/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs
@@ -74,8 +74,34 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override TlsSecret DeriveUsingPrf(int prfAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<byte> seed,
+            int length)
+        {
+            lock (this)
+            {
+                CheckAlive();
+
+                switch (prfAlgorithm)
+                {
+                case PrfAlgorithm.tls13_hkdf_sha256:
+                    return TlsCryptoUtilities.HkdfExpandLabel(this, CryptoHashAlgorithm.sha256, label, seed, length);
+                case PrfAlgorithm.tls13_hkdf_sha384:
+                    return TlsCryptoUtilities.HkdfExpandLabel(this, CryptoHashAlgorithm.sha384, label, seed, length);
+                case PrfAlgorithm.tls13_hkdf_sm3:
+                    return TlsCryptoUtilities.HkdfExpandLabel(this, CryptoHashAlgorithm.sm3, label, seed, length);
+                default:
+                    return m_crypto.AdoptLocalSecret(Prf(prfAlgorithm, label, seed, length));
+                }
+            }
+        }
+#endif
+
         public override TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return HkdfExpand(cryptoHashAlgorithm, info.AsSpan(), length);
+#else
             lock (this)
             {
                 if (length < 1)
@@ -118,7 +144,58 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
 
                 return m_crypto.AdoptLocalSecret(okm);
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override TlsSecret HkdfExpand(int cryptoHashAlgorithm, ReadOnlySpan<byte> info, int length)
+        {
+            lock (this)
+            {
+                if (length < 1)
+                    return m_crypto.AdoptLocalSecret(TlsUtilities.EmptyBytes);
+
+                int hashLen = TlsCryptoUtilities.GetHashOutputSize(cryptoHashAlgorithm);
+                if (length > (255 * hashLen))
+                    throw new ArgumentException("must be <= 255 * (output size of 'hashAlgorithm')", "length");
+
+                CheckAlive();
+
+                ReadOnlySpan<byte> prk = m_data;
+
+                HMac hmac = new HMac(m_crypto.CreateDigest(cryptoHashAlgorithm));
+                hmac.Init(new KeyParameter(prk));
+
+                byte[] okm = new byte[length];
+
+                Span<byte> t = hashLen <= 128
+                    ? stackalloc byte[hashLen]
+                    : new byte[hashLen];
+                byte counter = 0x00;
+
+                int pos = 0;
+                for (;;)
+                {
+                    hmac.BlockUpdate(info);
+                    hmac.Update(++counter);
+                    hmac.DoFinal(t);
+
+                    int remaining = length - pos;
+                    if (remaining <= hashLen)
+                    {
+                        t[..remaining].CopyTo(okm.AsSpan(pos));
+                        break;
+                    }
+
+                    t.CopyTo(okm.AsSpan(pos));
+                    pos += hashLen;
+                    hmac.BlockUpdate(t);
+                }
+
+                return m_crypto.AdoptLocalSecret(okm);
+            }
         }
+#endif
 
         public override TlsSecret HkdfExtract(int cryptoHashAlgorithm, TlsSecret ikm)
         {
@@ -187,8 +264,33 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             return Prf_1_2(prfAlgorithm, labelSeed, length);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected virtual byte[] Prf(int prfAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<byte> seed, int length)
+        {
+            if (PrfAlgorithm.ssl_prf_legacy == prfAlgorithm)
+                return Prf_Ssl(seed, length);
+
+            byte[] labelSeed = new byte[label.Length + seed.Length];
+
+            for (int i = 0; i < label.Length; ++i)
+            {
+                labelSeed[i] = (byte)label[i];
+            }
+
+            seed.CopyTo(labelSeed.AsSpan(label.Length));
+
+            if (PrfAlgorithm.tls_prf_legacy == prfAlgorithm)
+                return Prf_1_0(labelSeed, length);
+
+            return Prf_1_2(prfAlgorithm, labelSeed, length);
+        }
+#endif
+
         protected virtual byte[] Prf_Ssl(byte[] seed, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Prf_Ssl(seed.AsSpan(), length);
+#else
             IDigest md5 = m_crypto.CreateDigest(CryptoHashAlgorithm.md5);
             IDigest sha1 = m_crypto.CreateDigest(CryptoHashAlgorithm.sha1);
 
@@ -226,7 +328,51 @@ namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
             }
 
             return result;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected virtual byte[] Prf_Ssl(ReadOnlySpan<byte> seed, int length)
+        {
+            IDigest md5 = m_crypto.CreateDigest(CryptoHashAlgorithm.md5);
+            IDigest sha1 = m_crypto.CreateDigest(CryptoHashAlgorithm.sha1);
+
+            int md5Size = md5.GetDigestSize();
+            int sha1Size = sha1.GetDigestSize();
+
+            Span<byte> tmp = stackalloc byte[System.Math.Max(md5Size, sha1Size)];
+            byte[] result = new byte[length];
+
+            int constLen = 1, constPos = 0, resultPos = 0;
+            while (resultPos < length)
+            {
+                sha1.BlockUpdate(Ssl3Const.AsSpan(constPos, constLen));
+                constPos += constLen++;
+
+                sha1.BlockUpdate(m_data);
+                sha1.BlockUpdate(seed);
+                sha1.DoFinal(tmp);
+
+                md5.BlockUpdate(m_data);
+                md5.BlockUpdate(tmp[..sha1Size]);
+
+                int remaining = length - resultPos;
+                if (remaining < md5Size)
+                {
+                    md5.DoFinal(tmp);
+                    tmp[..remaining].CopyTo(result.AsSpan(resultPos));
+                    resultPos += remaining;
+                }
+                else
+                {
+                    md5.DoFinal(result.AsSpan(resultPos));
+                    resultPos += md5Size;
+                }
+            }
+
+            return result;
         }
+#endif
 
         protected virtual byte[] Prf_1_0(byte[] labelSeed, int length)
         {
diff --git a/crypto/src/tsp/TimeStampResponseGenerator.cs b/crypto/src/tsp/TimeStampResponseGenerator.cs
index 9a9c78678..76b400d6e 100644
--- a/crypto/src/tsp/TimeStampResponseGenerator.cs
+++ b/crypto/src/tsp/TimeStampResponseGenerator.cs
@@ -8,7 +8,6 @@ using Org.BouncyCastle.Asn1.Cms;
 using Org.BouncyCastle.Asn1.Tsp;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Utilities.Date;
 
 namespace Org.BouncyCastle.Tsp
 {
@@ -84,14 +83,6 @@ namespace Org.BouncyCastle.Tsp
             return new PkiStatusInfo(new DerSequence(v));
         }
 
-        public TimeStampResponse Generate(
-            TimeStampRequest request,
-            BigInteger serialNumber,
-            DateTime genTime)
-        {
-            return Generate(request, serialNumber, new DateTimeObject(genTime));
-        }
-
         /**
          * Return an appropriate TimeStampResponse.
          * <p>
@@ -107,10 +98,7 @@ namespace Org.BouncyCastle.Tsp
          * @throws TSPException
          * </p>
          */
-        public TimeStampResponse Generate(
-            TimeStampRequest request,
-            BigInteger serialNumber,
-            DateTimeObject genTime)
+        public TimeStampResponse Generate(TimeStampRequest request, BigInteger serialNumber, DateTime? genTime)
         {
             TimeStampResp resp;
 
@@ -164,13 +152,8 @@ namespace Org.BouncyCastle.Tsp
             }
         }
 
-
-        public TimeStampResponse GenerateGrantedResponse(
-            TimeStampRequest request,
-            BigInteger serialNumber,
-            DateTimeObject genTime, 
-            string statusString, 
-            X509Extensions additionalExtensions)
+        public TimeStampResponse GenerateGrantedResponse(TimeStampRequest request, BigInteger serialNumber,
+            DateTime? genTime, string statusString, X509Extensions additionalExtensions)
         {
             TimeStampResp resp;
 
diff --git a/crypto/src/tsp/TimeStampTokenGenerator.cs b/crypto/src/tsp/TimeStampTokenGenerator.cs
index 0d6d102d3..9e6a21f9c 100644
--- a/crypto/src/tsp/TimeStampTokenGenerator.cs
+++ b/crypto/src/tsp/TimeStampTokenGenerator.cs
@@ -80,7 +80,7 @@ namespace Org.BouncyCastle.Tsp
 
             try
             {
-                IStreamCalculator calculator = digestCalculator.CreateCalculator();
+                IStreamCalculator<IBlockResult> calculator = digestCalculator.CreateCalculator();
                 Stream stream = calculator.Stream;
                 byte[] certEnc = assocCert.GetEncoded();
                 stream.Write(certEnc, 0, certEnc.Length);
@@ -90,7 +90,7 @@ namespace Org.BouncyCastle.Tsp
                 if (((AlgorithmIdentifier)digestCalculator.AlgorithmDetails).Algorithm.Equals(OiwObjectIdentifiers.IdSha1))
                 {
                     EssCertID essCertID = new EssCertID(
-                       ((IBlockResult)calculator.GetResult()).Collect(),
+                       calculator.GetResult().Collect(),
                        isIssuerSerialIncluded ?
                            new IssuerSerial(
                                new GeneralNames(
@@ -107,7 +107,7 @@ namespace Org.BouncyCastle.Tsp
                         ((AlgorithmIdentifier)digestCalculator.AlgorithmDetails).Algorithm);
 
                     EssCertIDv2 essCertID = new EssCertIDv2(
-                        ((IBlockResult)calculator.GetResult()).Collect(),
+                        calculator.GetResult().Collect(),
                         isIssuerSerialIncluded ?
                             new IssuerSerial(
                                 new GeneralNames(
@@ -334,21 +334,18 @@ namespace Org.BouncyCastle.Tsp
                 respExtensions = extGen.Generate();
             }
 
-
-
-            DerGeneralizedTime generalizedTime;
-            if (resolution != Resolution.R_SECONDS)
+            Asn1GeneralizedTime timeStampTime;
+            if (resolution == Resolution.R_SECONDS)
             {
-                generalizedTime = new DerGeneralizedTime(createGeneralizedTime(genTime));
-            } 
+                timeStampTime = new Asn1GeneralizedTime(genTime);
+            }
             else
             {
-                generalizedTime = new DerGeneralizedTime(genTime);
-            }
-
+                timeStampTime = CreateGeneralizedTime(genTime);
+            } 
 
             TstInfo tstInfo = new TstInfo(tsaPolicy, messageImprint,
-                new DerInteger(serialNumber), generalizedTime, accuracy,
+                new DerInteger(serialNumber), timeStampTime, accuracy,
                 derOrdering, nonce, tsa, respExtensions);
 
             try
@@ -382,13 +379,13 @@ namespace Org.BouncyCastle.Tsp
             {
                 throw new TspException("Exception encoding info", e);
             }
-            //			catch (InvalidAlgorithmParameterException e)
-            //			{
-            //				throw new TspException("Exception handling CertStore CRLs", e);
-            //			}
+            //catch (InvalidAlgorithmParameterException e)
+            //{
+            //    throw new TspException("Exception handling CertStore CRLs", e);
+            //}
         }
 
-        private string createGeneralizedTime(DateTime genTime)
+        private Asn1GeneralizedTime CreateGeneralizedTime(DateTime genTime)
         {
             string format = "yyyyMMddHHmmss.fff";
            
@@ -398,33 +395,31 @@ namespace Org.BouncyCastle.Tsp
             if (dotIndex <0)
             {
                 sBuild.Append("Z");
-                return sBuild.ToString();
+                return new Asn1GeneralizedTime(sBuild.ToString());
             }
 
             switch(resolution)
             {
-                case Resolution.R_TENTHS_OF_SECONDS:
-                    if (sBuild.Length > dotIndex + 2)
-                    {
-                        sBuild.Remove(dotIndex + 2, sBuild.Length-(dotIndex+2));
-                    }
-                    break;
-                case Resolution.R_HUNDREDTHS_OF_SECONDS:
-                    if (sBuild.Length > dotIndex + 3)
-                    {
-                        sBuild.Remove(dotIndex + 3, sBuild.Length-(dotIndex+3));
-                    }
-                    break;
+            case Resolution.R_TENTHS_OF_SECONDS:
+                if (sBuild.Length > dotIndex + 2)
+                {
+                    sBuild.Remove(dotIndex + 2, sBuild.Length-(dotIndex+2));
+                }
+                break;
+            case Resolution.R_HUNDREDTHS_OF_SECONDS:
+                if (sBuild.Length > dotIndex + 3)
+                {
+                    sBuild.Remove(dotIndex + 3, sBuild.Length-(dotIndex+3));
+                }
+                break;
 
 
-                case Resolution.R_SECONDS:
-                case Resolution.R_MILLISECONDS:
-                    // do nothing.
-                    break;
-             
+            case Resolution.R_SECONDS:
+            case Resolution.R_MILLISECONDS:
+                // do nothing.
+                break;
             }
 
-           
             while (sBuild[sBuild.Length - 1] == '0')
             {
                 sBuild.Remove(sBuild.Length - 1,1);
@@ -436,7 +431,7 @@ namespace Org.BouncyCastle.Tsp
             }
 
             sBuild.Append("Z");
-            return sBuild.ToString();
+            return new Asn1GeneralizedTime(sBuild.ToString());
         }
 
         private class TableGen
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 247f3ab77..8b10ae6a3 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -21,6 +21,18 @@ namespace Org.BouncyCastle.Utilities
             return bits == 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool AreAllZeroes(ReadOnlySpan<byte> buf)
+        {
+            uint bits = 0;
+            for (int i = 0; i < buf.Length; ++i)
+            {
+                bits |= buf[i];
+            }
+            return bits == 0;
+        }
+#endif
+
         public static bool AreEqual(
             bool[]  a,
             bool[]  b)
@@ -124,10 +136,26 @@ namespace Org.BouncyCastle.Utilities
             int d = 0;
             for (int i = 0; i < len; ++i)
             {
-                d |= (a[aOff + i] ^ b[bOff + i]);
+                d |= a[aOff + i] ^ b[bOff + i];
+            }
+            return 0 == d;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ConstantTimeAreEqual(Span<byte> a, Span<byte> b)
+        {
+            if (a.Length != b.Length)
+                throw new ArgumentException("Spans to compare must have equal length");
+
+            int d = 0;
+            for (int i = 0, count = a.Length; i < count; ++i)
+            {
+                d |= a[i] ^ b[i];
             }
             return 0 == d;
+
         }
+#endif
 
         public static bool AreEqual(
             int[]	a,
@@ -592,6 +620,21 @@ namespace Org.BouncyCastle.Utilities
             }
         }
 
+        public static void Fill<T>(T[] ts, T t)
+        {
+            for (int i = 0; i < ts.Length; ++i)
+            {
+                ts[i] = t;
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Fill<T>(Span<T> ts, T t)
+        {
+            ts.Fill(t);
+        }
+#endif
+
         public static byte[] CopyOf(byte[] data, int newLength)
         {
             byte[] tmp = new byte[newLength];
@@ -841,6 +884,17 @@ namespace Org.BouncyCastle.Utilities
             return result;
         }
 
+        public static T[] Prepend<T>(T[] a, T b)
+        {
+            if (a == null)
+                return new T[1]{ b };
+
+            T[] result = new T[1 + a.Length];
+            result[0] = b;
+            a.CopyTo(result, 1);
+            return result;
+        }
+
         public static byte[] Reverse(byte[] a)
         {
             if (a == null)
@@ -873,36 +927,13 @@ namespace Org.BouncyCastle.Utilities
             return result;
         }
 
-        public static byte[] ReverseInPlace(byte[] a)
+        public static T[] ReverseInPlace<T>(T[] array)
         {
-            if (null == a)
-                return null;
-
-            int p1 = 0, p2 = a.Length - 1;
-            while (p1 < p2)
-            {
-                byte t1 = a[p1], t2 = a[p2];
-                a[p1++] = t2;
-                a[p2--] = t1;
-            }
-
-            return a;
-        }
-
-        public static int[] ReverseInPlace(int[] a)
-        {
-            if (null == a)
+            if (null == array)
                 return null;
 
-            int p1 = 0, p2 = a.Length - 1;
-            while (p1 < p2)
-            {
-                int t1 = a[p1], t2 = a[p2];
-                a[p1++] = t2;
-                a[p2--] = t1;
-            }
-
-            return a;
+            Array.Reverse(array);
+            return array;
         }
 
         public static void Clear(byte[] data)
@@ -944,5 +975,37 @@ namespace Org.BouncyCastle.Utilities
         {
             return null == array || array.Length < 1;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+
+        public static byte[] Concatenate(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b)
+        {
+            byte[] rv = new byte[a.Length + b.Length];
+            a.CopyTo(rv);
+            b.CopyTo(rv.AsSpan(a.Length));
+            return rv;
+        }
+
+        public static byte[] Concatenate(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b, ReadOnlySpan<byte> c)
+        {
+            byte[] rv = new byte[a.Length + b.Length + c.Length];
+            a.CopyTo(rv);
+            b.CopyTo(rv.AsSpan(a.Length));
+            c.CopyTo(rv.AsSpan(a.Length + b.Length));
+            return rv;
+        }
+
+        public static byte[] Concatenate(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b, ReadOnlySpan<byte> c,
+            ReadOnlySpan<byte> d)
+        {
+            byte[] rv = new byte[a.Length + b.Length + c.Length + d.Length];
+            a.CopyTo(rv);
+            b.CopyTo(rv.AsSpan(a.Length));
+            c.CopyTo(rv.AsSpan(a.Length + b.Length));
+            d.CopyTo(rv.AsSpan(a.Length + b.Length + c.Length));
+            return rv;
+        }
+
+#endif
     }
 }
diff --git a/crypto/src/util/BigIntegers.cs b/crypto/src/util/BigIntegers.cs
index a61824394..e63af7c7c 100644
--- a/crypto/src/util/BigIntegers.cs
+++ b/crypto/src/util/BigIntegers.cs
@@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Utilities
     /**
      * BigInteger utilities.
      */
-    public abstract class BigIntegers
+    public static class BigIntegers
     {
         public static readonly BigInteger Zero = BigInteger.Zero;
         public static readonly BigInteger One = BigInteger.One;
@@ -22,8 +22,7 @@ namespace Org.BouncyCastle.Utilities
         * @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(
-            BigInteger n)
+        public static byte[] AsUnsignedByteArray(BigInteger n)
         {
             return n.ToByteArrayUnsigned();
         }
@@ -37,6 +36,16 @@ namespace Org.BouncyCastle.Utilities
          */
         public static byte[] AsUnsignedByteArray(int length, BigInteger n)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int bytesLength = n.GetLengthofByteArrayUnsigned();
+
+            if (bytesLength > length)
+                throw new ArgumentException("standard length exceeded", nameof(n));
+
+            byte[] bytes = new byte[length];
+            n.ToByteArrayUnsigned(bytes.AsSpan(length - bytesLength));
+            return bytes;
+#else
             byte[] bytes = n.ToByteArrayUnsigned();
             int bytesLength = bytes.Length;
 
@@ -44,18 +53,19 @@ namespace Org.BouncyCastle.Utilities
                 return bytes;
 
             if (bytesLength > length)
-                throw new ArgumentException("standard length exceeded", "n");
+                throw new ArgumentException("standard length exceeded", nameof(n));
 
             byte[] tmp = new byte[length];
             Array.Copy(bytes, 0, tmp, length - bytesLength, bytesLength);
             return tmp;
+#endif
         }
 
         /**
          * Write the passed in value as unsigned bytes to the specified buffer range, padded with
          * leading zeros as necessary.
          *
-         * @param value
+         * @param n
          *            the value to be converted.
          * @param buf
          *            the buffer to which the value is written.
@@ -64,25 +74,35 @@ namespace Org.BouncyCastle.Utilities
          * @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)
+        public static void AsUnsignedByteArray(BigInteger n, byte[] buf, int off, int len)
         {
-            byte[] bytes = value.ToByteArrayUnsigned();
-            if (bytes.Length == len)
-            {
-                Array.Copy(bytes, 0, buf, off, len);
-                return;
-            }
-
-            int start = bytes[0] == 0 ? 1 : 0;
-            int count = bytes.Length - start;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            AsUnsignedByteArray(n, buf.AsSpan(off, len));
+#else
+            byte[] bytes = n.ToByteArrayUnsigned();
+            int bytesLength = bytes.Length;
 
-            if (count > len)
-                throw new ArgumentException("standard length exceeded for value");
+            if (bytesLength > len)
+                throw new ArgumentException("standard length exceeded", nameof(n));
 
-            int padLen = len - count;
+            int padLen = len - bytesLength;
             Arrays.Fill(buf, off, off + padLen, 0);
-            Array.Copy(bytes, start, buf, off + padLen, count);
+            Array.Copy(bytes, 0, buf, off + padLen, bytesLength);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void AsUnsignedByteArray(BigInteger n, Span<byte> buf)
+        {
+            int bytesLength = n.GetLengthofByteArrayUnsigned();
+
+            if (bytesLength > buf.Length)
+                throw new ArgumentException("standard length exceeded", nameof(n));
+
+            buf[..^bytesLength].Fill(0x00);
+            n.ToByteArrayUnsigned(buf[^bytesLength..]);
         }
+#endif
 
         /// <summary>
         /// Creates a Random BigInteger from the secure random of a given bit length.
@@ -148,13 +168,31 @@ namespace Org.BouncyCastle.Utilities
             }
 
             int bits = M.BitLength;
-            uint[] m = Nat.FromBigInteger(bits, M);
-            uint[] x = Nat.FromBigInteger(bits, X);
-            int len = m.Length;
-            uint[] z = Nat.Create(len);
-            if (0 == Mod.ModOddInverse(m, x, z))
-                throw new ArithmeticException("BigInteger not invertible");
-            return Nat.ToBigInteger(len, z);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (bits <= 2048)
+            {
+                int len = Nat.GetLengthForBits(bits);
+                Span<uint> m = stackalloc uint[len];
+                Span<uint> x = stackalloc uint[len];
+                Span<uint> z = stackalloc uint[len];
+                Nat.FromBigInteger(bits, M, m);
+                Nat.FromBigInteger(bits, X, x);
+                if (0 == Mod.ModOddInverse(m, x, z))
+                    throw new ArithmeticException("BigInteger not invertible");
+                return Nat.ToBigInteger(len, z);
+            }
+            else
+#endif
+            {
+                uint[] m = Nat.FromBigInteger(bits, M);
+                uint[] x = Nat.FromBigInteger(bits, X);
+                int len = m.Length;
+                uint[] 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)
@@ -173,18 +211,41 @@ namespace Org.BouncyCastle.Utilities
                 return One;
 
             int bits = M.BitLength;
-            uint[] m = Nat.FromBigInteger(bits, M);
-            uint[] x = Nat.FromBigInteger(bits, X);
-            int len = m.Length;
-            uint[] z = Nat.Create(len);
-            if (!Mod.ModOddInverseVar(m, x, z))
-                throw new ArithmeticException("BigInteger not invertible");
-            return Nat.ToBigInteger(len, z);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (bits <= 2048)
+            {
+                int len = Nat.GetLengthForBits(bits);
+                Span<uint> m = stackalloc uint[len];
+                Span<uint> x = stackalloc uint[len];
+                Span<uint> z = stackalloc uint[len];
+                Nat.FromBigInteger(bits, M, m);
+                Nat.FromBigInteger(bits, X, x);
+                if (!Mod.ModOddInverseVar(m, x, z))
+                    throw new ArithmeticException("BigInteger not invertible");
+                return Nat.ToBigInteger(len, z);
+            }
+            else
+#endif
+            {
+                uint[] m = Nat.FromBigInteger(bits, M);
+                uint[] x = Nat.FromBigInteger(bits, X);
+                int len = m.Length;
+                uint[] z = Nat.Create(len);
+                if (!Mod.ModOddInverseVar(m, x, z))
+                    throw new ArithmeticException("BigInteger not invertible");
+                return Nat.ToBigInteger(len, z);
+            }
+        }
+
+        public static int GetByteLength(BigInteger n)
+        {
+            return n.GetLengthofByteArray();
         }
 
         public static int GetUnsignedByteLength(BigInteger n)
         {
-            return (n.BitLength + 7) / 8;
+            return n.GetLengthofByteArrayUnsigned();
         }
     }
 }
diff --git a/crypto/src/util/Bytes.cs b/crypto/src/util/Bytes.cs
index ecde85dde..466eba576 100644
--- a/crypto/src/util/Bytes.cs
+++ b/crypto/src/util/Bytes.cs
@@ -2,7 +2,7 @@
 
 namespace Org.BouncyCastle.Utilities
 {
-    public abstract class Bytes
+    public static class Bytes
     {
         public const int NumBits = 8;
         public const int NumBytes = 1;
diff --git a/crypto/src/util/Enums.cs b/crypto/src/util/Enums.cs
index fb685e2a7..1034b5b7e 100644
--- a/crypto/src/util/Enums.cs
+++ b/crypto/src/util/Enums.cs
@@ -4,38 +4,43 @@ using Org.BouncyCastle.Utilities.Date;
 
 namespace Org.BouncyCastle.Utilities
 {
-    internal abstract class Enums
+    internal static class Enums
     {
-        internal static Enum GetEnumValue(Type enumType, string s)
+        internal static TEnum GetEnumValue<TEnum>(string s)
+            where TEnum : struct, Enum
         {
-            if (!enumType.IsEnum)
-                throw new ArgumentException("Not an enumeration type", nameof(enumType));
-
             // We only want to parse single named constants
             if (s.Length > 0 && char.IsLetter(s[0]) && s.IndexOf(',') < 0)
             {
                 s = s.Replace('-', '_');
                 s = s.Replace('/', '_');
 
-                return (Enum)Enum.Parse(enumType, s, false);
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                return Enum.Parse<TEnum>(s, false);
+#else
+                return (TEnum)Enum.Parse(typeof(TEnum), s, false);
+#endif
             }
 
             throw new ArgumentException();
         }
 
-        internal static Array GetEnumValues(Type enumType)
+        internal static TEnum[] GetEnumValues<TEnum>()
+            where TEnum : struct, Enum
         {
-            if (!enumType.IsEnum)
-                throw new ArgumentException("Not an enumeration type", nameof(enumType));
-
-            return Enum.GetValues(enumType);
+#if NET5_0_OR_GREATER
+            return Enum.GetValues<TEnum>();
+#else
+            return (TEnum[])Enum.GetValues(typeof(TEnum));
+#endif
         }
 
-        internal static Enum GetArbitraryValue(Type enumType)
+        internal static TEnum GetArbitraryValue<TEnum>()
+            where TEnum : struct, Enum
         {
-            Array values = GetEnumValues(enumType);
+            TEnum[] values = GetEnumValues<TEnum>();
             int pos = (int)(DateTimeUtilities.CurrentUnixMs() & int.MaxValue) % values.Length;
-            return (Enum)values.GetValue(pos);
+            return values[pos];
         }
     }
 }
diff --git a/crypto/src/util/Integers.cs b/crypto/src/util/Integers.cs
index ff907ac80..ab1868b74 100644
--- a/crypto/src/util/Integers.cs
+++ b/crypto/src/util/Integers.cs
@@ -1,5 +1,9 @@
 using System;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers.Binary;
+#endif
 #if NETCOREAPP3_0_OR_GREATER
+using System.Numerics;
 using System.Runtime.Intrinsics.X86;
 #endif
 
@@ -7,7 +11,7 @@ using Org.BouncyCastle.Math.Raw;
 
 namespace Org.BouncyCastle.Utilities
 {
-    public abstract class Integers
+    public static class Integers
     {
         public const int NumBits = 32;
         public const int NumBytes = 4;
@@ -95,36 +99,60 @@ namespace Org.BouncyCastle.Utilities
 
         public static int ReverseBytes(int i)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReverseEndianness(i);
+#else
             return (int)ReverseBytes((uint)i);
+#endif
         }
 
         [CLSCompliant(false)]
         public static uint ReverseBytes(uint i)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReverseEndianness(i);
+#else
             return RotateLeft(i & 0xFF00FF00U,  8) |
                    RotateLeft(i & 0x00FF00FFU, 24);
+#endif
         }
 
         public static int RotateLeft(int i, int distance)
         {
-            return (i << distance) ^ (int)((uint)i >> -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return (int)BitOperations.RotateLeft((uint)i, distance);
+#else
+            return (i << distance) | (int)((uint)i >> -distance);
+#endif
         }
 
         [CLSCompliant(false)]
         public static uint RotateLeft(uint i, int distance)
         {
-            return (i << distance) ^ (i >> -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return BitOperations.RotateLeft(i, distance);
+#else
+            return (i << distance) | (i >> -distance);
+#endif
         }
 
         public static int RotateRight(int i, int distance)
         {
-            return (int)((uint)i >> distance) ^ (i << -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return (int)BitOperations.RotateRight((uint)i, distance);
+#else
+            return (int)((uint)i >> distance) | (i << -distance);
+#endif
         }
 
         [CLSCompliant(false)]
         public static uint RotateRight(uint i, int distance)
         {
-            return (i >> distance) ^ (i << -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return BitOperations.RotateRight(i, distance);
+#else
+            return (i >> distance) | (i << -distance);
+#endif
         }
     }
 }
diff --git a/crypto/src/util/Longs.cs b/crypto/src/util/Longs.cs
index 45dd91090..02ffb7676 100644
--- a/crypto/src/util/Longs.cs
+++ b/crypto/src/util/Longs.cs
@@ -1,5 +1,9 @@
 using System;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers.Binary;
+#endif
 #if NETCOREAPP3_0_OR_GREATER
+using System.Numerics;
 using System.Runtime.Intrinsics.X86;
 #endif
 
@@ -7,7 +11,7 @@ using Org.BouncyCastle.Math.Raw;
 
 namespace Org.BouncyCastle.Utilities
 {
-    public abstract class Longs
+    public static class Longs
     {
         public const int NumBits = 64;
         public const int NumBytes = 8;
@@ -94,38 +98,62 @@ namespace Org.BouncyCastle.Utilities
 
         public static long ReverseBytes(long i)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReverseEndianness(i);
+#else
             return (long)ReverseBytes((ulong)i);
+#endif
         }
 
         [CLSCompliant(false)]
         public static ulong ReverseBytes(ulong i)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReverseEndianness(i);
+#else
             return RotateLeft(i & 0xFF000000FF000000UL,  8) |
                    RotateLeft(i & 0x00FF000000FF0000UL, 24) |
                    RotateLeft(i & 0x0000FF000000FF00UL, 40) |
                    RotateLeft(i & 0x000000FF000000FFUL, 56);
+#endif
         }
 
         public static long RotateLeft(long i, int distance)
         {
-            return (i << distance) ^ (long)((ulong)i >> -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return (long)BitOperations.RotateLeft((ulong)i, distance);
+#else
+            return (i << distance) | (long)((ulong)i >> -distance);
+#endif
         }
 
         [CLSCompliant(false)]
         public static ulong RotateLeft(ulong i, int distance)
         {
-            return (i << distance) ^ (i >> -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return BitOperations.RotateLeft(i, distance);
+#else
+            return (i << distance) | (i >> -distance);
+#endif
         }
 
         public static long RotateRight(long i, int distance)
         {
-            return (long)((ulong)i >> distance) ^ (i << -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return (long)BitOperations.RotateRight((ulong)i, distance);
+#else
+            return (long)((ulong)i >> distance) | (i << -distance);
+#endif
         }
 
         [CLSCompliant(false)]
         public static ulong RotateRight(ulong i, int distance)
         {
-            return (i >> distance) ^ (i << -distance);
+#if NETCOREAPP3_0_OR_GREATER
+            return BitOperations.RotateRight(i, distance);
+#else
+            return (i >> distance) | (i << -distance);
+#endif
         }
     }
 }
diff --git a/crypto/src/util/Platform.cs b/crypto/src/util/Platform.cs
index 75b728bd9..118c29918 100644
--- a/crypto/src/util/Platform.cs
+++ b/crypto/src/util/Platform.cs
@@ -3,7 +3,7 @@ using System.Globalization;
 
 namespace Org.BouncyCastle.Utilities
 {
-    internal abstract class Platform
+    internal static class Platform
     {
         private static readonly CompareInfo InvariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo;
 
diff --git a/crypto/src/util/Shorts.cs b/crypto/src/util/Shorts.cs
new file mode 100644
index 000000000..eb9f13fa1
--- /dev/null
+++ b/crypto/src/util/Shorts.cs
@@ -0,0 +1,54 @@
+using System;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers.Binary;
+#endif
+
+namespace Org.BouncyCastle.Utilities
+{
+    public static class Shorts
+    {
+        public const int NumBits = 16;
+        public const int NumBytes = 2;
+
+        public static short ReverseBytes(short i)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReverseEndianness(i);
+#else
+            return RotateLeft(i, 8);
+#endif
+        }
+
+        [CLSCompliant(false)]
+        public static ushort ReverseBytes(ushort i)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return BinaryPrimitives.ReverseEndianness(i);
+#else
+            return RotateLeft(i, 8);
+#endif
+        }
+
+        public static short RotateLeft(short i, int distance)
+        {
+            return (short)RotateLeft((ushort)i, distance);
+        }
+
+        [CLSCompliant(false)]
+        public static ushort RotateLeft(ushort i, int distance)
+        {
+            return (ushort)((i << distance) | (i >> (16 - distance)));
+        }
+
+        public static short RotateRight(short i, int distance)
+        {
+            return (short)RotateRight((ushort)i, distance);
+        }
+
+        [CLSCompliant(false)]
+        public static ushort RotateRight(ushort i, int distance)
+        {
+            return (ushort)((i >> distance) | (i << (16 - distance)));
+        }
+    }
+}
diff --git a/crypto/src/util/Spans.cs b/crypto/src/util/Spans.cs
new file mode 100644
index 000000000..fa2b768d9
--- /dev/null
+++ b/crypto/src/util/Spans.cs
@@ -0,0 +1,42 @@
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System;
+using System.Runtime.CompilerServices;
+
+#nullable enable
+
+namespace Org.BouncyCastle.Utilities
+{
+    internal static class Spans
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void CopyFrom<T>(this Span<T> output, ReadOnlySpan<T> input)
+        {
+            input[..output.Length].CopyTo(output);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static Span<T> FromNullable<T>(T[]? array)
+        {
+            return array == null ? Span<T>.Empty : array.AsSpan();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static Span<T> FromNullable<T>(T[]? array, int start)
+        {
+            return array == null ? Span<T>.Empty : array.AsSpan(start);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static ReadOnlySpan<T> FromNullableReadOnly<T>(T[]? array)
+        {
+            return array == null ? Span<T>.Empty : array.AsSpan();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static ReadOnlySpan<T> FromNullableReadOnly<T>(T[]? array, int start)
+        {
+            return array == null ? Span<T>.Empty : array.AsSpan(start);
+        }
+    }
+}
+#endif
diff --git a/crypto/src/util/Strings.cs b/crypto/src/util/Strings.cs
index baee573be..12eafd21e 100644
--- a/crypto/src/util/Strings.cs
+++ b/crypto/src/util/Strings.cs
@@ -4,7 +4,7 @@ using System.Text;
 namespace Org.BouncyCastle.Utilities
 {
     /// <summary> General string utilities.</summary>
-    public abstract class Strings
+    public static class Strings
     {
         internal static bool IsOneOf(string s, params string[] candidates)
         {
diff --git a/crypto/src/util/Times.cs b/crypto/src/util/Times.cs
deleted file mode 100644
index 99a78d21a..000000000
--- a/crypto/src/util/Times.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace Org.BouncyCastle.Utilities
-{
-    public sealed class Times
-    {
-        private static long NanosecondsPerTick = 100L;
-
-        public static long NanoTime()
-        {
-            return DateTime.UtcNow.Ticks * NanosecondsPerTick;
-        }
-    }
-}
diff --git a/crypto/src/bzip2/BZip2Constants.cs b/crypto/src/util/bzip2/BZip2Constants.cs
index 81db7fa57..6fc15e55f 100644
--- a/crypto/src/bzip2/BZip2Constants.cs
+++ b/crypto/src/util/bzip2/BZip2Constants.cs
@@ -22,9 +22,7 @@
  * great code.
  */
 
-using System;
-
-namespace Org.BouncyCastle.Bzip2
+namespace Org.BouncyCastle.Utilities.Bzip2
 {
     /**
     * Base class for both the compress and decompress classes.
diff --git a/crypto/src/bzip2/CBZip2InputStream.cs b/crypto/src/util/bzip2/CBZip2InputStream.cs
index b73e52a01..7879f28af 100644
--- a/crypto/src/bzip2/CBZip2InputStream.cs
+++ b/crypto/src/util/bzip2/CBZip2InputStream.cs
@@ -26,10 +26,9 @@ using System;
 using System.Diagnostics;
 using System.IO;
 
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
-namespace Org.BouncyCastle.Bzip2
+namespace Org.BouncyCastle.Utilities.Bzip2
 {
 	/**
     * An input stream that decompresses from the BZip2 format (with the file
diff --git a/crypto/src/bzip2/CBZip2OutputStream.cs b/crypto/src/util/bzip2/CBZip2OutputStream.cs
index 38966efd4..b896f36c6 100644
--- a/crypto/src/bzip2/CBZip2OutputStream.cs
+++ b/crypto/src/util/bzip2/CBZip2OutputStream.cs
@@ -27,10 +27,9 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
-namespace Org.BouncyCastle.Bzip2
+namespace Org.BouncyCastle.Utilities.Bzip2
 {
 	/**
     * An output stream that compresses into the BZip2 format (with the file
@@ -421,39 +420,32 @@ namespace Org.BouncyCastle.Bzip2
 
         bool closed = false;
 
-//        protected void Finalize()
-//        {
-//            Close();
-//        }
-
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+        protected void Detach(bool disposing)
         {
             if (disposing)
             {
-                if (closed)
-                    return;
-
-                Finish();
-                closed = true;
-                Platform.Dispose(this.bsStream);
+                if (!closed)
+                {
+                    Finish();
+                    closed = true;
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (closed)
-                return;
 
-            Finish();
-
-            closed = true;
-            Platform.Dispose(this.bsStream);
-
-            base.Close();
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                if (!closed)
+                {
+                    Finish();
+                    closed = true;
+                    Platform.Dispose(this.bsStream);
+                }
+            }
+            base.Dispose(disposing);
         }
-#endif
 
         public void Finish()
         {
@@ -1605,4 +1597,23 @@ namespace Org.BouncyCastle.Bzip2
             return a;
         }
     }
+
+    public class CBZip2OutputStreamLeaveOpen
+        : CBZip2OutputStream
+    {
+        public CBZip2OutputStreamLeaveOpen(Stream outStream)
+            : base(outStream)
+        {
+        }
+
+        public CBZip2OutputStreamLeaveOpen(Stream outStream, int blockSize)
+            : base(outStream, blockSize)
+        {
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            Detach(disposing);
+        }
+    }
 }
diff --git a/crypto/src/bzip2/CRC.cs b/crypto/src/util/bzip2/CRC.cs
index 70d05e084..30c7e9c7d 100644
--- a/crypto/src/bzip2/CRC.cs
+++ b/crypto/src/util/bzip2/CRC.cs
@@ -22,12 +22,9 @@
  * great code.
  */
 
-using System;
 using System.Diagnostics;
 
-using Org.BouncyCastle.Utilities;
-
-namespace Org.BouncyCastle.Bzip2
+namespace Org.BouncyCastle.Utilities.Bzip2
 {
     /**
     * A simple class the hold and calculate the CRC for sanity checking
diff --git a/crypto/src/util/collections/CollectionUtilities.cs b/crypto/src/util/collections/CollectionUtilities.cs
index 41b558130..26b3f2a1d 100644
--- a/crypto/src/util/collections/CollectionUtilities.cs
+++ b/crypto/src/util/collections/CollectionUtilities.cs
@@ -32,7 +32,6 @@ namespace Org.BouncyCastle.Utilities.Collections
         }
 
         public static T GetValueOrKey<T>(IDictionary<T, T> d, T k)
-            where T : class
         {
             return d.TryGetValue(k, out var v) ? v : k;
         }
diff --git a/crypto/src/util/date/DateTimeObject.cs b/crypto/src/util/date/DateTimeObject.cs
deleted file mode 100644
index 793376b6d..000000000
--- a/crypto/src/util/date/DateTimeObject.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-
-namespace Org.BouncyCastle.Utilities.Date
-{
-	public sealed class DateTimeObject
-	{
-		private readonly DateTime dt;
-
-		public DateTimeObject(
-			DateTime dt)
-		{
-			this.dt = dt;
-		}
-
-		public DateTime Value
-		{
-			get { return dt; }
-		}
-
-		public override string ToString()
-		{
-			return dt.ToString();
-		}
-	}
-}
diff --git a/crypto/src/util/date/DateTimeUtilities.cs b/crypto/src/util/date/DateTimeUtilities.cs
index 72e5123a2..3660e29c2 100644
--- a/crypto/src/util/date/DateTimeUtilities.cs
+++ b/crypto/src/util/date/DateTimeUtilities.cs
@@ -2,38 +2,48 @@ using System;
 
 namespace Org.BouncyCastle.Utilities.Date
 {
-	public class DateTimeUtilities
+	public static class DateTimeUtilities
 	{
-		public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static readonly DateTime UnixEpoch = DateTime.UnixEpoch;
+#else
+        public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+#endif
 
-		private DateTimeUtilities()
-		{
-		}
+        public static readonly long MaxUnixMs =
+            (DateTime.MaxValue.Ticks - UnixEpoch.Ticks) / TimeSpan.TicksPerMillisecond;
+        public static readonly long MinUnixMs = 0L;
 
-		/// <summary>
-		/// Return the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC) for a given DateTime value.
-		/// </summary>
-		/// <param name="dateTime">A UTC DateTime value not before epoch.</param>
-		/// <returns>Number of whole milliseconds after epoch.</returns>
-		/// <exception cref="ArgumentException">'dateTime' is before epoch.</exception>
-		public static long DateTimeToUnixMs(
-			DateTime dateTime)
+        /// <summary>
+        /// Return the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC) for a given DateTime value.
+        /// </summary>
+        /// <remarks>The DateTime value will be converted to UTC (using <see cref="DateTime.ToUniversalTime"/> before
+        /// conversion.</remarks>
+        /// <param name="dateTime">A DateTime value not before the epoch.</param>
+        /// <returns>Number of whole milliseconds after epoch.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">'dateTime' is before the epoch.</exception>
+        public static long DateTimeToUnixMs(DateTime dateTime)
 		{
-			if (dateTime.CompareTo(UnixEpoch) < 0)
-				throw new ArgumentException("DateTime value may not be before the epoch", "dateTime");
+            DateTime utc = dateTime.ToUniversalTime();
+            if (utc.CompareTo(UnixEpoch) < 0)
+				throw new ArgumentOutOfRangeException(nameof(dateTime), "DateTime value may not be before the epoch");
 
-			return (dateTime.Ticks - UnixEpoch.Ticks) / TimeSpan.TicksPerMillisecond;
+			return (utc.Ticks - UnixEpoch.Ticks) / TimeSpan.TicksPerMillisecond;
 		}
 
-		/// <summary>
-		/// Create a DateTime value from the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC).
-		/// </summary>
-		/// <param name="unixMs">Number of milliseconds since the epoch.</param>
-		/// <returns>A UTC DateTime value</returns>
-		public static DateTime UnixMsToDateTime(
-			long unixMs)
+        /// <summary>
+        /// Create a UTC DateTime value from the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC).
+        /// </summary>
+        /// <param name="unixMs">Number of milliseconds since the epoch.</param>
+        /// <returns>A UTC DateTime value</returns>
+        /// <exception cref="ArgumentOutOfRangeException">'unixMs' is before 'MinUnixMs' or after 'MaxUnixMs'.
+        /// </exception>
+        public static DateTime UnixMsToDateTime(long unixMs)
 		{
-			return new DateTime(unixMs * TimeSpan.TicksPerMillisecond + UnixEpoch.Ticks, DateTimeKind.Utc);
+			if (unixMs < MinUnixMs || unixMs > MaxUnixMs)
+				throw new ArgumentOutOfRangeException(nameof(unixMs));
+
+            return new DateTime(unixMs * TimeSpan.TicksPerMillisecond + UnixEpoch.Ticks, DateTimeKind.Utc);
 		}
 
 		/// <summary>
diff --git a/crypto/src/util/encoders/Base64Encoder.cs b/crypto/src/util/encoders/Base64Encoder.cs
index 7ec79eaa0..6ba9e97a4 100644
--- a/crypto/src/util/encoders/Base64Encoder.cs
+++ b/crypto/src/util/encoders/Base64Encoder.cs
@@ -46,6 +46,9 @@ namespace Org.BouncyCastle.Utilities.Encoders
 
         public int Encode(byte[] inBuf, int inOff, int inLen, byte[] outBuf, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Encode(inBuf.AsSpan(inOff, inLen), outBuf.AsSpan(outOff));
+#else
             int inPos = inOff;
             int inEnd = inOff + inLen - 2;
             int outPos = outOff;
@@ -88,8 +91,57 @@ namespace Org.BouncyCastle.Utilities.Encoders
             }
 
             return outPos - outOff;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Encode(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int inPos = 0;
+            int inEnd = input.Length - 2;
+            int outPos = 0;
+
+            while (inPos < inEnd)
+            {
+                uint a1 = input[inPos++];
+                uint a2 = input[inPos++];
+                uint a3 = input[inPos++];
+
+                output[outPos++] = encodingTable[(a1 >> 2) & 0x3F];
+                output[outPos++] = encodingTable[((a1 << 4) | (a2 >> 4)) & 0x3F];
+                output[outPos++] = encodingTable[((a2 << 2) | (a3 >> 6)) & 0x3F];
+                output[outPos++] = encodingTable[a3 & 0x3F];
+            }
+
+            switch (input.Length - inPos)
+            {
+            case 1:
+            {
+                uint a1 = input[inPos++];
+
+                output[outPos++] = encodingTable[(a1 >> 2) & 0x3F];
+                output[outPos++] = encodingTable[(a1 << 4) & 0x3F];
+                output[outPos++] = padding;
+                output[outPos++] = padding;
+                break;
+            }
+            case 2:
+            {
+                uint a1 = input[inPos++];
+                uint a2 = input[inPos++];
+
+                output[outPos++] = encodingTable[(a1 >> 2) & 0x3F];
+                output[outPos++] = encodingTable[((a1 << 4) | (a2 >> 4)) & 0x3F];
+                output[outPos++] = encodingTable[(a2 << 2) & 0x3F];
+                output[outPos++] = padding;
+                break;
+            }
+            }
+
+            return outPos;
+        }
+#endif
+
         /**
         * encode the input data producing a base 64 output stream.
         *
@@ -97,6 +149,9 @@ namespace Org.BouncyCastle.Utilities.Encoders
         */
         public int Encode(byte[] buf, int off, int len, Stream outStream) 
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Encode(buf.AsSpan(off, len), outStream);
+#else
             if (len < 0)
                 return 0;
 
@@ -111,8 +166,25 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 remaining -= inLen;
             }
             return (len + 2) / 3 * 4;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Encode(ReadOnlySpan<byte> data, Stream outStream)
+        {
+            Span<byte> tmp = stackalloc byte[72];
+            int result = (data.Length + 2) / 3 * 4;
+            while (!data.IsEmpty)
+            {
+                int inLen = System.Math.Min(54, data.Length);
+                int outLen = Encode(data[..inLen], tmp);
+                outStream.Write(tmp[..outLen]);
+                data = data[inLen..];
+            }
+            return result;
+        }
+#endif
+
         private bool Ignore(char c)
         {
             return c == '\n' || c =='\r' || c == '\t' || c == ' ';
@@ -126,6 +198,9 @@ namespace Org.BouncyCastle.Utilities.Encoders
         */
         public int Decode(byte[] data, int off, int length, Stream outStream)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Decode(data.AsSpan(off, length), outStream);
+#else
             byte b1, b2, b3, b4;
             byte[] outBuffer = new byte[54];   // S/MIME standard
             int bufOff = 0;
@@ -140,10 +215,8 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 end--;
             }
 
-            int  i = off;
-            int  finish = end - 4;
-
-            i = NextI(data, i, finish);
+            int finish = end - 4;
+            int i = NextI(data, off, finish);
 
             while (i < finish)
             {
@@ -192,12 +265,84 @@ namespace Org.BouncyCastle.Utilities.Encoders
             outLen += DecodeLastBlock(outStream, (char)data[e0], (char)data[e1], (char)data[e2], (char)data[e3]);
 
             return outLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Decode(ReadOnlySpan<byte> data, Stream outStream)
+        {
+            byte b1, b2, b3, b4;
+            Span<byte> outBuffer = stackalloc byte[54];   // S/MIME standard
+            int bufOff = 0;
+            int outLen = 0;
+            int end = data.Length;
+
+            while (end > 0)
+            {
+                if (!Ignore((char)data[end - 1]))
+                    break;
+
+                end--;
+            }
+
+            int finish = end - 4;
+            int i = NextI(data, 0, finish);
+
+            while (i < finish)
+            {
+                b1 = decodingTable[data[i++]];
+
+                i = NextI(data, i, finish);
+
+                b2 = decodingTable[data[i++]];
+
+                i = NextI(data, i, finish);
+
+                b3 = decodingTable[data[i++]];
+
+                i = NextI(data, i, finish);
+
+                b4 = decodingTable[data[i++]];
+
+                if ((b1 | b2 | b3 | b4) >= 0x80)
+                    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);
+
+                if (bufOff == outBuffer.Length)
+                {
+                    outStream.Write(outBuffer);
+                    bufOff = 0;
+                }
+
+                outLen += 3;
+
+                i = NextI(data, i, finish);
+            }
+
+            if (bufOff > 0)
+            {
+                outStream.Write(outBuffer[..bufOff]);
+            }
+
+            int e0 = NextI(data, i, end);
+            int e1 = NextI(data, e0 + 1, end);
+            int e2 = NextI(data, e1 + 1, end);
+            int e3 = NextI(data, e2 + 1, end);
+
+            outLen += DecodeLastBlock(outStream, (char)data[e0], (char)data[e1], (char)data[e2], (char)data[e3]);
+
+            return outLen;
         }
+#endif
 
-        private int NextI(
-            byte[]	data,
-            int		i,
-            int		finish)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int NextI(ReadOnlySpan<byte> data, int i, int finish)
+#else
+        private int NextI(byte[] data, int i, int finish)
+#endif
         {
             while ((i < finish) && Ignore((char)data[i]))
             {
@@ -232,10 +377,11 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 end--;
             }
 
-            int  i = 0;
-            int  finish = end - 4;
-
-            i = NextI(data, i, finish);
+            int finish = end - 4;
+            int i = NextI(data, 0, finish);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> buf = stackalloc byte[3];
+#endif
 
             while (i < finish)
             {
@@ -256,9 +402,16 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 if ((b1 | b2 | b3 | b4) >= 0x80)
                     throw new IOException("invalid characters encountered in base64 data");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                buf[0] = (byte)((b1 << 2) | (b2 >> 4));
+                buf[1] = (byte)((b2 << 4) | (b3 >> 2));
+                buf[2] = (byte)((b3 << 6) | b4);
+                outStream.Write(buf);
+#else
                 outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4)));
                 outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2)));
                 outStream.WriteByte((byte)((b3 << 6) | b4));
+#endif
 
                 length += 3;
 
@@ -302,8 +455,16 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 if ((b1 | b2 | b3) >= 0x80)
                     throw new IOException("invalid characters encountered at end of base64 data");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<byte> buf = stackalloc byte[2] {
+                    (byte)((b1 << 2) | (b2 >> 4)),
+                    (byte)((b2 << 4) | (b3 >> 2)),
+                };
+                outStream.Write(buf);
+#else
                 outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4)));
                 outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2)));
+#endif
 
                 return 2;
             }
@@ -317,9 +478,18 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 if ((b1 | b2 | b3 | b4) >= 0x80)
                     throw new IOException("invalid characters encountered at end of base64 data");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Span<byte> buf = stackalloc byte[3] {
+                    (byte)((b1 << 2) | (b2 >> 4)),
+                    (byte)((b2 << 4) | (b3 >> 2)),
+                    (byte)((b3 << 6) | b4),
+                };
+                outStream.Write(buf);
+#else
                 outStream.WriteByte((byte)((b1 << 2) | (b2 >> 4)));
                 outStream.WriteByte((byte)((b2 << 4) | (b3 >> 2)));
                 outStream.WriteByte((byte)((b3 << 6) | b4));
+#endif
 
                 return 3;
             }
diff --git a/crypto/src/util/encoders/HexEncoder.cs b/crypto/src/util/encoders/HexEncoder.cs
index bf68aff9d..6ccb184d9 100644
--- a/crypto/src/util/encoders/HexEncoder.cs
+++ b/crypto/src/util/encoders/HexEncoder.cs
@@ -1,3 +1,4 @@
+using Org.BouncyCastle.Crypto;
 using System;
 using System.IO;
 
@@ -41,6 +42,9 @@ namespace Org.BouncyCastle.Utilities.Encoders
 
         public int Encode(byte[] inBuf, int inOff, int inLen, byte[] outBuf, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Encode(inBuf.AsSpan(inOff, inLen), outBuf.AsSpan(outOff));
+#else
             int inPos = inOff;
             int inEnd = inOff + inLen;
             int outPos = outOff;
@@ -54,8 +58,28 @@ namespace Org.BouncyCastle.Utilities.Encoders
             }
 
             return outPos - outOff;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Encode(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int inPos = 0;
+            int inEnd = input.Length;
+            int outPos = 0;
+
+            while (inPos < inEnd)
+            {
+                uint b = input[inPos++];
+
+                output[outPos++] = encodingTable[b >> 4];
+                output[outPos++] = encodingTable[b & 0xF];
+            }
+
+            return outPos;
+        }
+#endif
+
         /**
         * encode the input data producing a Hex output stream.
         *
@@ -63,6 +87,9 @@ namespace Org.BouncyCastle.Utilities.Encoders
         */
         public int Encode(byte[] buf, int off, int len, Stream outStream)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Encode(buf.AsSpan(off, len), outStream);
+#else
             if (len < 0)
                 return 0;
 
@@ -77,8 +104,25 @@ namespace Org.BouncyCastle.Utilities.Encoders
                 remaining -= inLen;
             }
             return len * 2;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Encode(ReadOnlySpan<byte> data, Stream outStream)
+        {
+            Span<byte> tmp = stackalloc byte[72];
+            int result = data.Length * 2;
+            while (!data.IsEmpty)
+            {
+                int inLen = System.Math.Min(36, data.Length);
+                int outLen = Encode(data[..inLen], tmp);
+                outStream.Write(tmp[..outLen]);
+                data = data[inLen..];
+            }
+            return result;
+        }
+#endif
+
         private static bool Ignore(char c)
         {
             return c == '\n' || c =='\r' || c == '\t' || c == ' ';
@@ -90,12 +134,11 @@ namespace Org.BouncyCastle.Utilities.Encoders
         *
         * @return the number of bytes produced.
         */
-        public int Decode(
-            byte[]	data,
-            int		off,
-            int		length,
-            Stream	outStream)
+        public int Decode(byte[] data, int off, int length, Stream outStream)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Decode(data.AsSpan(off, length), outStream);
+#else
             byte b1, b2;
             int outLen = 0;
             byte[] buf = new byte[36];
@@ -147,7 +190,65 @@ namespace Org.BouncyCastle.Utilities.Encoders
             }
 
             return outLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Decode(ReadOnlySpan<byte> data, Stream outStream)
+        {
+            byte b1, b2;
+            int outLen = 0;
+            Span<byte> buf = stackalloc byte[36];
+            int bufOff = 0;
+            int end = data.Length;
+
+            while (end > 0)
+            {
+                if (!Ignore((char)data[end - 1]))
+                    break;
+
+                end--;
+            }
+
+            int i = 0;
+            while (i < end)
+            {
+                while (i < end && Ignore((char)data[i]))
+                {
+                    i++;
+                }
+
+                b1 = decodingTable[data[i++]];
+
+                while (i < end && Ignore((char)data[i]))
+                {
+                    i++;
+                }
+
+                b2 = decodingTable[data[i++]];
+
+                if ((b1 | b2) >= 0x80)
+                    throw new IOException("invalid characters encountered in Hex data");
+
+                buf[bufOff++] = (byte)((b1 << 4) | b2);
+
+                if (bufOff == buf.Length)
+                {
+                    outStream.Write(buf);
+                    bufOff = 0;
+                }
+
+                outLen++;
+            }
+
+            if (bufOff > 0)
+            {
+                outStream.Write(buf[..bufOff]);
+            }
+
+            return outLen;
         }
+#endif
 
         /**
         * decode the Hex encoded string data writing it to the given output stream,
@@ -155,13 +256,15 @@ namespace Org.BouncyCastle.Utilities.Encoders
         *
         * @return the number of bytes produced.
         */
-        public int DecodeString(
-            string	data,
-            Stream	outStream)
+        public int DecodeString(string data, Stream outStream)
         {
             byte b1, b2;
             int length = 0;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> buf = stackalloc byte[36];
+#else
             byte[] buf = new byte[36];
+#endif
             int bufOff = 0;
             int end = data.Length;
 
@@ -197,7 +300,11 @@ namespace Org.BouncyCastle.Utilities.Encoders
 
                 if (bufOff == buf.Length)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    outStream.Write(buf);
+#else
                     outStream.Write(buf, 0, bufOff);
+#endif
                     bufOff = 0;
                 }
 
@@ -206,7 +313,11 @@ namespace Org.BouncyCastle.Utilities.Encoders
 
             if (bufOff > 0)
             {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                outStream.Write(buf[..bufOff]);
+#else
                 outStream.Write(buf, 0, bufOff);
+#endif
             }
 
             return length;
diff --git a/crypto/src/util/encoders/IEncoder.cs b/crypto/src/util/encoders/IEncoder.cs
index 5887d5daa..33a2cde18 100644
--- a/crypto/src/util/encoders/IEncoder.cs
+++ b/crypto/src/util/encoders/IEncoder.cs
@@ -11,8 +11,16 @@ namespace Org.BouncyCastle.Utilities.Encoders
 	{
 		int Encode(byte[] data, int off, int length, Stream outStream);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		int Encode(ReadOnlySpan<byte> data, Stream outStream);
+#endif
+
 		int Decode(byte[] data, int off, int length, Stream outStream);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		int Decode(ReadOnlySpan<byte> data, Stream outStream);
+#endif
+
 		int DecodeString(string data, Stream outStream);
 	}
 }
diff --git a/crypto/src/util/io/BaseInputStream.cs b/crypto/src/util/io/BaseInputStream.cs
index ebe256632..eaaf9556d 100644
--- a/crypto/src/util/io/BaseInputStream.cs
+++ b/crypto/src/util/io/BaseInputStream.cs
@@ -45,5 +45,9 @@ namespace Org.BouncyCastle.Utilities.IO
         public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
         public sealed override void SetLength(long value) { throw new NotSupportedException(); }
         public sealed override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer) { throw new NotSupportedException(); }
+#endif
     }
 }
diff --git a/crypto/src/util/io/BaseOutputStream.cs b/crypto/src/util/io/BaseOutputStream.cs
index d9a5b92d6..0fc8e9681 100644
--- a/crypto/src/util/io/BaseOutputStream.cs
+++ b/crypto/src/util/io/BaseOutputStream.cs
@@ -10,6 +10,9 @@ namespace Org.BouncyCastle.Utilities.IO
         public sealed override bool CanSeek { get { return false; } }
         public sealed override bool CanWrite { get { return true; } }
 
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize) { throw new NotSupportedException(); }
+#endif
         public override void Flush() {}
         public sealed override long Length { get { throw new NotSupportedException(); } }
         public sealed override long Position
@@ -18,6 +21,9 @@ namespace Org.BouncyCastle.Utilities.IO
             set { throw new NotSupportedException(); }
         }
         public sealed override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public sealed override int Read(Span<byte> buffer) { throw new NotSupportedException(); }
+#endif
         public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
         public sealed override void SetLength(long value) { throw new NotSupportedException(); }
 
@@ -35,10 +41,5 @@ namespace Org.BouncyCastle.Utilities.IO
         {
             Write(buffer, 0, buffer.Length);
         }
-
-        public override void WriteByte(byte value)
-        {
-            Write(new byte[]{ value }, 0, 1);
-        }
     }
 }
diff --git a/crypto/src/util/io/BinaryReaders.cs b/crypto/src/util/io/BinaryReaders.cs
new file mode 100644
index 000000000..c5f99a712
--- /dev/null
+++ b/crypto/src/util/io/BinaryReaders.cs
@@ -0,0 +1,94 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Utilities.IO
+{
+    public static class BinaryReaders
+    {
+        public static byte[] ReadBytesFully(BinaryReader binaryReader, int count)
+        {
+            byte[] bytes = binaryReader.ReadBytes(count);
+            if (bytes == null || bytes.Length != count)
+                throw new EndOfStreamException();
+            return bytes;
+        }
+
+        public static short ReadInt16BigEndian(BinaryReader binaryReader)
+        {
+            short n = binaryReader.ReadInt16();
+            return BitConverter.IsLittleEndian ? Shorts.ReverseBytes(n) : n;
+        }
+
+        public static short ReadInt16LittleEndian(BinaryReader binaryReader)
+        {
+            short n = binaryReader.ReadInt16();
+            return BitConverter.IsLittleEndian ? n : Shorts.ReverseBytes(n);
+        }
+
+        public static int ReadInt32BigEndian(BinaryReader binaryReader)
+        {
+            int n = binaryReader.ReadInt32();
+            return BitConverter.IsLittleEndian ? Integers.ReverseBytes(n) : n;
+        }
+
+        public static int ReadInt32LittleEndian(BinaryReader binaryReader)
+        {
+            int n = binaryReader.ReadInt32();
+            return BitConverter.IsLittleEndian ? n : Integers.ReverseBytes(n);
+        }
+
+        public static long ReadInt64BigEndian(BinaryReader binaryReader)
+        {
+            long n = binaryReader.ReadInt64();
+            return BitConverter.IsLittleEndian ? Longs.ReverseBytes(n) : n;
+        }
+
+        public static long ReadInt64LittleEndian(BinaryReader binaryReader)
+        {
+            long n = binaryReader.ReadInt64();
+            return BitConverter.IsLittleEndian ? n : Longs.ReverseBytes(n);
+        }
+
+        [CLSCompliant(false)]
+        public static ushort ReadUInt16BigEndian(BinaryReader binaryReader)
+        {
+            ushort n = binaryReader.ReadUInt16();
+            return BitConverter.IsLittleEndian ? Shorts.ReverseBytes(n) : n;
+        }
+
+        [CLSCompliant(false)]
+        public static ushort ReadUInt16LittleEndian(BinaryReader binaryReader)
+        {
+            ushort n = binaryReader.ReadUInt16();
+            return BitConverter.IsLittleEndian ? n : Shorts.ReverseBytes(n);
+        }
+
+        [CLSCompliant(false)]
+        public static uint ReadUInt32BigEndian(BinaryReader binaryReader)
+        {
+            uint n = binaryReader.ReadUInt32();
+            return BitConverter.IsLittleEndian ? Integers.ReverseBytes(n) : n;
+        }
+
+        [CLSCompliant(false)]
+        public static uint ReadUInt32LittleEndian(BinaryReader binaryReader)
+        {
+            uint n = binaryReader.ReadUInt32();
+            return BitConverter.IsLittleEndian ? n : Integers.ReverseBytes(n);
+        }
+
+        [CLSCompliant(false)]
+        public static ulong ReadUInt64BigEndian(BinaryReader binaryReader)
+        {
+            ulong n = binaryReader.ReadUInt64();
+            return BitConverter.IsLittleEndian ? Longs.ReverseBytes(n) : n;
+        }
+
+        [CLSCompliant(false)]
+        public static ulong ReadUInt64LittleEndian(BinaryReader binaryReader)
+        {
+            ulong n = binaryReader.ReadUInt64();
+            return BitConverter.IsLittleEndian ? n : Longs.ReverseBytes(n);
+        }
+    }
+}
diff --git a/crypto/src/util/io/BinaryWriters.cs b/crypto/src/util/io/BinaryWriters.cs
new file mode 100644
index 000000000..6650dcda5
--- /dev/null
+++ b/crypto/src/util/io/BinaryWriters.cs
@@ -0,0 +1,86 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Utilities.IO
+{
+    public static class BinaryWriters
+    {
+        public static void WriteInt16BigEndian(BinaryWriter binaryWriter, short n)
+        {
+            short bigEndian = BitConverter.IsLittleEndian ? Shorts.ReverseBytes(n) : n;
+            binaryWriter.Write(bigEndian);
+        }
+
+        public static void WriteInt16LittleEndian(BinaryWriter binaryWriter, short n)
+        {
+            short littleEndian = BitConverter.IsLittleEndian ? n : Shorts.ReverseBytes(n);
+            binaryWriter.Write(littleEndian);
+        }
+
+        public static void WriteInt32BigEndian(BinaryWriter binaryWriter, int n)
+        {
+            int bigEndian = BitConverter.IsLittleEndian ? Integers.ReverseBytes(n) : n;
+            binaryWriter.Write(bigEndian);
+        }
+
+        public static void WriteInt32LittleEndian(BinaryWriter binaryWriter, int n)
+        {
+            int littleEndian = BitConverter.IsLittleEndian ? n : Integers.ReverseBytes(n);
+            binaryWriter.Write(littleEndian);
+        }
+
+        public static void WriteInt64BigEndian(BinaryWriter binaryWriter, long n)
+        {
+            long bigEndian = BitConverter.IsLittleEndian ? Longs.ReverseBytes(n) : n;
+            binaryWriter.Write(bigEndian);
+        }
+
+        public static void WriteInt64LittleEndian(BinaryWriter binaryWriter, long n)
+        {
+            long littleEndian = BitConverter.IsLittleEndian ? n : Longs.ReverseBytes(n);
+            binaryWriter.Write(littleEndian);
+        }
+
+        [CLSCompliant(false)]
+        public static void WriteUInt16BigEndian(BinaryWriter binaryWriter, ushort n)
+        {
+            ushort bigEndian = BitConverter.IsLittleEndian ? Shorts.ReverseBytes(n) : n;
+            binaryWriter.Write(bigEndian);
+        }
+
+        [CLSCompliant(false)]
+        public static void WriteUInt16LittleEndian(BinaryWriter binaryWriter, ushort n)
+        {
+            ushort littleEndian = BitConverter.IsLittleEndian ? n : Shorts.ReverseBytes(n);
+            binaryWriter.Write(littleEndian);
+        }
+
+        [CLSCompliant(false)]
+        public static void WriteUInt32BigEndian(BinaryWriter binaryWriter, uint n)
+        {
+            uint bigEndian = BitConverter.IsLittleEndian ? Integers.ReverseBytes(n) : n;
+            binaryWriter.Write(bigEndian);
+        }
+
+        [CLSCompliant(false)]
+        public static void WriteUInt32LittleEndian(BinaryWriter binaryWriter, uint n)
+        {
+            uint littleEndian = BitConverter.IsLittleEndian ? n : Integers.ReverseBytes(n);
+            binaryWriter.Write(littleEndian);
+        }
+
+        [CLSCompliant(false)]
+        public static void WriteUInt64BigEndian(BinaryWriter binaryWriter, ulong n)
+        {
+            ulong bigEndian = BitConverter.IsLittleEndian ? Longs.ReverseBytes(n) : n;
+            binaryWriter.Write(bigEndian);
+        }
+
+        [CLSCompliant(false)]
+        public static void WriteUInt64LittleEndian(BinaryWriter binaryWriter, ulong n)
+        {
+            ulong littleEndian = BitConverter.IsLittleEndian ? n : Longs.ReverseBytes(n);
+            binaryWriter.Write(littleEndian);
+        }
+    }
+}
diff --git a/crypto/src/util/io/FilterStream.cs b/crypto/src/util/io/FilterStream.cs
index 0db82ec88..38077edd2 100644
--- a/crypto/src/util/io/FilterStream.cs
+++ b/crypto/src/util/io/FilterStream.cs
@@ -10,19 +10,7 @@ namespace Org.BouncyCastle.Utilities.IO
 
         public FilterStream(Stream s)
         {
-            if (s == null)
-                throw new ArgumentNullException(nameof(s));
-
-            this.s = s;
-        }
-        protected override void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                s.Dispose();
-            }
-
-            base.Dispose(disposing);
+            this.s = s ?? throw new ArgumentNullException(nameof(s));
         }
         public override bool CanRead
         {
@@ -36,6 +24,16 @@ namespace Org.BouncyCastle.Utilities.IO
         {
             get { return s.CanWrite; }
         }
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
+        {
+            s.CopyTo(destination, bufferSize);
+        }
+#endif
+        public override void Flush()
+        {
+            s.Flush();
+        }
         public override long Length
         {
             get { return s.Length; }
@@ -45,33 +43,54 @@ namespace Org.BouncyCastle.Utilities.IO
             get { return s.Position; }
             set { s.Position = value; }
         }
-        public override void Flush()
+        public override int Read(byte[] buffer, int offset, int count)
         {
-            s.Flush();
+            return s.Read(buffer, offset, count);
         }
-        public override long Seek(long offset, SeekOrigin origin)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
         {
-            return s.Seek(offset, origin);
+            return s.Read(buffer);
         }
-        public override void SetLength(long value)
+#endif
+        public override int ReadByte()
         {
-            s.SetLength(value);
+            return s.ReadByte();
         }
-        public override int Read(byte[] buffer, int offset, int count)
+        public override long Seek(long offset, SeekOrigin origin)
         {
-            return s.Read(buffer, offset, count);
+            return s.Seek(offset, origin);
         }
-        public override int ReadByte()
+        public override void SetLength(long value)
         {
-            return s.ReadByte();
+            s.SetLength(value);
         }
         public override void Write(byte[] buffer, int offset, int count)
         {
             s.Write(buffer, offset, count);
         }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            s.Write(buffer);
+        }
+#endif
         public override void WriteByte(byte value)
         {
             s.WriteByte(value);
         }
+        protected void Detach(bool disposing)
+        {
+            base.Dispose(disposing);
+        }
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                s.Dispose();
+            }
+
+            base.Dispose(disposing);
+        }
     }
 }
diff --git a/crypto/src/util/io/LimitedInputStream.cs b/crypto/src/util/io/LimitedInputStream.cs
new file mode 100644
index 000000000..d6616eff5
--- /dev/null
+++ b/crypto/src/util/io/LimitedInputStream.cs
@@ -0,0 +1,56 @@
+using Org.BouncyCastle.Utilities.Zlib;
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Utilities.IO
+{
+    internal class LimitedInputStream
+        : BaseInputStream
+    {
+        private readonly Stream m_stream;
+        private long m_limit;
+
+        internal LimitedInputStream(Stream stream, long limit)
+        {
+            this.m_stream = stream;
+            this.m_limit = limit;
+        }
+
+        internal long CurrentLimit => m_limit;
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            int numRead = m_stream.Read(buffer, offset, count);
+            if (numRead > 0)
+            {
+                if ((m_limit -= numRead) < 0)
+                    throw new StreamOverflowException("Data Overflow");
+            }
+            return numRead;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int numRead = m_stream.Read(buffer);
+            if (numRead > 0)
+            {
+                if ((m_limit -= numRead) < 0)
+                    throw new StreamOverflowException("Data Overflow");
+            }
+            return numRead;
+        }
+#endif
+
+        public override int ReadByte()
+        {
+            int b = m_stream.ReadByte();
+            if (b >= 0)
+            {
+                if (--m_limit < 0)
+                    throw new StreamOverflowException("Data Overflow");
+            }
+            return b;
+        }
+    }
+}
diff --git a/crypto/src/util/io/NullOutputStream.cs b/crypto/src/util/io/NullOutputStream.cs
deleted file mode 100644
index c435549d2..000000000
--- a/crypto/src/util/io/NullOutputStream.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace Org.BouncyCastle.Utilities.IO
-{
-	internal class NullOutputStream
-		: BaseOutputStream
-	{
-		public override void Write(byte[] buffer, int offset, int count)
-		{
-			Streams.ValidateBufferArguments(buffer, offset, count);
-		}
-
-		public override void WriteByte(byte value)
-		{
-		}
-	}
-}
diff --git a/crypto/src/util/io/PushbackStream.cs b/crypto/src/util/io/PushbackStream.cs
index 2ceb64361..15ba70501 100644
--- a/crypto/src/util/io/PushbackStream.cs
+++ b/crypto/src/util/io/PushbackStream.cs
@@ -13,7 +13,20 @@ namespace Org.BouncyCastle.Utilities.IO
 		{
 		}
 
-		public override int Read(byte[] buffer, int offset, int count)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
+        {
+			if (m_buf != -1)
+			{
+				destination.WriteByte((byte)m_buf);
+                m_buf = -1;
+            }
+
+            s.CopyTo(destination, bufferSize);
+        }
+#endif
+
+        public override int Read(byte[] buffer, int offset, int count)
 		{
 			Streams.ValidateBufferArguments(buffer, offset, count);
 
@@ -27,10 +40,27 @@ namespace Org.BouncyCastle.Utilities.IO
 				return 1;
 			}
 
-			return base.Read(buffer, offset, count);
+			return s.Read(buffer, offset, count);
 		}
 
-		public override int ReadByte()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+			if (m_buf != -1)
+			{
+                if (buffer.IsEmpty)
+                    return 0;
+
+                buffer[0] = (byte)m_buf;
+                m_buf = -1;
+                return 1;
+            }
+
+            return s.Read(buffer);
+        }
+#endif
+
+        public override int ReadByte()
 		{
 			if (m_buf != -1)
 			{
diff --git a/crypto/src/util/io/Streams.cs b/crypto/src/util/io/Streams.cs
index e1da47fcd..da8f01068 100644
--- a/crypto/src/util/io/Streams.cs
+++ b/crypto/src/util/io/Streams.cs
@@ -3,20 +3,13 @@ using System.IO;
 
 namespace Org.BouncyCastle.Utilities.IO
 {
-	public sealed class Streams
+	public static class Streams
 	{
 		private const int BufferSize = 4096;
 
-		private Streams()
-		{
-		}
-
 		public static void Drain(Stream inStr)
 		{
-			byte[] bs = new byte[BufferSize];
-			while (inStr.Read(bs, 0, bs.Length) > 0)
-			{
-			}
+			inStr.CopyTo(Stream.Null, BufferSize);
 		}
 
         /// <summary>Write the full contents of inStr to the destination stream outStr.</summary>
@@ -25,7 +18,7 @@ namespace Org.BouncyCastle.Utilities.IO
         /// <exception cref="IOException">In case of IO failure.</exception>
         public static void PipeAll(Stream inStr, Stream outStr)
 		{
-            PipeAll(inStr, outStr, BufferSize);
+			inStr.CopyTo(outStr, BufferSize);
         }
 
         /// <summary>Write the full contents of inStr to the destination stream outStr.</summary>
@@ -35,12 +28,7 @@ namespace Org.BouncyCastle.Utilities.IO
         /// <exception cref="IOException">In case of IO failure.</exception>
         public static void PipeAll(Stream inStr, Stream outStr, int bufferSize)
         {
-            byte[] bs = new byte[bufferSize];
-            int numRead;
-			while ((numRead = inStr.Read(bs, 0, bs.Length)) > 0)
-			{
-				outStr.Write(bs, 0, numRead);
-			}
+            inStr.CopyTo(outStr, bufferSize);
 		}
 
 		/// <summary>
@@ -60,17 +48,9 @@ namespace Org.BouncyCastle.Utilities.IO
 		/// <exception cref="IOException"></exception>
 		public static long PipeAllLimited(Stream inStr, long limit, Stream outStr)
 		{
-			byte[] bs = new byte[BufferSize];
-			long total = 0;
-			int numRead;
-			while ((numRead = inStr.Read(bs, 0, bs.Length)) > 0)
-			{
-                if ((limit - total) < numRead)
-					throw new StreamOverflowException("Data Overflow");
-                total += numRead;
-                outStr.Write(bs, 0, numRead);
-			}
-			return total;
+			var limited = new LimitedInputStream(inStr, limit);
+            limited.CopyTo(outStr, BufferSize);
+			return limit - limited.CurrentLimit;
 		}
 
 		public static byte[] ReadAll(Stream inStr)
@@ -80,7 +60,12 @@ namespace Org.BouncyCastle.Utilities.IO
 			return buf.ToArray();
 		}
 
-		public static byte[] ReadAllLimited(Stream inStr, int limit)
+        public static byte[] ReadAll(MemoryStream inStr)
+        {
+			return inStr.ToArray();
+        }
+
+        public static byte[] ReadAllLimited(Stream inStr, int limit)
 		{
 			MemoryStream buf = new MemoryStream();
 			PipeAllLimited(inStr, limit, buf);
@@ -105,7 +90,22 @@ namespace Org.BouncyCastle.Utilities.IO
 			return totalRead;
 		}
 
-		public static void ValidateBufferArguments(byte[] buffer, int offset, int count)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int ReadFully(Stream inStr, Span<byte> buffer)
+        {
+            int totalRead = 0;
+            while (totalRead < buffer.Length)
+            {
+                int numRead = inStr.Read(buffer[totalRead..]);
+                if (numRead < 1)
+                    break;
+                totalRead += numRead;
+            }
+            return totalRead;
+        }
+#endif
+
+        public static void ValidateBufferArguments(byte[] buffer, int offset, int count)
         {
 			if (buffer == null)
 				throw new ArgumentNullException("buffer");
@@ -120,6 +120,14 @@ namespace Org.BouncyCastle.Utilities.IO
         /// <exception cref="IOException"></exception>
         public static int WriteBufTo(MemoryStream buf, byte[] output, int offset)
         {
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (buf.TryGetBuffer(out var buffer))
+            {
+				buffer.CopyTo(output, offset);
+				return buffer.Count;
+            }
+#endif
+
 			int size = Convert.ToInt32(buf.Length);
             buf.WriteTo(new MemoryStream(output, offset, size));
             return size;
diff --git a/crypto/src/util/io/TeeInputStream.cs b/crypto/src/util/io/TeeInputStream.cs
index 73ea8fed0..3d45bb4f1 100644
--- a/crypto/src/util/io/TeeInputStream.cs
+++ b/crypto/src/util/io/TeeInputStream.cs
@@ -18,7 +18,6 @@ namespace Org.BouncyCastle.Utilities.IO
 			this.tee = tee;
 		}
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -28,14 +27,6 @@ namespace Org.BouncyCastle.Utilities.IO
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(input);
-            Platform.Dispose(tee);
-            base.Close();
-		}
-#endif
 
         public override int Read(byte[] buffer, int offset, int count)
 		{
@@ -49,7 +40,21 @@ namespace Org.BouncyCastle.Utilities.IO
 			return i;
 		}
 
-		public override int ReadByte()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int i = input.Read(buffer);
+
+            if (i > 0)
+            {
+				tee.Write(buffer[..i]);
+            }
+
+            return i;
+        }
+#endif
+
+        public override int ReadByte()
 		{
 			int i = input.ReadByte();
 
diff --git a/crypto/src/util/io/TeeOutputStream.cs b/crypto/src/util/io/TeeOutputStream.cs
index 5f447b18b..fc213ae55 100644
--- a/crypto/src/util/io/TeeOutputStream.cs
+++ b/crypto/src/util/io/TeeOutputStream.cs
@@ -18,7 +18,6 @@ namespace Org.BouncyCastle.Utilities.IO
 			this.tee = tee;
 		}
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -28,14 +27,6 @@ namespace Org.BouncyCastle.Utilities.IO
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(output);
-            Platform.Dispose(tee);
-            base.Close();
-		}
-#endif
 
         public override void Write(byte[] buffer, int offset, int count)
 		{
@@ -43,7 +34,15 @@ namespace Org.BouncyCastle.Utilities.IO
 			tee.Write(buffer, offset, count);
 		}
 
-		public override void WriteByte(byte value)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            output.Write(buffer);
+            tee.Write(buffer);
+        }
+#endif
+
+        public override void WriteByte(byte value)
 		{
 			output.WriteByte(value);
 			tee.WriteByte(value);
diff --git a/crypto/src/util/io/compression/Bzip2.cs b/crypto/src/util/io/compression/Bzip2.cs
new file mode 100644
index 000000000..72b006dc9
--- /dev/null
+++ b/crypto/src/util/io/compression/Bzip2.cs
@@ -0,0 +1,21 @@
+using System.IO;
+
+namespace Org.BouncyCastle.Utilities.IO.Compression
+{
+    using Impl = Utilities.Bzip2;
+
+    internal static class Bzip2
+    {
+        internal static Stream CompressOutput(Stream stream, bool leaveOpen = false)
+        {
+            return leaveOpen
+                ?   new Impl.CBZip2OutputStreamLeaveOpen(stream)
+                :   new Impl.CBZip2OutputStream(stream);
+        }
+
+        internal static Stream DecompressInput(Stream stream)
+        {
+            return new Impl.CBZip2InputStream(stream);
+        }
+    }
+}
diff --git a/crypto/src/util/io/compression/ZLib.cs b/crypto/src/util/io/compression/ZLib.cs
new file mode 100644
index 000000000..1254da012
--- /dev/null
+++ b/crypto/src/util/io/compression/ZLib.cs
@@ -0,0 +1,46 @@
+using System.IO;
+
+#if NET6_0_OR_GREATER
+using System.IO.Compression;
+#else
+using Org.BouncyCastle.Utilities.Zlib;
+#endif
+
+namespace Org.BouncyCastle.Utilities.IO.Compression
+{
+    internal static class ZLib
+    {
+        internal static Stream CompressOutput(Stream stream, int zlibCompressionLevel, bool leaveOpen = false)
+        {
+#if NET6_0_OR_GREATER
+            return new ZLibStream(stream, GetCompressionLevel(zlibCompressionLevel), leaveOpen);
+#else
+            return leaveOpen
+                ?   new ZOutputStreamLeaveOpen(stream, zlibCompressionLevel, false)
+                :   new ZOutputStream(stream, zlibCompressionLevel, false);
+#endif
+        }
+
+        internal static Stream DecompressInput(Stream stream)
+        {
+#if NET6_0_OR_GREATER
+            return new ZLibStream(stream, CompressionMode.Decompress, leaveOpen: false);
+#else
+            return new ZInputStream(stream);
+#endif
+        }
+
+#if NET6_0_OR_GREATER
+        internal static CompressionLevel GetCompressionLevel(int zlibCompressionLevel)
+        {
+            return zlibCompressionLevel switch
+            {
+                0           => CompressionLevel.NoCompression,
+                1 or 2 or 3 => CompressionLevel.Fastest,
+                7 or 8 or 9 => CompressionLevel.SmallestSize,
+                _           => CompressionLevel.Optimal,
+            };
+        }
+#endif
+    }
+}
diff --git a/crypto/src/util/io/compression/Zip.cs b/crypto/src/util/io/compression/Zip.cs
new file mode 100644
index 000000000..f2773d63b
--- /dev/null
+++ b/crypto/src/util/io/compression/Zip.cs
@@ -0,0 +1,33 @@
+using System.IO;
+
+#if NET6_0_OR_GREATER
+using System.IO.Compression;
+#else
+using Org.BouncyCastle.Utilities.Zlib;
+#endif
+
+namespace Org.BouncyCastle.Utilities.IO.Compression
+{
+    internal static class Zip
+    {
+        internal static Stream CompressOutput(Stream stream, int zlibCompressionLevel, bool leaveOpen = false)
+        {
+#if NET6_0_OR_GREATER
+            return new DeflateStream(stream, ZLib.GetCompressionLevel(zlibCompressionLevel), leaveOpen);
+#else
+            return leaveOpen
+                ?   new ZOutputStreamLeaveOpen(stream, zlibCompressionLevel, true)
+                :   new ZOutputStream(stream, zlibCompressionLevel, true);
+#endif
+        }
+
+        internal static Stream DecompressInput(Stream stream)
+        {
+#if NET6_0_OR_GREATER
+            return new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: false);
+#else
+            return new ZInputStream(stream, true);
+#endif
+        }
+    }
+}
diff --git a/crypto/src/util/zlib/ZInputStream.cs b/crypto/src/util/zlib/ZInputStream.cs
index 3e6fcc1be..0433a0182 100644
--- a/crypto/src/util/zlib/ZInputStream.cs
+++ b/crypto/src/util/zlib/ZInputStream.cs
@@ -116,34 +116,18 @@ namespace Org.BouncyCastle.Utilities.Zlib
             this.z.avail_in = 0;
         }
 
-        /*public int available() throws IOException {
-		return inf.finished() ? 0 : 1;
-		}*/
-
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
             {
-			    if (closed)
-                    return;
-
-                closed = true;
-                Platform.Dispose(input);
+			    if (!closed)
+                {
+                    closed = true;
+                    Platform.Dispose(input);
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (closed)
-                return;
-
-            closed = true;
-            Platform.Dispose(input);
-            base.Close();
-        }
-#endif
 
         public virtual int FlushMode
         {
diff --git a/crypto/src/util/zlib/ZOutputStream.cs b/crypto/src/util/zlib/ZOutputStream.cs
index dcb93f97b..51a5050dd 100644
--- a/crypto/src/util/zlib/ZOutputStream.cs
+++ b/crypto/src/util/zlib/ZOutputStream.cs
@@ -109,49 +109,61 @@ namespace Org.BouncyCastle.Utilities.Zlib
             this.z.deflateInit(level, nowrap);
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+        protected void Detach(bool disposing)
         {
             if (disposing)
             {
-			    if (closed)
-				    return;
-
-                DoClose();
+                if (!closed)
+                {
+                    try
+                    {
+                        try
+                        {
+                            Finish();
+                        }
+                        catch (IOException)
+                        {
+                            // Ignore
+                        }
+                    }
+                    finally
+                    {
+                        this.closed = true;
+                        End();
+                        output = null;
+                    }
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (closed)
-                return;
-
-            DoClose();
-            base.Close();
-        }
-#endif
 
-        private void DoClose()
+        protected override void Dispose(bool disposing)
         {
-            try
+            if (disposing)
             {
-                try
-                {
-                    Finish();
-                }
-                catch (IOException)
+			    if (!closed)
                 {
-                    // Ignore
+                    try
+                    {
+                        try
+                        {
+                            Finish();
+                        }
+                        catch (IOException)
+                        {
+                            // Ignore
+                        }
+                    }
+                    finally
+                    {
+                        this.closed = true;
+                        End();
+                        Platform.Dispose(output);
+                        output = null;
+                    }
                 }
             }
-            finally
-            {
-                this.closed = true;
-                End();
-                Platform.Dispose(output);
-                output = null;
-            }
+            base.Dispose(disposing);
         }
 
         public virtual void End()
@@ -252,4 +264,38 @@ namespace Org.BouncyCastle.Utilities.Zlib
             Write(buf1, 0, 1);
         }
     }
+
+    public class ZOutputStreamLeaveOpen
+        : ZOutputStream
+    {
+        public ZOutputStreamLeaveOpen(Stream output)
+            : base(output)
+        {
+        }
+
+        public ZOutputStreamLeaveOpen(Stream output, bool nowrap)
+            : base(output, nowrap)
+        {
+        }
+
+        public ZOutputStreamLeaveOpen(Stream output, ZStream z)
+            : base(output, z)
+        {
+        }
+
+        public ZOutputStreamLeaveOpen(Stream output, int level)
+            : base(output, level)
+        {
+        }
+
+        public ZOutputStreamLeaveOpen(Stream output, int level, bool nowrap)
+            : base(output, level, nowrap)
+        {
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            Detach(disposing);
+        }
+    }
 }
diff --git a/crypto/src/x509/X509Certificate.cs b/crypto/src/x509/X509Certificate.cs
index 75efdfbb1..627903e1f 100644
--- a/crypto/src/x509/X509Certificate.cs
+++ b/crypto/src/x509/X509Certificate.cs
@@ -682,7 +682,7 @@ namespace Org.BouncyCastle.X509
 
             Asn1Encodable parameters = c.SignatureAlgorithm.Parameters;
 
-            IStreamCalculator streamCalculator = verifier.CreateCalculator();
+            IStreamCalculator<IVerifier> streamCalculator = verifier.CreateCalculator();
 
             byte[] b = this.GetTbsCertificate();
 
@@ -690,10 +690,8 @@ namespace Org.BouncyCastle.X509
 
             Platform.Dispose(streamCalculator.Stream);
 
-            if (!((IVerifier)streamCalculator.GetResult()).IsVerified(this.GetSignature()))
-            {
+            if (!streamCalculator.GetResult().IsVerified(this.GetSignature()))
                 throw new InvalidKeyException("Public key presented not for certificate signature");
-            }
         }
 
         private CachedEncoding GetCachedEncoding()
diff --git a/crypto/src/x509/X509Crl.cs b/crypto/src/x509/X509Crl.cs
index 8a7c51a1d..265c2293c 100644
--- a/crypto/src/x509/X509Crl.cs
+++ b/crypto/src/x509/X509Crl.cs
@@ -12,7 +12,6 @@ using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Security.Certificates;
 using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.X509.Extension;
 
@@ -127,14 +126,13 @@ namespace Org.BouncyCastle.X509
         protected virtual void CheckSignature(
             IVerifierFactory verifier)
         {
+            // TODO Compare IsAlgIDEqual in X509Certificate.CheckSignature
             if (!c.SignatureAlgorithm.Equals(c.TbsCertList.Signature))
-            {
                 throw new CrlException("Signature algorithm on CertificateList does not match TbsCertList.");
-            }
 
             Asn1Encodable parameters = c.SignatureAlgorithm.Parameters;
 
-            IStreamCalculator streamCalculator = verifier.CreateCalculator();
+            IStreamCalculator<IVerifier> streamCalculator = verifier.CreateCalculator();
 
             byte[] b = this.GetTbsCertList();
 
@@ -142,10 +140,8 @@ namespace Org.BouncyCastle.X509
 
             Platform.Dispose(streamCalculator.Stream);
 
-            if (!((IVerifier)streamCalculator.GetResult()).IsVerified(this.GetSignature()))
-            {
+            if (!streamCalculator.GetResult().IsVerified(this.GetSignature()))
                 throw new InvalidKeyException("CRL does not verify with supplied public key.");
-            }
         }
 
         public virtual int Version
@@ -163,15 +159,7 @@ namespace Org.BouncyCastle.X509
 			get { return c.ThisUpdate.ToDateTime(); }
 		}
 
-		public virtual DateTimeObject NextUpdate
-		{
-			get
-			{
-				return c.NextUpdate == null
-					?	null
-					:	new DateTimeObject(c.NextUpdate.ToDateTime());
-			}
-		}
+		public virtual DateTime? NextUpdate => c.NextUpdate?.ToDateTime();
 
 		private ISet<X509CrlEntry> LoadCrlEntries()
 		{
diff --git a/crypto/src/x509/X509V1CertificateGenerator.cs b/crypto/src/x509/X509V1CertificateGenerator.cs
index 01c155b5d..d95f522e8 100644
--- a/crypto/src/x509/X509V1CertificateGenerator.cs
+++ b/crypto/src/x509/X509V1CertificateGenerator.cs
@@ -124,13 +124,13 @@ namespace Org.BouncyCastle.X509
 
 			TbsCertificateStructure tbsCert = tbsGen.GenerateTbsCertificate();
 
-			IStreamCalculator streamCalculator = signatureFactory.CreateCalculator();
+			IStreamCalculator<IBlockResult> streamCalculator = signatureFactory.CreateCalculator();
 			using (Stream sigStream = streamCalculator.Stream)
 			{
 				tbsCert.EncodeTo(sigStream, Asn1Encodable.Der);
 			}
 
-			var signature = ((IBlockResult)streamCalculator.GetResult()).Collect();
+			var signature = streamCalculator.GetResult().Collect();
 
 			return new X509Certificate(
 				new X509CertificateStructure(tbsCert, sigAlgID, new DerBitString(signature)));
diff --git a/crypto/src/x509/X509V2AttributeCertificate.cs b/crypto/src/x509/X509V2AttributeCertificate.cs
index 8c6ff0062..fbb4fe20f 100644
--- a/crypto/src/x509/X509V2AttributeCertificate.cs
+++ b/crypto/src/x509/X509V2AttributeCertificate.cs
@@ -182,12 +182,11 @@ namespace Org.BouncyCastle.X509
         protected virtual void CheckSignature(
             IVerifierFactory verifier)
         {
+            // TODO Compare IsAlgIDEqual in X509Certificate.CheckSignature
             if (!cert.SignatureAlgorithm.Equals(cert.ACInfo.Signature))
-			{
 				throw new CertificateException("Signature algorithm in certificate info not same as outer certificate");
-			}
 
-            IStreamCalculator streamCalculator = verifier.CreateCalculator();
+            IStreamCalculator<IVerifier> streamCalculator = verifier.CreateCalculator();
 
 			try
 			{
@@ -202,10 +201,8 @@ namespace Org.BouncyCastle.X509
 				throw new SignatureException("Exception encoding certificate info object", e);
 			}
 
-			if (!((IVerifier)streamCalculator.GetResult()).IsVerified(this.GetSignature()))
-			{
+			if (!streamCalculator.GetResult().IsVerified(this.GetSignature()))
 				throw new InvalidKeyException("Public key presented not for certificate signature");
-			}
 		}
 
 		public virtual byte[] GetEncoded()
diff --git a/crypto/src/x509/X509V2AttributeCertificateGenerator.cs b/crypto/src/x509/X509V2AttributeCertificateGenerator.cs
index 3e1a58e49..3a0a02ea9 100644
--- a/crypto/src/x509/X509V2AttributeCertificateGenerator.cs
+++ b/crypto/src/x509/X509V2AttributeCertificateGenerator.cs
@@ -54,13 +54,13 @@ namespace Org.BouncyCastle.X509
 		public void SetNotBefore(
 			DateTime date)
 		{
-			acInfoGen.SetStartDate(new DerGeneralizedTime(date));
+			acInfoGen.SetStartDate(new Asn1GeneralizedTime(date));
 		}
 
 		public void SetNotAfter(
 			DateTime date)
 		{
-			acInfoGen.SetEndDate(new DerGeneralizedTime(date));
+			acInfoGen.SetEndDate(new Asn1GeneralizedTime(date));
 		}
 
 		/// <summary>Add an attribute.</summary>
@@ -119,13 +119,13 @@ namespace Org.BouncyCastle.X509
 
             AttributeCertificateInfo acInfo = acInfoGen.GenerateAttributeCertificateInfo();
 
-			IStreamCalculator streamCalculator = signatureFactory.CreateCalculator();
+			IStreamCalculator<IBlockResult> streamCalculator = signatureFactory.CreateCalculator();
 			using (Stream sigStream = streamCalculator.Stream)
 			{
 				acInfo.EncodeTo(sigStream, Asn1Encodable.Der);
 			}
 
-			var signature = ((IBlockResult)streamCalculator.GetResult()).Collect();
+			var signature = streamCalculator.GetResult().Collect();
 
 			return new X509V2AttributeCertificate(
 				new AttributeCertificate(acInfo, sigAlgID, new DerBitString(signature)));
diff --git a/crypto/src/x509/X509V2CRLGenerator.cs b/crypto/src/x509/X509V2CRLGenerator.cs
index e386ee8f2..a57383613 100644
--- a/crypto/src/x509/X509V2CRLGenerator.cs
+++ b/crypto/src/x509/X509V2CRLGenerator.cs
@@ -79,7 +79,8 @@ namespace Org.BouncyCastle.X509
 			int			reason,
 			DateTime	invalidityDate)
 		{
-			tbsGen.AddCrlEntry(new DerInteger(userCertificate), new Time(revocationDate), reason, new DerGeneralizedTime(invalidityDate));
+			tbsGen.AddCrlEntry(new DerInteger(userCertificate), new Time(revocationDate), reason,
+				new Asn1GeneralizedTime(invalidityDate));
 		}
 
 		/**
@@ -186,13 +187,13 @@ namespace Org.BouncyCastle.X509
 
 			TbsCertificateList tbsCertList = tbsGen.GenerateTbsCertList();
 
-            IStreamCalculator streamCalculator = signatureFactory.CreateCalculator();
+            IStreamCalculator<IBlockResult> streamCalculator = signatureFactory.CreateCalculator();
 			using (Stream sigStream = streamCalculator.Stream)
 			{
 				tbsCertList.EncodeTo(sigStream, Asn1Encodable.Der);
 			}
 
-			var signature = ((IBlockResult)streamCalculator.GetResult()).Collect();
+			var signature = streamCalculator.GetResult().Collect();
 
 			return new X509Crl(
 				CertificateList.GetInstance(new DerSequence(tbsCertList, sigAlgID, new DerBitString(signature))));
diff --git a/crypto/src/x509/X509V3CertificateGenerator.cs b/crypto/src/x509/X509V3CertificateGenerator.cs
index 50e3fc689..1854ac3b4 100644
--- a/crypto/src/x509/X509V3CertificateGenerator.cs
+++ b/crypto/src/x509/X509V3CertificateGenerator.cs
@@ -259,13 +259,13 @@ namespace Org.BouncyCastle.X509
 
             TbsCertificateStructure tbsCert = tbsGen.GenerateTbsCertificate();
 
-			IStreamCalculator streamCalculator = signatureFactory.CreateCalculator();
+			IStreamCalculator<IBlockResult> streamCalculator = signatureFactory.CreateCalculator();
 			using (Stream sigStream = streamCalculator.Stream)
             {
 				tbsCert.EncodeTo(sigStream, Asn1Encodable.Der);
 			}
 
-			var signature = ((IBlockResult)streamCalculator.GetResult()).Collect();
+			var signature = streamCalculator.GetResult().Collect();
 
 			return new X509Certificate(new X509CertificateStructure(tbsCert, sigAlgID, new DerBitString(signature)));
 		}
diff --git a/crypto/src/x509/store/X509AttrCertStoreSelector.cs b/crypto/src/x509/store/X509AttrCertStoreSelector.cs
index 5744289e9..e68208c74 100644
--- a/crypto/src/x509/store/X509AttrCertStoreSelector.cs
+++ b/crypto/src/x509/store/X509AttrCertStoreSelector.cs
@@ -5,7 +5,6 @@ using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities.Collections;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509.Extension;
 
 namespace Org.BouncyCastle.X509.Store
@@ -23,7 +22,7 @@ namespace Org.BouncyCastle.X509.Store
 		// TODO: name constraints???
 
 		private X509V2AttributeCertificate attributeCert;
-		private DateTimeObject attributeCertificateValid;
+		private DateTime? attributeCertificateValid;
 		private AttributeCertificateHolder holder;
 		private AttributeCertificateIssuer issuer;
 		private BigInteger serialNumber;
@@ -162,7 +161,7 @@ namespace Org.BouncyCastle.X509.Store
 
 		/// <summary>The criteria for validity</summary>
 		/// <remarks>If <c>null</c> is given any will do.</remarks>
-		public DateTimeObject AttributeCertificateValid
+		public DateTime? AttributeCertificateValid
 		{
 			get { return attributeCertificateValid; }
 			set { this.attributeCertificateValid = value; }
diff --git a/crypto/src/x509/store/X509CertStoreSelector.cs b/crypto/src/x509/store/X509CertStoreSelector.cs
index 71b5419a7..357b5e76d 100644
--- a/crypto/src/x509/store/X509CertStoreSelector.cs
+++ b/crypto/src/x509/store/X509CertStoreSelector.cs
@@ -6,7 +6,6 @@ using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509.Extension;
 
 namespace Org.BouncyCastle.X509.Store
@@ -19,13 +18,13 @@ namespace Org.BouncyCastle.X509.Store
 		private byte[] authorityKeyIdentifier;
 		private int basicConstraints = -1;
 		private X509Certificate certificate;
-		private DateTimeObject certificateValid;
+		private DateTime? certificateValid;
 		private ISet<DerObjectIdentifier> extendedKeyUsage;
         private bool ignoreX509NameOrdering;
 		private X509Name issuer;
 		private bool[] keyUsage;
 		private ISet<DerObjectIdentifier> policy;
-		private DateTimeObject privateKeyValid;
+		private DateTime? privateKeyValid;
 		private BigInteger serialNumber;
 		private X509Name subject;
 		private byte[] subjectKeyIdentifier;
@@ -85,7 +84,7 @@ namespace Org.BouncyCastle.X509.Store
 			set { this.certificate = value; }
 		}
 
-		public DateTimeObject CertificateValid
+		public DateTime? CertificateValid
 		{
 			get { return certificateValid; }
 			set { certificateValid = value; }
@@ -124,7 +123,7 @@ namespace Org.BouncyCastle.X509.Store
 			set { policy = CopySet(value); }
 		}
 
-		public DateTimeObject PrivateKeyValid
+		public DateTime? PrivateKeyValid
 		{
 			get { return privateKeyValid; }
 			set { privateKeyValid = value; }
diff --git a/crypto/src/x509/store/X509CrlStoreSelector.cs b/crypto/src/x509/store/X509CrlStoreSelector.cs
index 365ec1d38..9e84b82ae 100644
--- a/crypto/src/x509/store/X509CrlStoreSelector.cs
+++ b/crypto/src/x509/store/X509CrlStoreSelector.cs
@@ -6,7 +6,6 @@ using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
-using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.X509.Extension;
 
 namespace Org.BouncyCastle.X509.Store
@@ -17,7 +16,7 @@ namespace Org.BouncyCastle.X509.Store
 		// TODO Missing criteria?
 
 		private X509Certificate certificateChecking;
-		private DateTimeObject dateAndTime;
+		private DateTime? dateAndTime;
 		private IList<X509Name> issuers;
 		private BigInteger maxCrlNumber;
 		private BigInteger minCrlNumber;
@@ -61,7 +60,7 @@ namespace Org.BouncyCastle.X509.Store
 			set { certificateChecking = value; }
 		}
 
-		public DateTimeObject DateAndTime
+		public DateTime? DateAndTime
 		{
 			get { return dateAndTime; }
 			set { dateAndTime = value; }
@@ -190,7 +189,7 @@ namespace Org.BouncyCastle.X509.Store
 			{
 				DateTime dt = dateAndTime.Value;
 				DateTime tu = c.ThisUpdate;
-				DateTimeObject nu = c.NextUpdate;
+				DateTime? nu = c.NextUpdate;
 
 				if (dt.CompareTo(tu) < 0 || nu == null || dt.CompareTo(nu.Value) >= 0)
 					return false;