diff options
Diffstat (limited to 'Tools/Hazel-Networking/Hazel/Crypto')
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/AesGcm.cs | 369 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/Const.cs | 82 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/CryptoProvider.cs | 36 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/DefaultAes.cs | 49 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/IAes.cs | 27 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/Sha256Stream.cs | 86 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/SpanCryptoExtensions.cs | 36 | ||||
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Crypto/X25519.cs | 844 |
8 files changed, 1529 insertions, 0 deletions
diff --git a/Tools/Hazel-Networking/Hazel/Crypto/AesGcm.cs b/Tools/Hazel-Networking/Hazel/Crypto/AesGcm.cs new file mode 100644 index 0000000..bfbbc01 --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/AesGcm.cs @@ -0,0 +1,369 @@ +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace Hazel.Crypto +{ + /// <summary> + /// Implementation of AEAD_AES128_GCM based on: + /// * RFC 5116 [1] + /// * NIST SP 800-38d [2] + /// + /// [1] https://tools.ietf.org/html/rfc5116 + /// [2] https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf + /// + /// Adapted from: https://gist.github.com/mendsley/777e6bd9ae7eddcb2b0c0fe18247dc60 + /// </summary> + public class Aes128Gcm : IDisposable + { + public const int KeySize = 16; + public const int NonceSize = 12; + public const int CiphertextOverhead = TagSize; + + private const int TagSize = 16; + + private readonly IAes encryptor_; + + private readonly ByteSpan hashSubkey_; + private readonly ByteSpan blockJ_; + private readonly ByteSpan blockS_; + private readonly ByteSpan blockZ_; + private readonly ByteSpan blockV_; + private readonly ByteSpan blockScratch_; + + /// <summary> + /// Creates a new instance of an AEAD_AES128_GCM cipher + /// </summary> + /// <param name="key">Symmetric key</param> + public Aes128Gcm(ByteSpan key) + { + if (key.Length != KeySize) + { + throw new ArgumentException("Invalid key length", nameof(key)); + } + + // Create the AES block cipher + this.encryptor_ = CryptoProvider.CreateAes(key); + + // Allocate scratch space + ByteSpan scratchSpace = new byte[96]; + this.hashSubkey_ = scratchSpace.Slice(0, 16); + this.blockJ_ = scratchSpace.Slice(16, 16); + this.blockS_ = scratchSpace.Slice(32, 16); + this.blockZ_ = scratchSpace.Slice(48, 16); + this.blockV_ = scratchSpace.Slice(64, 16); + this.blockScratch_ = scratchSpace.Slice(80, 16); + + // Create the GHASH subkey by encrypting the 0-block + this.encryptor_.EncryptBlock(this.hashSubkey_, this.hashSubkey_); + } + + /// <summary> + /// Encryptes the specified plaintext and generates an authentication + /// tag for the provided additional data. Returns the byte array + /// containg both the ciphertext and authentication tag. + /// </summary> + /// <param name="output"> + /// Array in which to encode the encrypted ciphertext and + /// authentication tag. This array must be large enough to hold + /// `plaintext.Lengh + CiphertextOverhead` bytes. + /// </param> + /// <param name="nonce">Unique value for this message</param> + /// <param name="plaintext">Plaintext data to encrypt</param> + /// <param name="associatedData"> + /// Additional data used to authenticate the message + /// </param> + public void Seal(ByteSpan output, ByteSpan nonce, ByteSpan plaintext, ByteSpan associatedData) + { + if (nonce.Length != NonceSize) + { + throw new ArgumentException("Invalid nonce size", nameof(nonce)); + } + if (output.Length < plaintext.Length + CiphertextOverhead) + { + throw new ArgumentException("Invalid output size", nameof(output)); + } + + // Create the initial counter block + nonce.CopyTo(this.blockJ_); + + // Encrypt the plaintext to output + GCTR(output, this.blockJ_, 2, plaintext); + + // Generate and append the authentication tag + int tagOffset = plaintext.Length; + GenerateAuthenticationTag(output.Slice(tagOffset), output.Slice(0, tagOffset), associatedData); + } + + /// <summary> + /// Validates the authentication tag against the provided additional + /// data, then decrypts the cipher text returning the original + /// plaintext. + /// </summary> + /// <param name="nonce"> + /// The unique value used to seal this message + /// </param> + /// <param name="ciphertext"> + /// Combined ciphertext and authentication tag + /// </param> + /// <param name="associatedData"> + /// Additional data used to authenticate the message + /// </param> + /// <param name="output"> + /// On successful validation and decryprion, Open writes the original + /// plaintext to output. Must contain enough space to hold + /// `ciphertext.Length - CiphertextOverhead` bytes. + /// </param> + /// <returns> + /// True if the data was validated and successfully decrypted. + /// Otherwise, false. + /// </returns> + public bool Open(ByteSpan output, ByteSpan nonce, ByteSpan ciphertext, ByteSpan associatedData) + { + if (nonce.Length != NonceSize) + { + throw new ArgumentException("Invalid nonce size", nameof(nonce)); + } + if (ciphertext.Length < CiphertextOverhead) + { + throw new ArgumentException("Invalid ciphertext size", nameof(ciphertext)); + } + else if (output.Length < ciphertext.Length - CiphertextOverhead) + { + throw new ArgumentException("Invalid output size", nameof(output)); + } + + // Split ciphertext into actual ciphertext and authentication + // tag components. + ByteSpan authenticationTag = ciphertext.Slice(ciphertext.Length - TagSize); + ciphertext = ciphertext.Slice(0, ciphertext.Length - TagSize); + + // Create the initial counter block + nonce.CopyTo(this.blockJ_); + + // Verify the tags match + GenerateAuthenticationTag(this.blockScratch_, ciphertext, associatedData); + if (0 == Const.ConstantCompareSpans(this.blockScratch_, authenticationTag)) + { + return false; + } + + // Decrypt the cipher text to output + GCTR(output, this.blockJ_, 2, ciphertext); + return true; + } + + /// <summary> + /// Release resources acquired by the cipher + /// </summary> + public void Dispose() + { + this.encryptor_.Dispose(); + } + + // Generate the authentication tag for a ciphertext+associated data + void GenerateAuthenticationTag(ByteSpan output, ByteSpan ciphertext, ByteSpan associatedData) + { + Debug.Assert(output.Length >= 16); + + // Hash `Associated data || Ciphertext || len(AssociatedD data) || len(Ciphertext)` + // into `blockS` + { + // Clear hash output block + SetSpanToZeros(this.blockS_); + + // Write associated data blocks to hash + int fullBlocks = associatedData.Length / 16; + GHASH(this.blockS_, associatedData, fullBlocks); + if (fullBlocks * 16 < associatedData.Length) + { + SetSpanToZeros(this.blockScratch_); + associatedData.Slice(fullBlocks * 16).CopyTo(this.blockScratch_); + GHASH(this.blockS_, this.blockScratch_, 1); + } + + // Write ciphertext blocks to hash + fullBlocks = ciphertext.Length / 16; + GHASH(this.blockS_, ciphertext, fullBlocks); + if (fullBlocks * 16 < ciphertext.Length) + { + SetSpanToZeros(this.blockScratch_); + ciphertext.Slice(fullBlocks * 16).CopyTo(this.blockScratch_); + GHASH(this.blockS_, this.blockScratch_, 1); + } + + // Write bit sizes to hash + ulong associatedDataLengthInBits = (ulong)(8 * associatedData.Length); + ulong ciphertextDataLengthInBits = (ulong)(8 * ciphertext.Length); + this.blockScratch_.WriteBigEndian64(associatedDataLengthInBits); + this.blockScratch_.WriteBigEndian64(ciphertextDataLengthInBits, 8); + + GHASH(this.blockS_, this.blockScratch_, 1); + } + + // Encrypt the tag. GCM requires this because `GASH` is not + // cryptographically secure. An attacker could derive our hash + // subkey `hashSubkey_` from an unencrypted tag. + GCTR(output, this.blockJ_, 1, this.blockS_); + } + + // Run the GCTR cipher + void GCTR(ByteSpan output, ByteSpan counterBlock, uint counter, ByteSpan data) + { + Debug.Assert(counterBlock.Length == 16); + Debug.Assert(output.Length >= data.Length); + + // Loop through plaintext blocks + int writeIndex = 0; + int numBlocks = (data.Length + 15) / 16; + for (int ii = 0; ii != numBlocks; ++ii) + { + // Encode counter into block + // CB[1] = J0 + // CB[i] = inc[32](CB[i-1]) + counterBlock.WriteBigEndian32(counter, 12); + ++counter; + + // CIPH[k](CB[i]) + this.encryptor_.EncryptBlock(counterBlock.Slice(0, 16), this.blockScratch_); + + // Y[i] = X[i] xor CIPH[k](CB[i]) + for (int jj = 0; jj != 16 && writeIndex < data.Length; ++jj, ++writeIndex) + { + output[writeIndex] = (byte)(data[writeIndex] ^ this.blockScratch_[jj]); + } + } + } + + // Run the GHASH function + void GHASH(ByteSpan output, ByteSpan data, int numBlocks) + { + ///TODO(mendsley): See Ref[6] for opitmizations of GHASH on both hardware and software + /// + ///[6] D. McGrew, J. Viega, The Galois/Counter Mode of Operation (GCM), Natl. Inst. Stand. + ///Technol. [Web page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/ + ///proposedmodes / gcm / gcm - revised - spec.pdf, May 31, 2005. + + Debug.Assert(output.Length == 16); + Debug.Assert(data.Length >= numBlocks * 16); + + int readIndex = 0; + for (int ii = 0; ii != numBlocks; ++ii) + { + for (int jj = 0; jj != 16; ++jj, ++readIndex) + { + // Y[ii-1] xor X[ii] + output[jj] ^= data[readIndex]; + } + + // Y[ii] = (Y[ii-1] xor X[ii]) · H + MultiplyGF128Elements(output, this.hashSubkey_, this.blockZ_, this.blockV_); + } + } + + // Multiply two Galois field elements `X` and `Y` together and store + // the result in `X` such that at the end of the function: + // X = X·Y + static void MultiplyGF128Elements(ByteSpan X, ByteSpan Y, ByteSpan scratchZ, ByteSpan scratchV) + { + Debug.Assert(X.Length == 16); + Debug.Assert(Y.Length == 16); + Debug.Assert(scratchZ.Length == 16); + Debug.Assert(scratchV.Length == 16); + + // Galois (finite) fields represented by GF(p) define a set of + // closed algebraic operations. For AES128_GCM we'll be dealing + // with the GF(2^128) field. + // + // We treat each incoming 16 byte block as a polynomial in field + // and define multiplication between two polynomials as the + // polynomial product reduced by (mod) the field polynomial: + // 1 + x + x^2 + x^7 + x^128 + // + // Field polynomials are represented by a 128 bit string. Bit n is + // the coefficient of the x^n term. We use little-endian bit + // ordering (not to be confused with byte ordering) for these + // coefficients. E.g. X[0] & 0x00000001 represents the 7th bit in + // the bit string defined by X, _not_ the 0th bit. + // + + // What follows is a modified version of the "peasant's algorithm" + // to multiply two numbers: + // + // Z contains the accumulated product + // V is a copy of Y (so we can modify it via shifting). + // + // We calculate Z = X·V as follows + // We loop through each of the 128 bits in X maintaining the + // following loop invariant: X·V + Z = the final product + // + // On each iteration `ii`: + // + // If the `ii`th bit of `X` is set, add the add the polynomial + // in `V` to `X`: `X[n] = X[n] ^ V[n]` + // + // Double V (Shift one bit right since we're storing little + // endian bit). This has the effect of multiplying V by the + // polynomial `x`. We track the unrepresentable coefficient + // of `x^128` by storing the most significant bit before the + // shift `V[15] >> 7` as `carry` + // + // Check if we've overflowed our multiplication. If overflow + // occurred, there will be a non-zero coefficient for the + // `x^128` term in the step above `carry` + // + // If we have overflowed, our polynomial is exactly of degree + // 129 (since we're only multiplying by `x`). We reduce the + // polynomial back into degree 128 by adding our field's + // irreducible polynomial: 1 + x + x^2 + x^7 + x^128. This + // reduction cancels out the x^128 term (x^128 + x^128 in GF(2) + // is zero). Therefore this modulo can be achieved by simply + // adding the irreducible polynomial to the new value of `V`. The + // irreducible polynomial is represented by the bit string: + // `11100001` followed by 120 `0`s. We can add this value to `V` + // by: `V[0] = V[0] ^ 0xE1`. + SetSpanToZeros(scratchZ); + X.CopyTo(scratchV); + + for (int ii = 0; ii != 128; ++ii) + { + int bitIndex = 7 - (ii % 8); + if ((Y[ii / 8] & (1 << bitIndex)) != 0) + { + for (int jj = 0; jj != 16; ++jj) + { + scratchZ[jj] ^= scratchV[jj]; + } + } + + bool carry = false; + for (int jj = 0; jj != 16; ++jj) + { + bool newCarry = (scratchV[jj] & 0x01) != 0; + scratchV[jj] >>= 1; + if (carry) + { + scratchV[jj] |= 0x80; + } + carry = newCarry; + } + + if (carry) + { + scratchV[0] ^= 0xE1; + } + } + + scratchZ.CopyTo(X); + } + + // Set the contents of a span to all zero + static void SetSpanToZeros(ByteSpan span) + { + for (int ii = 0, nn = span.Length; ii != nn; ++ii) + { + span[ii] = 0; + } + } + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/Const.cs b/Tools/Hazel-Networking/Hazel/Crypto/Const.cs new file mode 100644 index 0000000..4dfef47 --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/Const.cs @@ -0,0 +1,82 @@ +using System.Diagnostics; + +namespace Hazel.Crypto +{ + public static class Const + { + + /// <summary> + /// Compare two bytes for equality. + /// + /// This takes care to always use a constant amount of time to prevent + /// leaking information through side-channel attacks. + /// + /// This is aceived by collapsing the xor bits down into a single bit. + /// + /// Ported from: + /// https://github.com/mendsley/tiny/blob/master/include/tiny/crypto/constant.h + /// </summary> + /// <returns> + /// Returns `1` is the two bytes or equivalent. Otherwise, returns `0` + /// </returns> + public static byte ConstantCompareByte(byte a, byte b) + { + byte result = (byte)(~(a ^ b)); + + // collapse bits down to the LSB + result &= (byte)(result >> 4); + result &= (byte)(result >> 2); + result &= (byte)(result >> 1); + + return result; + } + + /// <summary> + /// Compare two equal length spans for equality. + /// + /// This takes care to always use a constant amount of time to prevent + /// leaking information through side-channel attacks. + /// + /// Ported from: + /// https://github.com/mendsley/tiny/blob/master/include/tiny/crypto/constant.h + /// </summary> + /// <returns> + /// Returns `1` if the spans are equivalent. Others, returns `0`. + /// </returns> + public static byte ConstantCompareSpans(ByteSpan a, ByteSpan b) + { + Debug.Assert(a.Length == b.Length); + + byte value = 0; + for (int ii = 0, nn = a.Length; ii != nn; ++ii) + { + value |= (byte)(a[ii] ^ b[ii]); + } + + return ConstantCompareByte(value, 0); + } + + /// <summary> + /// Compare a span against an all zero span + /// + /// This takes care to always use a constant amount of time to prevent + /// leaking information through side-channel attacks. + /// + /// Ported from: + /// https://github.com/mendsley/tiny/blob/master/include/tiny/crypto/constant.h + /// </summary> + /// <returns> + /// Returns `1` if the spans is all zeros. Others, returns `0`. + /// </returns> + public static byte ConstantCompareZeroSpan(ByteSpan a) + { + byte value = 0; + for (int ii = 0, nn = a.Length; ii != nn; ++ii) + { + value |= (byte)(a[ii] ^ 0); + } + + return ConstantCompareByte(value, 0); + } + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/CryptoProvider.cs b/Tools/Hazel-Networking/Hazel/Crypto/CryptoProvider.cs new file mode 100644 index 0000000..2c56c70 --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/CryptoProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hazel.Crypto +{ + public static class CryptoProvider + { + public delegate IAes CreateAesOverrideDelegate(ByteSpan key); + + /// <summary> + /// Override the default AES creation function + /// </summary> + public static CreateAesOverrideDelegate OverrideCreateAes = null; + + /// <summary> + /// Create a new AES cipher + /// </summary> + /// <param name="key">Encrtyption key</param> + public static IAes CreateAes(ByteSpan key) + { + if (OverrideCreateAes != null) + { + IAes result = OverrideCreateAes(key); + if (null != result) + { + return result; + } + } + + return new DefaultAes(key); + } + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/DefaultAes.cs b/Tools/Hazel-Networking/Hazel/Crypto/DefaultAes.cs new file mode 100644 index 0000000..da72fb8 --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/DefaultAes.cs @@ -0,0 +1,49 @@ +using System; +using System.Security.Cryptography; + +namespace Hazel.Crypto +{ + /// <summary> + /// AES provider using the default System.Security.Cryptography implementation + /// </summary> + public class DefaultAes : IAes + { + private readonly ICryptoTransform encryptor_; + + /// <summary> + /// Create a new default instance of the AES block cipher + /// </summary> + /// <param name="key">Encryption key</param> + public DefaultAes(ByteSpan key) + { + // Create the AES block cipher + using (Aes aes = Aes.Create()) + { + aes.KeySize = key.Length * 8; + aes.BlockSize = aes.KeySize; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.Zeros; + aes.Key = key.ToArray(); + + this.encryptor_ = aes.CreateEncryptor(); + } + } + + /// <inheritdoc/> + public void Dispose() + { + this.encryptor_.Dispose(); + } + + /// <inheritdoc/> + public int EncryptBlock(ByteSpan inputSpan, ByteSpan outputSpan) + { + if (inputSpan.Length != outputSpan.Length) + { + throw new ArgumentException($"ouputSpan length ({outputSpan.Length}) does not match inputSpan length ({inputSpan.Length})", nameof(outputSpan)); + } + + return this.encryptor_.TransformBlock(inputSpan.GetUnderlyingArray(), inputSpan.Offset, inputSpan.Length, outputSpan.GetUnderlyingArray(), outputSpan.Offset); + } + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/IAes.cs b/Tools/Hazel-Networking/Hazel/Crypto/IAes.cs new file mode 100644 index 0000000..6c494cd --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/IAes.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hazel.Crypto +{ + /// <summary> + /// AES encryption interface + /// </summary> + public interface IAes : IDisposable + { + /// <summary> + /// Encrypts the specified region of the input byte array and copies + /// the resulting transform to the specified region of the output + /// array. + /// </summary> + /// <param name="inputSpan">The input for which to encrypt</param> + /// <param name="outputSpan"> + /// The otput to which to write the encrypted data. This span can + /// overlap with `inputSpan`. + /// </param> + /// <returns>The number of bytes written</returns> + int EncryptBlock(ByteSpan inputSpan, ByteSpan outputSpan); + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/Sha256Stream.cs b/Tools/Hazel-Networking/Hazel/Crypto/Sha256Stream.cs new file mode 100644 index 0000000..1903693 --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/Sha256Stream.cs @@ -0,0 +1,86 @@ +using System; +using System.Security.Cryptography; + +namespace Hazel.Crypto +{ + /// <summary> + /// Streams data into a SHA256 digest + /// </summary> + public class Sha256Stream : IDisposable + { + /// <summary> + /// Size of the SHA256 digest in bytes + /// </summary> + public const int DigestSize = 32; + + private SHA256 hash = SHA256.Create(); + private bool isHashFinished = false; + + struct EmptyArray + { + public static readonly byte[] Value = new byte[0]; + } + + /// <summary> + /// Create a new instance of a SHA256 stream + /// </summary> + public Sha256Stream() + { + } + + /// <summary> + /// Release resources associated with the stream + /// </summary> + public void Dispose() + { + this.hash?.Dispose(); + this.hash = null; + + GC.SuppressFinalize(this); + } + + /// <summary> + /// Reset the stream to its initial state + /// </summary> + public void Reset() + { + this.hash?.Dispose(); + this.hash = SHA256.Create(); + this.isHashFinished = false; + } + + /// <summary> + /// Add data to the stream + /// </summary> + public void AddData(ByteSpan data) + { + while (data.Length > 0) + { + int offset = this.hash.TransformBlock(data.GetUnderlyingArray(), data.Offset, data.Length, null, 0); + data = data.Slice(offset); + } + } + + /// <summary> + /// Calculate the final hash of the stream data + /// </summary> + /// <param name="output"> + /// Target span to which the hash will be written + /// </param> + public void CopyOrCalculateFinalHash(ByteSpan output) + { + if (output.Length != DigestSize) + { + throw new ArgumentException($"Expected a span of {DigestSize} bytes. Got a span of {output.Length} bytes", nameof(output)); + } + + if (this.isHashFinished == false) + { + this.hash.TransformFinalBlock(EmptyArray.Value, 0, 0); + this.isHashFinished = true; + } + + new ByteSpan(this.hash.Hash).CopyTo(output); + } + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/SpanCryptoExtensions.cs b/Tools/Hazel-Networking/Hazel/Crypto/SpanCryptoExtensions.cs new file mode 100644 index 0000000..03164ec --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/SpanCryptoExtensions.cs @@ -0,0 +1,36 @@ +using System; +using System.Security.Cryptography; + +namespace Hazel.Crypto +{ + public static class SpanCryptoExtensions + { + /// <summary> + /// Clear a span's contents to zero + /// </summary> + public static void SecureClear(this ByteSpan span) + { + if (span.Length > 0) + { + Array.Clear(span.GetUnderlyingArray(), span.Offset, span.Length); + } + } + + /// <summary> + /// Fill a byte span with random data + /// </summary> + /// <param name="random">Entropy source</param> + public static void FillWithRandom(this ByteSpan span, RandomNumberGenerator random) + { + if (span.Offset == 0 && span.Length == span.GetUnderlyingArray().Length) + { + random.GetBytes(span.GetUnderlyingArray()); + return; + } + + byte[] temp = new byte[span.Length]; + random.GetBytes(temp); + new ByteSpan(temp).CopyTo(span); + } + } +} diff --git a/Tools/Hazel-Networking/Hazel/Crypto/X25519.cs b/Tools/Hazel-Networking/Hazel/Crypto/X25519.cs new file mode 100644 index 0000000..3f4624b --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Crypto/X25519.cs @@ -0,0 +1,844 @@ +using System; +using System.Diagnostics; + +namespace Hazel.Crypto +{ + /// <summary> + /// The x25519 key agreement algorithm + /// </summary> + public static class X25519 + { + public const int KeySize = 32; + + /// <summary> + /// Element in the GF(2^255 - 19) field + /// </summary> + public partial struct FieldElement + { + public int x0, x1, x2, x3, x4; + public int x5, x6, x7, x8, x9; + }; + + private static readonly byte[] BasePoint = {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + /// <summary> + /// Performs the core x25519 function: Multiplying an EC point by a scalar value + /// </summary> + public static bool Func(ByteSpan output, ByteSpan scalar, ByteSpan point) + { + InternalFunc(output, scalar, point); + if (Const.ConstantCompareZeroSpan(output) == 1) + { + return false; + } + + return true; + } + + /// <summary> + /// Multiplies the base x25519 point by the provided scalar value + /// </summary> + public static void Func(ByteSpan output, ByteSpan scalar) + { + InternalFunc(output, scalar, BasePoint); + } + + // The FieldElement code below is ported from the original + // public domain reference implemtation of X25519 + // by D. J. Bernstien + // + // See: https://cr.yp.to/ecdh.html + + private static void InternalFunc(ByteSpan output, ByteSpan scalar, ByteSpan point) + { + if (output.Length != KeySize) + { + throw new ArgumentException("Invalid output size", nameof(output)); + } + else if (scalar.Length != KeySize) + { + throw new ArgumentException("Invalid scalar size", nameof(scalar)); + } + else if (point.Length != KeySize) + { + throw new ArgumentException("Invalid point size", nameof(point)); + } + + // copy the scalar so we can properly mask it + ByteSpan maskedScalar = new byte[32]; + scalar.CopyTo(maskedScalar); + maskedScalar[0] &= 248; + maskedScalar[31] &= 127; + maskedScalar[31] |= 64; + + FieldElement x1 = FieldElement.FromBytes(point); + FieldElement x2 = FieldElement.One(); + FieldElement x3 = x1; + FieldElement z2 = FieldElement.Zero(); + FieldElement z3 = FieldElement.One(); + + FieldElement tmp0 = new FieldElement(); + FieldElement tmp1 = new FieldElement(); + + int swap = 0; + for (int pos = 254; pos >= 0; --pos) + { + int b = (int)maskedScalar[pos / 8] >> (int)(pos % 8); + b &= 1; + swap ^= b; + + FieldElement.ConditionalSwap(ref x2, ref x3, swap); + FieldElement.ConditionalSwap(ref z2, ref z3, swap); + swap = b; + + FieldElement.Sub(ref tmp0, ref x3, ref z3); + FieldElement.Sub(ref tmp1, ref x2, ref z2); + FieldElement.Add(ref x2, ref x2, ref z2); + FieldElement.Add(ref z2, ref x3, ref z3); + FieldElement.Multiply(ref z3, ref tmp0, ref x2); + FieldElement.Multiply(ref z2, ref z2, ref tmp1); + FieldElement.Square(ref tmp0, ref tmp1); + FieldElement.Square(ref tmp1, ref x2); + FieldElement.Add(ref x3, ref z3, ref z2); + FieldElement.Sub(ref z2, ref z3, ref z2); + FieldElement.Multiply(ref x2, ref tmp1, ref tmp0); + FieldElement.Sub(ref tmp1, ref tmp1, ref tmp0); + FieldElement.Square(ref z2, ref z2); + FieldElement.Multiply121666(ref z3, ref tmp1); + FieldElement.Square(ref x3, ref x3); + FieldElement.Add(ref tmp0, ref tmp0, ref z3); + FieldElement.Multiply(ref z3, ref x1, ref z2); + FieldElement.Multiply(ref z2, ref tmp1, ref tmp0); + } + + FieldElement.ConditionalSwap(ref x2, ref x3, swap); + FieldElement.ConditionalSwap(ref z2, ref z3, swap); + + FieldElement.Invert(ref z2, ref z2); + FieldElement.Multiply(ref x2, ref x2, ref z2); + x2.CopyTo(output); + } + + + /// <summary> + /// Mathematical operators over GF(2^255 - 19) + /// </summary> + partial struct FieldElement + { + /// <summary> + /// Convert a byte array to a field element + /// </summary> + public static FieldElement FromBytes(ByteSpan bytes) + { + Debug.Assert(bytes.Length >= KeySize); + + long tmp0 = (long)bytes.ReadLittleEndian32(); + long tmp1 = (long)bytes.ReadLittleEndian24(4) << 6; + long tmp2 = (long)bytes.ReadLittleEndian24(7) << 5; + long tmp3 = (long)bytes.ReadLittleEndian24(10) << 3; + long tmp4 = (long)bytes.ReadLittleEndian24(13) << 2; + long tmp5 = (long)bytes.ReadLittleEndian32(16); + long tmp6 = (long)bytes.ReadLittleEndian24(20) << 7; + long tmp7 = (long)bytes.ReadLittleEndian24(23) << 5; + long tmp8 = (long)bytes.ReadLittleEndian24(26) << 4; + long tmp9 = (long)(bytes.ReadLittleEndian24(29) & 0x007FFFFF) << 2; + + long carry9 = (tmp9 + (1L<<24)) >> 25; + tmp0 += carry9 * 19; + tmp9 -= carry9 << 25; + long carry1 = (tmp1 + (1L<<24)) >> 25; + tmp2 += carry1; + tmp1 -= carry1 << 25; + long carry3 = (tmp3 + (1L<<24)) >> 25; + tmp4 += carry3; + tmp3 -= carry3 << 25; + long carry5 = (tmp5 + (1L<<24)) >> 25; + tmp6 += carry5; + tmp5 -= carry5 << 25; + long carry7 = (tmp7 + (1L<<24)) >> 25; + tmp8 += carry7; + tmp7 -= carry7 << 25; + + long carry0 = (tmp0 + (1L<<25)) >> 26; + tmp1 += carry0; + tmp0 -= carry0 << 26; + long carry2 = (tmp2 + (1L<<25)) >> 26; + tmp3 += carry2; + tmp2 -= carry2 << 26; + long carry4 = (tmp4 + (1L<<25)) >> 26; + tmp5 += carry4; + tmp4 -= carry4 << 26; + long carry6 = (tmp6 + (1L<<25)) >> 26; + tmp7 += carry6; + tmp6 -= carry6 << 26; + long carry8 = (tmp8 + (1L<<25)) >> 26; + tmp9 += carry8; + tmp8 -= carry8 << 26; + + return new FieldElement + { + x0 = (int)tmp0, + x1 = (int)tmp1, + x2 = (int)tmp2, + x3 = (int)tmp3, + x4 = (int)tmp4, + x5 = (int)tmp5, + x6 = (int)tmp6, + x7 = (int)tmp7, + x8 = (int)tmp8, + x9 = (int)tmp9, + }; + } + + /// <summary> + /// Convert the field element to a byte array + /// </summary> + public void CopyTo(ByteSpan output) + { + Debug.Assert(output.Length >= 32); + + long q = (19 * this.x9 + (1L << 24)) >> 25; + q = ((long)this.x0 + q) >> 26; + q = ((long)this.x1 + q) >> 25; + q = ((long)this.x2 + q) >> 26; + q = ((long)this.x3 + q) >> 25; + q = ((long)this.x4 + q) >> 26; + q = ((long)this.x5 + q) >> 25; + q = ((long)this.x6 + q) >> 26; + q = ((long)this.x7 + q) >> 25; + q = ((long)this.x8 + q) >> 26; + q = ((long)this.x9 + q) >> 25; + + this.x0 = (int)((long)this.x0 + (19L * q)); + + int carry0 = (int)(this.x0 >> 26); + this.x1 = (int)((int)this.x1 + carry0); + this.x0 = (int)((int)this.x0 - (carry0 << 26)); + int carry1 = (int)(this.x1 >> 25); + this.x2 = (int)((int)this.x2 + carry1); + this.x1 = (int)((int)this.x1 - (carry1 << 25)); + int carry2 = (int)(this.x2 >> 26); + this.x3 = (int)((int)this.x3 + carry2); + this.x2 = (int)((int)this.x2 - (carry2 << 26)); + int carry3 = (int)(this.x3 >> 25); + this.x4 = (int)((int)this.x4 + carry3); + this.x3 = (int)((int)this.x3 - (carry3 << 25)); + int carry4 = (int)(this.x4 >> 26); + this.x5 = (int)((int)this.x5 + carry4); + this.x4 = (int)((int)this.x4 - (carry4 << 26)); + int carry5 = (int)(this.x5 >> 25); + this.x6 = (int)((int)this.x6 + carry5); + this.x5 = (int)((int)this.x5 - (carry5 << 25)); + int carry6 = (int)(this.x6 >> 26); + this.x7 = (int)((int)this.x7 + carry6); + this.x6 = (int)((int)this.x6 - (carry6 << 26)); + int carry7 = (int)(this.x7 >> 25); + this.x8 = (int)((int)this.x8 + carry7); + this.x7 = (int)((int)this.x7 - (carry7 << 25)); + int carry8 = (int)(this.x8 >> 26); + this.x9 = (int)((int)this.x9 + carry8); + this.x8 = (int)((int)this.x8 - (carry8 << 26)); + int carry9 = (int)(this.x9 >> 25); + this.x9 = (int)((int)this.x9 - (carry9 << 25)); + + output[ 0] = (byte)(this.x0 >> 0); + output[ 1] = (byte)(this.x0 >> 8); + output[ 2] = (byte)(this.x0 >> 16); + output[ 3] = (byte)((this.x0 >> 24) | (this.x1 << 2)); + output[ 4] = (byte)(this.x1 >> 6); + output[ 5] = (byte)(this.x1 >> 14); + output[ 6] = (byte)((this.x1 >> 22) | (this.x2 << 3)); + output[ 7] = (byte)(this.x2 >> 5); + output[ 8] = (byte)(this.x2 >> 13); + output[ 9] = (byte)((this.x2 >> 21) | (this.x3 << 5)); + output[10] = (byte)(this.x3 >> 3); + output[11] = (byte)(this.x3 >> 11); + output[12] = (byte)((this.x3 >> 19) | (this.x4 << 6)); + output[13] = (byte)(this.x4 >> 2); + output[14] = (byte)(this.x4 >> 10); + output[15] = (byte)(this.x4 >> 18); + output[16] = (byte)(this.x5 >> 0); + output[17] = (byte)(this.x5 >> 8); + output[18] = (byte)(this.x5 >> 16); + output[19] = (byte)((this.x5 >> 24) | (this.x6 << 1)); + output[20] = (byte)(this.x6 >> 7); + output[21] = (byte)(this.x6 >> 15); + output[22] = (byte)((this.x6 >> 23) | (this.x7 << 3)); + output[23] = (byte)(this.x7 >> 5); + output[24] = (byte)(this.x7 >> 13); + output[25] = (byte)((this.x7 >> 21) | (this.x8 << 4)); + output[26] = (byte)(this.x8 >> 4); + output[27] = (byte)(this.x8 >> 12); + output[28] = (byte)((this.x8 >> 20) | (this.x9 << 6)); + output[29] = (byte)(this.x9 >> 2); + output[30] = (byte)(this.x9 >> 10); + output[31] = (byte)(this.x9 >> 18); + } + + /// <summary> + /// Set the field element to `0` + /// </summary> + public static FieldElement Zero() + { + return new FieldElement(); + } + + /// <summary> + /// Set the field element to `1` + /// </summary> + public static FieldElement One() + { + FieldElement result = Zero(); + result.x0 = 1; + return result; + } + + /// <summary> + /// Add two field elements + /// </summary> + public static void Add(ref FieldElement output, ref FieldElement a, ref FieldElement b) + { + output.x0 = a.x0 + b.x0; + output.x1 = a.x1 + b.x1; + output.x2 = a.x2 + b.x2; + output.x3 = a.x3 + b.x3; + output.x4 = a.x4 + b.x4; + output.x5 = a.x5 + b.x5; + output.x6 = a.x6 + b.x6; + output.x7 = a.x7 + b.x7; + output.x8 = a.x8 + b.x8; + output.x9 = a.x9 + b.x9; + } + + /// <summary> + /// Subtract two field elements + /// </summary> + public static void Sub(ref FieldElement output, ref FieldElement a, ref FieldElement b) + { + output.x0 = a.x0 - b.x0; + output.x1 = a.x1 - b.x1; + output.x2 = a.x2 - b.x2; + output.x3 = a.x3 - b.x3; + output.x4 = a.x4 - b.x4; + output.x5 = a.x5 - b.x5; + output.x6 = a.x6 - b.x6; + output.x7 = a.x7 - b.x7; + output.x8 = a.x8 - b.x8; + output.x9 = a.x9 - b.x9; + } + + /// <summary> + /// Multiply two field elements + /// </summary> + public static void Multiply(ref FieldElement output, ref FieldElement a, ref FieldElement b) + { + int b1_19 = 19 * b.x1; + int b2_19 = 19 * b.x2; + int b3_19 = 19 * b.x3; + int b4_19 = 19 * b.x4; + int b5_19 = 19 * b.x5; + int b6_19 = 19 * b.x6; + int b7_19 = 19 * b.x7; + int b8_19 = 19 * b.x8; + int b9_19 = 19 * b.x9; + + int a1_2 = 2 * a.x1; + int a3_2 = 2 * a.x3; + int a5_2 = 2 * a.x5; + int a7_2 = 2 * a.x7; + int a9_2 = 2 * a.x9; + + long a0b0 = (long)a.x0 * (long)b.x0; + long a0b1 = (long)a.x0 * (long)b.x1; + long a0b2 = (long)a.x0 * (long)b.x2; + long a0b3 = (long)a.x0 * (long)b.x3; + long a0b4 = (long)a.x0 * (long)b.x4; + long a0b5 = (long)a.x0 * (long)b.x5; + long a0b6 = (long)a.x0 * (long)b.x6; + long a0b7 = (long)a.x0 * (long)b.x7; + long a0b8 = (long)a.x0 * (long)b.x8; + long a0b9 = (long)a.x0 * (long)b.x9; + long a1b0 = (long)a.x1 * (long)b.x0; + long a1b1_2 = (long)a1_2 * (long)b.x1; + long a1b2 = (long)a.x1 * (long)b.x2; + long a1b3_2 = (long)a1_2 * (long)b.x3; + long a1b4 = (long)a.x1 * (long)b.x4; + long a1b5_2 = (long)a1_2 * (long)b.x5; + long a1b6 = (long)a.x1 * (long)b.x6; + long a1b7_2 = (long)a1_2 * (long)b.x7; + long a1b8 = (long)a.x1 * (long)b.x8; + long a1b9_38 = (long)a1_2 * (long)b9_19; + long a2b0 = (long)a.x2 * (long)b.x0; + long a2b1 = (long)a.x2 * (long)b.x1; + long a2b2 = (long)a.x2 * (long)b.x2; + long a2b3 = (long)a.x2 * (long)b.x3; + long a2b4 = (long)a.x2 * (long)b.x4; + long a2b5 = (long)a.x2 * (long)b.x5; + long a2b6 = (long)a.x2 * (long)b.x6; + long a2b7 = (long)a.x2 * (long)b.x7; + long a2b8_19 = (long)a.x2 * (long)b8_19; + long a2b9_19 = (long)a.x2 * (long)b9_19; + long a3b0 = (long)a.x3 * (long)b.x0; + long a3b1_2 = (long)a3_2 * (long)b.x1; + long a3b2 = (long)a.x3 * (long)b.x2; + long a3b3_2 = (long)a3_2 * (long)b.x3; + long a3b4 = (long)a.x3 * (long)b.x4; + long a3b5_2 = (long)a3_2 * (long)b.x5; + long a3b6 = (long)a.x3 * (long)b.x6; + long a3b7_38 = (long)a3_2 * (long)b7_19; + long a3b8_19 = (long)a.x3 * (long)b8_19; + long a3b9_38 = (long)a3_2 * (long)b9_19; + long a4b0 = (long)a.x4 * (long)b.x0; + long a4b1 = (long)a.x4 * (long)b.x1; + long a4b2 = (long)a.x4 * (long)b.x2; + long a4b3 = (long)a.x4 * (long)b.x3; + long a4b4 = (long)a.x4 * (long)b.x4; + long a4b5 = (long)a.x4 * (long)b.x5; + long a4b6_19 = (long)a.x4 * (long)b6_19; + long a4b7_19 = (long)a.x4 * (long)b7_19; + long a4b8_19 = (long)a.x4 * (long)b8_19; + long a4b9_19 = (long)a.x4 * (long)b9_19; + long a5b0 = (long)a.x5 * (long)b.x0; + long a5b1_2 = (long)a5_2 * (long)b.x1; + long a5b2 = (long)a.x5 * (long)b.x2; + long a5b3_2 = (long)a5_2 * (long)b.x3; + long a5b4 = (long)a.x5 * (long)b.x4; + long a5b5_38 = (long)a5_2 * (long)b5_19; + long a5b6_19 = (long)a.x5 * (long)b6_19; + long a5b7_38 = (long)a5_2 * (long)b7_19; + long a5b8_19 = (long)a.x5 * (long)b8_19; + long a5b9_38 = (long)a5_2 * (long)b9_19; + long a6b0 = (long)a.x6 * (long)b.x0; + long a6b1 = (long)a.x6 * (long)b.x1; + long a6b2 = (long)a.x6 * (long)b.x2; + long a6b3 = (long)a.x6 * (long)b.x3; + long a6b4_19 = (long)a.x6 * (long)b4_19; + long a6b5_19 = (long)a.x6 * (long)b5_19; + long a6b6_19 = (long)a.x6 * (long)b6_19; + long a6b7_19 = (long)a.x6 * (long)b7_19; + long a6b8_19 = (long)a.x6 * (long)b8_19; + long a6b9_19 = (long)a.x6 * (long)b9_19; + long a7b0 = (long)a.x7 * (long)b.x0; + long a7b1_2 = (long)a7_2 * (long)b.x1; + long a7b2 = (long)a.x7 * (long)b.x2; + long a7b3_38 = (long)a7_2 * (long)b3_19; + long a7b4_19 = (long)a.x7 * (long)b4_19; + long a7b5_38 = (long)a7_2 * (long)b5_19; + long a7b6_19 = (long)a.x7 * (long)b6_19; + long a7b7_38 = (long)a7_2 * (long)b7_19; + long a7b8_19 = (long)a.x7 * (long)b8_19; + long a7b9_38 = (long)a7_2 * (long)b9_19; + long a8b0 = (long)a.x8 * (long)b.x0; + long a8b1 = (long)a.x8 * (long)b.x1; + long a8b2_19 = (long)a.x8 * (long)b2_19; + long a8b3_19 = (long)a.x8 * (long)b3_19; + long a8b4_19 = (long)a.x8 * (long)b4_19; + long a8b5_19 = (long)a.x8 * (long)b5_19; + long a8b6_19 = (long)a.x8 * (long)b6_19; + long a8b7_19 = (long)a.x8 * (long)b7_19; + long a8b8_19 = (long)a.x8 * (long)b8_19; + long a8b9_19 = (long)a.x8 * (long)b9_19; + long a9b0 = (long)a.x9 * (long)b.x0; + long a9b1_38 = (long)a9_2 * (long)b1_19; + long a9b2_19 = (long)a.x9 * (long)b2_19; + long a9b3_38 = (long)a9_2 * (long)b3_19; + long a9b4_19 = (long)a.x9 * (long)b4_19; + long a9b5_38 = (long)a9_2 * (long)b5_19; + long a9b6_19 = (long)a.x9 * (long)b6_19; + long a9b7_38 = (long)a9_2 * (long)b7_19; + long a9b8_19 = (long)a.x9 * (long)b8_19; + long a9b9_38 = (long)a9_2 * (long)b9_19; + + long h0 = a0b0 + a1b9_38 + a2b8_19 + a3b7_38 + a4b6_19 + a5b5_38 + a6b4_19 + a7b3_38 + a8b2_19 + a9b1_38; + long h1 = a0b1 + a1b0 + a2b9_19 + a3b8_19 + a4b7_19 + a5b6_19 + a6b5_19 + a7b4_19 + a8b3_19 + a9b2_19; + long h2 = a0b2 + a1b1_2 + a2b0 + a3b9_38 + a4b8_19 + a5b7_38 + a6b6_19 + a7b5_38 + a8b4_19 + a9b3_38; + long h3 = a0b3 + a1b2 + a2b1 + a3b0 + a4b9_19 + a5b8_19 + a6b7_19 + a7b6_19 + a8b5_19 + a9b4_19; + long h4 = a0b4 + a1b3_2 + a2b2 + a3b1_2 + a4b0 + a5b9_38 + a6b8_19 + a7b7_38 + a8b6_19 + a9b5_38; + long h5 = a0b5 + a1b4 + a2b3 + a3b2 + a4b1 + a5b0 + a6b9_19 + a7b8_19 + a8b7_19 + a9b6_19; + long h6 = a0b6 + a1b5_2 + a2b4 + a3b3_2 + a4b2 + a5b1_2 + a6b0 + a7b9_38 + a8b8_19 + a9b7_38; + long h7 = a0b7 + a1b6 + a2b5 + a3b4 + a4b3 + a5b2 + a6b1 + a7b0 + a8b9_19 + a9b8_19; + long h8 = a0b8 + a1b7_2 + a2b6 + a3b5_2 + a4b4 + a5b3_2 + a6b2 + a7b1_2 + a8b0 + a9b9_38; + long h9 = a0b9 + a1b8 + a2b7 + a3b6 + a4b5 + a5b4 + a6b3 + a7b2 + a8b1 + a9b0; + + long carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + long carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + long carry1 = (h1 + (1L << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + long carry5 = (h5 + (1L << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + long carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + long carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + long carry3 = (h3 + (1L << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + long carry7 = (h7 + (1L << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + long carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + long carry9 = (h9 + (1L << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + + output.x0 = (int)h0; + output.x1 = (int)h1; + output.x2 = (int)h2; + output.x3 = (int)h3; + output.x4 = (int)h4; + output.x5 = (int)h5; + output.x6 = (int)h6; + output.x7 = (int)h7; + output.x8 = (int)h8; + output.x9 = (int)h9; + } + + /// <summary> + /// Square a field element + /// </summary> + public static void Square(ref FieldElement output, ref FieldElement a) + { + int a0_2 = a.x0 * 2; + int a1_2 = a.x1 * 2; + int a2_2 = a.x2 * 2; + int a3_2 = a.x3 * 2; + int a4_2 = a.x4 * 2; + int a5_2 = a.x5 * 2; + int a6_2 = a.x6 * 2; + int a7_2 = a.x7 * 2; + + int a5_38 = a.x5 * 38; + int a6_19 = a.x6 * 19; + int a7_38 = a.x7 * 38; + int a8_19 = a.x8 * 19; + int a9_38 = a.x9 * 38; + + long a0a0 = (long)a.x0 * (long)a.x0; + long a0a1_2 = (long)a0_2 * (long)a.x1; + long a0a2_2 = (long)a0_2 * (long)a.x2; + long a0a3_2 = (long)a0_2 * (long)a.x3; + long a0a4_2 = (long)a0_2 * (long)a.x4; + long a0a5_2 = (long)a0_2 * (long)a.x5; + long a0a6_2 = (long)a0_2 * (long)a.x6; + long a0a7_2 = (long)a0_2 * (long)a.x7; + long a0a8_2 = (long)a0_2 * (long)a.x8; + long a0a9_2 = (long)a0_2 * (long)a.x9; + long a1a1_2 = (long)a1_2 * (long)a.x1; + long a1a2_2 = (long)a1_2 * (long)a.x2; + long a1a3_4 = (long)a1_2 * (long)a3_2; + long a1a4_2 = (long)a1_2 * (long)a.x4; + long a1a5_4 = (long)a1_2 * (long)a5_2; + long a1a6_2 = (long)a1_2 * (long)a.x6; + long a1a7_4 = (long)a1_2 * (long)a7_2; + long a1a8_2 = (long)a1_2 * (long)a.x8; + long a1a9_76 = (long)a1_2 * (long)a9_38; + long a2a2 = (long)a.x2 * (long)a.x2; + long a2a3_2 = (long)a2_2 * (long)a.x3; + long a2a4_2 = (long)a2_2 * (long)a.x4; + long a2a5_2 = (long)a2_2 * (long)a.x5; + long a2a6_2 = (long)a2_2 * (long)a.x6; + long a2a7_2 = (long)a2_2 * (long)a.x7; + long a2a8_38 = (long)a2_2 * (long)a8_19; + long a2a9_38 = (long)a.x2 * (long)a9_38; + long a3a3_2 = (long)a3_2 * (long)a.x3; + long a3a4_2 = (long)a3_2 * (long)a.x4; + long a3a5_4 = (long)a3_2 * (long)a5_2; + long a3a6_2 = (long)a3_2 * (long)a.x6; + long a3a7_76 = (long)a3_2 * (long)a7_38; + long a3a8_38 = (long)a3_2 * (long)a8_19; + long a3a9_76 = (long)a3_2 * (long)a9_38; + long a4a4 = (long)a.x4 * (long)a.x4; + long a4a5_2 = (long)a4_2 * (long)a.x5; + long a4a6_38 = (long)a4_2 * (long)a6_19; + long a4a7_38 = (long)a.x4 * (long)a7_38; + long a4a8_38 = (long)a4_2 * (long)a8_19; + long a4a9_38 = (long)a.x4 * (long)a9_38; + long a5a5_38 = (long)a.x5 * (long)a5_38; + long a5a6_38 = (long)a5_2 * (long)a6_19; + long a5a7_76 = (long)a5_2 * (long)a7_38; + long a5a8_38 = (long)a5_2 * (long)a8_19; + long a5a9_76 = (long)a5_2 * (long)a9_38; + long a6a6_19 = (long)a.x6 * (long)a6_19; + long a6a7_38 = (long)a.x6 * (long)a7_38; + long a6a8_38 = (long)a6_2 * (long)a8_19; + long a6a9_38 = (long)a.x6 * (long)a9_38; + long a7a7_38 = (long)a.x7 * (long)a7_38; + long a7a8_38 = (long)a7_2 * (long)a8_19; + long a7a9_76 = (long)a7_2 * (long)a9_38; + long a8a8_19 = (long)a.x8 * (long)a8_19; + long a8a9_38 = (long)a.x8 * (long)a9_38; + long a9a9_38 = (long)a.x9 * (long)a9_38; + + long h0 = a0a0 + a1a9_76 + a2a8_38 + a3a7_76 + a4a6_38 + a5a5_38; + long h1 = a0a1_2 + a2a9_38 + a3a8_38 + a4a7_38 + a5a6_38; + long h2 = a0a2_2 + a1a1_2 + a3a9_76 + a4a8_38 + a5a7_76 + a6a6_19; + long h3 = a0a3_2 + a1a2_2 + a4a9_38 + a5a8_38 + a6a7_38; + long h4 = a0a4_2 + a1a3_4 + a2a2 + a5a9_76 + a6a8_38 + a7a7_38; + long h5 = a0a5_2 + a1a4_2 + a2a3_2 + a6a9_38 + a7a8_38; + long h6 = a0a6_2 + a1a5_4 + a2a4_2 + a3a3_2 + a7a9_76 + a8a8_19; + long h7 = a0a7_2 + a1a6_2 + a2a5_2 + a3a4_2 + a8a9_38; + long h8 = a0a8_2 + a1a7_4 + a2a6_2 + a3a5_4 + a4a4 + a9a9_38; + long h9 = a0a9_2 + a1a8_2 + a2a7_2 + a3a6_2 + a4a5_2; + + long carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + long carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + long carry1 = (h1 + (1L << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + long carry5 = (h5 + (1L << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + long carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + long carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + long carry3 = (h3 + (1L << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + long carry7 = (h7 + (1L << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + long carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + long carry9 = (h9 + (1L << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + + output.x0 = (int)h0; + output.x1 = (int)h1; + output.x2 = (int)h2; + output.x3 = (int)h3; + output.x4 = (int)h4; + output.x5 = (int)h5; + output.x6 = (int)h6; + output.x7 = (int)h7; + output.x8 = (int)h8; + output.x9 = (int)h9; + } + + /// <summary> + /// Multiplay a field element by 121666 + /// </summary> + public static void Multiply121666(ref FieldElement output, ref FieldElement a) + { + long h0 = (long)a.x0 * 121666L; + long h1 = (long)a.x1 * 121666L; + long h2 = (long)a.x2 * 121666L; + long h3 = (long)a.x3 * 121666L; + long h4 = (long)a.x4 * 121666L; + long h5 = (long)a.x5 * 121666L; + long h6 = (long)a.x6 * 121666L; + long h7 = (long)a.x7 * 121666L; + long h8 = (long)a.x8 * 121666L; + long h9 = (long)a.x9 * 121666L; + + long carry9 = (h9 + (1L<<24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + long carry1 = (h1 + (1L<<24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + long carry3 = (h3 + (1L<<24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + long carry5 = (h5 + (1L<<24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + long carry7 = (h7 + (1L<<24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + long carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + long carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + long carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + long carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + long carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + output.x0 = (int)h0; + output.x1 = (int)h1; + output.x2 = (int)h2; + output.x3 = (int)h3; + output.x4 = (int)h4; + output.x5 = (int)h5; + output.x6 = (int)h6; + output.x7 = (int)h7; + output.x8 = (int)h8; + output.x9 = (int)h9; + } + + /// <summary> + /// Invert a field element + /// </summary> + public static void Invert(ref FieldElement output, ref FieldElement a) + { + FieldElement t0 = new FieldElement(); + Square(ref t0, ref a); + + FieldElement t1 = new FieldElement(); + Square(ref t1, ref t0); + Square(ref t1, ref t1); + + FieldElement t2= new FieldElement(); + Multiply(ref t1, ref a, ref t1); + Multiply(ref t0, ref t0, ref t1); + Square(ref t2, ref t0); + //Square(ref t2, ref t2); + + Multiply(ref t1, ref t1, ref t2); + Square(ref t2, ref t1); + for (int ii = 1; ii < 5; ++ii) + { + Square(ref t2, ref t2); + } + + Multiply(ref t1, ref t2, ref t1); + Square(ref t2, ref t1); + for (int ii = 1; ii < 10; ++ii) + { + Square(ref t2, ref t2); + } + + FieldElement t3 = new FieldElement(); + Multiply(ref t2, ref t2, ref t1); + Square(ref t3, ref t2); + for (int ii = 1; ii < 20; ++ii) + { + Square(ref t3, ref t3); + } + + Multiply(ref t2, ref t3, ref t2); + Square(ref t2, ref t2); + for (int ii = 1; ii < 10; ++ii) + { + Square(ref t2, ref t2); + } + + Multiply(ref t1, ref t2, ref t1); + Square(ref t2, ref t1); + for (int ii = 1; ii < 50; ++ii) + { + Square(ref t2, ref t2); + } + + Multiply(ref t2, ref t2, ref t1); + Square(ref t3, ref t2); + for (int ii = 1; ii < 100; ++ii) + { + Square(ref t3, ref t3); + } + + Multiply(ref t2, ref t3, ref t2); + Square(ref t2, ref t2); + for (int ii = 1; ii < 50; ++ii) + { + Square(ref t2, ref t2); + } + + Multiply(ref t1, ref t2, ref t1); + Square(ref t1, ref t1); + for (int ii = 1; ii < 5; ++ii) + { + Square(ref t1, ref t1); + } + + Multiply(ref output, ref t1, ref t0); + } + + /// <summary> + /// Swaps `a` and `b` if `swap` is 1 + /// </summary> + public static void ConditionalSwap(ref FieldElement a, ref FieldElement b, int swap) + { + Debug.Assert(swap == 0 || swap == 1); + swap = -swap; + + FieldElement temp = new FieldElement + { + x0 = swap & (a.x0 ^ b.x0), + x1 = swap & (a.x1 ^ b.x1), + x2 = swap & (a.x2 ^ b.x2), + x3 = swap & (a.x3 ^ b.x3), + x4 = swap & (a.x4 ^ b.x4), + x5 = swap & (a.x5 ^ b.x5), + x6 = swap & (a.x6 ^ b.x6), + x7 = swap & (a.x7 ^ b.x7), + x8 = swap & (a.x8 ^ b.x8), + x9 = swap & (a.x9 ^ b.x9), + }; + + a.x0 ^= temp.x0; + a.x1 ^= temp.x1; + a.x2 ^= temp.x2; + a.x3 ^= temp.x3; + a.x4 ^= temp.x4; + a.x5 ^= temp.x5; + a.x6 ^= temp.x6; + a.x7 ^= temp.x7; + a.x8 ^= temp.x8; + a.x9 ^= temp.x9; + + b.x0 ^= temp.x0; + b.x1 ^= temp.x1; + b.x2 ^= temp.x2; + b.x3 ^= temp.x3; + b.x4 ^= temp.x4; + b.x5 ^= temp.x5; + b.x6 ^= temp.x6; + b.x7 ^= temp.x7; + b.x8 ^= temp.x8; + b.x9 ^= temp.x9; + } + } + } +} |