This commit is contained in:
Thorsten Sommer 2020-01-03 19:28:12 +01:00
parent a39c5b0a6a
commit 9d1236a41a
4 changed files with 340 additions and 0 deletions

42
Ed25519/Constants.cs Normal file
View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Ed25519
{
internal static class Constants
{
//
// Compiler-time constants:
//
internal const int BIT_LENGTH = 256;
//
// Run-time constants:
//
internal static readonly BigInteger Q = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819949");
internal static readonly BigInteger QM2 = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819947");
internal static readonly BigInteger QP3 = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819952");
internal static readonly BigInteger L = BigInteger.Parse("7237005577332262213973186563042994240857116359379907606001950938285454250989");
internal static readonly BigInteger D = BigInteger.Parse("-4513249062541557337682894930092624173785641285191125241628941591882900924598840740");
internal static readonly BigInteger I = BigInteger.Parse("19681161376707505956807079304988542015446066515923890162744021073123829784752");
internal static readonly BigInteger U_N = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967");
internal static readonly BigInteger TWO = new BigInteger(2);
internal static readonly BigInteger EIGHT = new BigInteger(8);
//
// Pre-calculated values:
//
internal static readonly EdPoint B = new EdPoint
{
X = BigInteger.Parse("15112221349535400772501151409588531511454012693041857206046113283949847762202").Mod(Q),
Y = BigInteger.Parse("46316835694926478169428394003475163141307993866256225615783033603165251855960").Mod(Q),
};
internal static readonly BigInteger RECOVER_X_EXP = Constants.QP3 / Constants.EIGHT;
internal static readonly BigInteger TWO_POW_BIT_LENGTH_MINUS_TWO = BigInteger.Pow(2, BIT_LENGTH - 2);
internal static readonly BigInteger[] TWO_POW_CACHE = Enumerable.Range(0, 2 * BIT_LENGTH).Select(i => BigInteger.Pow(2, i)).ToArray();
}
}

101
Ed25519/EdPoint.cs Normal file
View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
namespace Ed25519
{
internal struct EdPoint
{
public BigInteger X { get; set; }
public BigInteger Y { get; set; }
public static EdPoint DecodePoint(ReadOnlySpan<byte> pointBytes)
{
var y = new BigInteger(pointBytes) & Constants.U_N;
var x = y.RecoverX();
if ((x.IsEven ? 0 : 1) != pointBytes.GetBit(Constants.BIT_LENGTH - 1))
{
x = Constants.Q - x;
}
var point = new EdPoint
{
X = x,
Y = y,
};
if (!point.IsOnCurve())
throw new ArgumentException("Decoding point that is not on curve");
return point;
}
public ReadOnlySpan<byte> EncodePoint()
{
var nout = this.Y.EncodeInt();
nout[^1] |= this.X.IsEven ? (byte)0 : (byte)0x80;
return nout;
}
public EdPoint Edwards(EdPoint point2)
{
var xx12 = this.X * point2.X;
var yy12 = this.Y * point2.Y;
var dTemp = Constants.D * xx12 * yy12;
var x3 = (this.X * point2.Y + point2.X * this.Y) * (1 + dTemp).Inv();
var y3 = (this.Y * point2.Y + xx12) * (1 - dTemp).Inv();
return new EdPoint
{
X = x3.Mod(Constants.Q),
Y = y3.Mod(Constants.Q),
};
}
public EdPoint ScalarMul(BigInteger e)
{
if (e.Equals(BigInteger.Zero))
{
return new EdPoint
{
X = BigInteger.Zero,
Y = BigInteger.One,
};
}
var q = this.ScalarMul(e / Constants.TWO);
q = q.EdwardsSquare();
return e.IsEven ? q : q.Edwards(this);
}
public EdPoint EdwardsSquare()
{
var xx = this.X * this.X;
var yy = this.Y * this.Y;
var dTemp = Constants.D * xx * yy;
var x3 = 2 * this.X * this.Y * (1 + dTemp).Inv();
var y3 = (yy + xx) * (1 - dTemp).Inv();
return new EdPoint
{
X = x3.Mod(Constants.Q),
Y = y3.Mod(Constants.Q),
};
}
public bool IsOnCurve()
{
var xx = this.X * this.X;
var yy = this.Y * this.Y;
var dxxyy = Constants.D * yy * xx;
return (yy - xx - dxxyy - 1).Mod(Constants.Q).Equals(BigInteger.Zero);
}
}
}

107
Ed25519/Extensions.cs Normal file
View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
namespace Ed25519
{
public static class Extensions
{
internal static ReadOnlySpan<byte> ComputeHash(this ReadOnlySpan<byte> data)
{
using var sha512 = SHA512.Create();
return sha512.ComputeHash(data.ToArray());
}
internal static ReadOnlySpan<byte> ComputeHash(this Stream inputStream)
{
using var sha512 = SHA512.Create();
return sha512.ComputeHash(inputStream);
}
internal static BigInteger Mod(this BigInteger number, BigInteger modulo)
{
var result = number % modulo;
return result < 0 ? result + modulo : result;
}
internal static BigInteger Inv(this BigInteger number)
{
return number.ExpMod(2, Constants.Q);
}
internal static BigInteger RecoverX(this BigInteger y)
{
var y2 = y * y;
var xx = (y2 - 1) * (Constants.D * y2 + 1).Inv();
var x = xx.ExpMod(Constants.RECOVER_X_EXP, Constants.Q);
if (!(x * x - xx).Mod(Constants.Q).Equals(BigInteger.Zero))
{
x = (x * Constants.I).Mod(Constants.Q);
}
if (!x.IsEven)
{
x = Constants.Q - x;
}
return x;
}
internal static BigInteger ExpMod(this BigInteger number, BigInteger exponent, BigInteger modulo)
{
if (exponent.Equals(BigInteger.Zero))
{
return BigInteger.One;
}
var result = BigInteger.Pow(number.ExpMod(exponent / Constants.TWO, modulo), 2).Mod(modulo);
if (exponent.IsEven)
return result;
result *= number;
result = result.Mod(modulo);
return result;
}
internal static Span<byte> EncodeInt(this BigInteger number)
{
var nin = number.ToByteArray();
var nout = new byte[Math.Max(nin.Length, 32)];
Array.Copy(nin, nout, nin.Length);
return nout;
}
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data)
{
return new BigInteger(data) & Constants.U_N;
}
internal static BigInteger HashInt(this MemoryStream data)
{
var hash = data.ComputeHash();
var hashSum = BigInteger.Zero;
for (var i = 0; i < 2 * Constants.BIT_LENGTH; i++)
{
var bit = hash.GetBit(i);
if (bit != 0)
{
hashSum += Constants.TWO_POW_CACHE[i];
}
}
return hashSum;
}
internal static int GetBit(this ReadOnlySpan<byte> data, int index)
{
return data[index / 8] >> (index % 8) & 1;
}
}
}

90
Ed25519/Signer.cs Normal file
View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
namespace Ed25519
{
public static class Signer
{
public static ReadOnlySpan<byte> Sign(ReadOnlySpan<byte> message, ReadOnlySpan<byte> privateKey, ReadOnlySpan<byte> publicKey)
{
var privateKeyHash = privateKey.ComputeHash();
var privateKeyBits = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
{
var bit = privateKeyHash.GetBit(i);
if (bit != 0)
{
privateKeyBits += Constants.TWO_POW_CACHE[i];
}
}
BigInteger r;
using (var rSub = new MemoryStream((Constants.BIT_LENGTH / 8) + message.Length))
{
rSub.Write(privateKeyHash[(privateKeyHash.Length/2)..]);
rSub.Write(message);
rSub.Flush();
r = rSub.HashInt();
}
var bigR = Constants.B.ScalarMul(r);
BigInteger s;
var encodedBigR = bigR.EncodePoint();
using (var sTemp = new MemoryStream(encodedBigR.Length + publicKey.Length + message.Length))
{
sTemp.Write(encodedBigR);
sTemp.Write(publicKey);
sTemp.Write(message);
sTemp.Flush();
s = (r + sTemp.HashInt() * privateKeyBits).Mod(Constants.L);
}
using (var nOut = new MemoryStream(64))
{
nOut.Write(encodedBigR);
nOut.Write(s.EncodeInt());
return nOut.ToArray();
}
}
public static bool Validate(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> message, ReadOnlySpan<byte> publicKey)
{
if (signature.Length != Constants.BIT_LENGTH / 4)
throw new ArgumentException("Signature length is wrong");
if (publicKey.Length != Constants.BIT_LENGTH / 8)
throw new ArgumentException("Public key length is wrong");
var signatureSliceLeft = signature[..(Constants.BIT_LENGTH / 8)];
var pointSignatureLeft = EdPoint.DecodePoint(signatureSliceLeft);
var pointPublicKey = EdPoint.DecodePoint(publicKey);
var signatureSliceRight = signature[(signature.Length/2)..];
var signatureRight = signatureSliceRight.DecodeInt();
var encodedSignatureLeftPoint = pointSignatureLeft.EncodePoint();
BigInteger h;
using (var sTemp = new MemoryStream(encodedSignatureLeftPoint.Length + publicKey.Length + message.Length))
{
sTemp.Write(encodedSignatureLeftPoint);
sTemp.Write(publicKey);
sTemp.Write(message);
sTemp.Flush();
h = sTemp.HashInt();
}
var ra = Constants.B.ScalarMul(signatureRight);
var ah = pointPublicKey.ScalarMul(h);
var rb = pointSignatureLeft.Edwards(ah);
return ra.X.Equals(rb.X) && ra.Y.Equals(rb.Y);
}
}
}