Added async methods
This commit is contained in:
parent
b5f780343b
commit
d1305e606c
@ -33,6 +33,28 @@ namespace Ed25519
|
||||
return point;
|
||||
}
|
||||
|
||||
public static EdPoint DecodePoint(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 is not on curve");
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> EncodePoint()
|
||||
{
|
||||
var nout = this.Y.EncodeInt();
|
||||
|
@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Encrypter;
|
||||
|
||||
namespace Ed25519
|
||||
@ -16,14 +17,36 @@ namespace Ed25519
|
||||
return sha512.ComputeHash(data.ToArray());
|
||||
}
|
||||
|
||||
internal static async Task<byte[]> ComputeHashAsync(this byte[] data)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
using var sha512 = SHA512.Create();
|
||||
return sha512.ComputeHash(data);
|
||||
});
|
||||
}
|
||||
|
||||
internal static ReadOnlySpan<byte> ComputeHash(this Stream inputStream)
|
||||
{
|
||||
if(inputStream.CanSeek)
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using var sha512 = SHA512.Create();
|
||||
return sha512.ComputeHash(inputStream);
|
||||
}
|
||||
|
||||
internal static async Task<byte[]> ComputeHashAsync(this Stream inputStream)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
if(inputStream.CanSeek)
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using var sha512 = SHA512.Create();
|
||||
return sha512.ComputeHash(inputStream);
|
||||
});
|
||||
}
|
||||
|
||||
internal static BigInteger Mod(this BigInteger number, BigInteger modulo)
|
||||
{
|
||||
var result = number % modulo;
|
||||
@ -31,10 +54,7 @@ namespace Ed25519
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static BigInteger Inv(this BigInteger number)
|
||||
{
|
||||
return number.ExpMod(Constants.QM2, Constants.Q);
|
||||
}
|
||||
internal static BigInteger Inv(this BigInteger number) => number.ExpMod(Constants.QM2, Constants.Q);
|
||||
|
||||
internal static BigInteger RecoverX(this BigInteger y)
|
||||
{
|
||||
@ -96,10 +116,9 @@ namespace Ed25519
|
||||
return nout;
|
||||
}
|
||||
|
||||
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data)
|
||||
{
|
||||
return new BigInteger(data) & Constants.U_N;
|
||||
}
|
||||
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data) => new BigInteger(data) & Constants.U_N;
|
||||
|
||||
internal static BigInteger DecodeInt(this byte[] data) => new BigInteger(data) & Constants.U_N;
|
||||
|
||||
internal static BigInteger HashInt(this MemoryStream data)
|
||||
{
|
||||
@ -120,16 +139,34 @@ namespace Ed25519
|
||||
return hashSum;
|
||||
}
|
||||
|
||||
internal static int GetBit(this ReadOnlySpan<byte> data, int index)
|
||||
internal static async Task<BigInteger> HashIntAsync(this MemoryStream data)
|
||||
{
|
||||
return data[index / 8] >> (index % 8) & 1;
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var hash = await data.ComputeHashAsync();
|
||||
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) => data[index / 8] >> (index % 8) & 1;
|
||||
|
||||
internal static int GetBit(this byte[] data, int index) => data[index / 8] >> (index % 8) & 1;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the public key out of the given private key. The private key must be valid, i.e. must consist of 32 bytes.
|
||||
/// </summary>
|
||||
/// <param name="privateKey">The private key.</param>
|
||||
/// <returns>The corresponding public key.</returns>
|
||||
/// <returns>The corresponding public key. Returns an empty ReadOnlySpan, when the private key's length is incorrect.</returns>
|
||||
public static ReadOnlySpan<byte> ExtractPublicKey(this ReadOnlySpan<byte> privateKey)
|
||||
{
|
||||
if(privateKey.Length != 32)
|
||||
@ -155,9 +192,31 @@ namespace Ed25519
|
||||
/// </summary>
|
||||
/// <param name="privateKey">The private key.</param>
|
||||
/// <returns>The corresponding public key.</returns>
|
||||
public static ReadOnlySpan<byte> ExtractPublicKey(this Span<byte> privateKey)
|
||||
public static ReadOnlySpan<byte> ExtractPublicKey(this Span<byte> privateKey) => new ReadOnlySpan<byte>(privateKey.ToArray()).ExtractPublicKey();
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the public key out of the given private key. The private key must be valid, i.e. must consist of 32 bytes.
|
||||
/// </summary>
|
||||
/// <param name="privateKey">The private key.</param>
|
||||
/// <returns>The corresponding public key. Returns null, when the private key's length is incorrect.</returns>
|
||||
public static async Task<byte[]> ExtractPublicKeyAsync(this byte[] privateKey)
|
||||
{
|
||||
return new ReadOnlySpan<byte>(privateKey.ToArray()).ExtractPublicKey();
|
||||
if (privateKey.Length != 32)
|
||||
return null;
|
||||
|
||||
var hash = await privateKey.ComputeHashAsync();
|
||||
var a = Constants.TWO_POW_BIT_LENGTH_MINUS_TWO;
|
||||
for (var i = 3; i < Constants.BIT_LENGTH - 2; i++)
|
||||
{
|
||||
var bit = hash.GetBit(i);
|
||||
if (bit != 0)
|
||||
{
|
||||
a += Constants.TWO_POW_CACHE[i];
|
||||
}
|
||||
}
|
||||
|
||||
var bigA = Constants.B.ScalarMul(a);
|
||||
return bigA.EncodePoint().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -165,10 +224,14 @@ namespace Ed25519
|
||||
/// </summary>
|
||||
/// <param name="key">The chosen key</param>
|
||||
/// <param name="filename">The desired file</param>
|
||||
public static void WriteKey(this ReadOnlySpan<byte> key, string filename)
|
||||
{
|
||||
File.WriteAllBytes(filename, key.ToArray());
|
||||
}
|
||||
public static void WriteKey(this ReadOnlySpan<byte> key, string filename) => File.WriteAllBytes(filename, key.ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Writes a given key to a file.
|
||||
/// </summary>
|
||||
/// <param name="key">The chosen key</param>
|
||||
/// <param name="filename">The desired file</param>
|
||||
public static async Task WriteKeyAsync(this byte[] key, string filename) => await File.WriteAllBytesAsync(filename, key);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts an encrypted private key.
|
||||
@ -184,5 +247,20 @@ namespace Ed25519
|
||||
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts an encrypted private key.
|
||||
/// </summary>
|
||||
/// <param name="privateKey">The encrypted private key.</param>
|
||||
/// <param name="password">The matching password.</param>
|
||||
/// <returns>The decrypted private key.</returns>
|
||||
public static async Task<byte[]> DecryptPrivateKeyAsync(this byte[] privateKey, string password)
|
||||
{
|
||||
await using var inputStream = new MemoryStream(privateKey);
|
||||
await using var outputStream = new MemoryStream();
|
||||
await CryptoProcessor.Decrypt(inputStream, outputStream, password);
|
||||
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,13 +34,42 @@ namespace Ed25519
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random private key.
|
||||
/// </summary>
|
||||
/// <param name="password">An optional password to encrypt the key.</param>
|
||||
/// <returns>The private key.</returns>
|
||||
public static async Task<byte[]> GeneratePrivateKeyAsync(string password = "")
|
||||
{
|
||||
var privateKey = new byte[32];
|
||||
await Task.Run(() => RandomNumberGenerator.Create().GetBytes(privateKey));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
await using var inputStream = new MemoryStream(privateKey, false);
|
||||
await using var outputStream = new MemoryStream();
|
||||
|
||||
await CryptoProcessor.Encrypt(inputStream, outputStream, password);
|
||||
privateKey = outputStream.ToArray();
|
||||
}
|
||||
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a key (public or private key) from a file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The entire path to the corresponding file.</param>
|
||||
/// <returns>The desired key.</returns>
|
||||
/// <returns>The desired key. Returns an empty ReadOnlySpan, when the file does not exist.</returns>
|
||||
public static ReadOnlySpan<byte> LoadKey(string filename) => !File.Exists(filename) ? ReadOnlySpan<byte>.Empty : File.ReadAllBytes(filename);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a key (public or private key) from a file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The entire path to the corresponding file.</param>
|
||||
/// <returns>The desired key. Returns null, when the file does not exist.</returns>
|
||||
public static async Task<byte[]> LoadKeyAsync(string filename) => !File.Exists(filename) ? null : await File.ReadAllBytesAsync(filename);
|
||||
|
||||
/// <summary>
|
||||
/// Signs a message with the given private and public keys.
|
||||
/// </summary>
|
||||
@ -101,6 +130,66 @@ namespace Ed25519
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs a message with the given private and public keys.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to sign.</param>
|
||||
/// <param name="privateKey">The desired private key.</param>
|
||||
/// <param name="publicKey">The corresponding public key.</param>
|
||||
/// <returns>The derived signature. It's length is 64 bytes.</returns>
|
||||
public static async Task<byte[]> SignAsync(byte[] message, byte[] privateKey, byte[] publicKey)
|
||||
{
|
||||
if (privateKey.Length != Constants.BIT_LENGTH / 8)
|
||||
throw new ArgumentException($"Private key length is wrong. Got {privateKey.Length} instead of {Constants.BIT_LENGTH / 8}.");
|
||||
|
||||
if (publicKey.Length != Constants.BIT_LENGTH / 8)
|
||||
throw new ArgumentException($"Public key length is wrong. Got {publicKey.Length} instead of {Constants.BIT_LENGTH / 8}.");
|
||||
|
||||
var privateKeyHash = await privateKey.ComputeHashAsync();
|
||||
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;
|
||||
await using (var rSub = new MemoryStream((Constants.BIT_LENGTH / 8) + message.Length))
|
||||
{
|
||||
await rSub.WriteAsync(privateKeyHash[(Constants.BIT_LENGTH / 8)..]);
|
||||
await rSub.WriteAsync(message);
|
||||
await rSub.FlushAsync();
|
||||
|
||||
r = await rSub.HashIntAsync();
|
||||
}
|
||||
|
||||
var bigR = Constants.B.ScalarMul(r);
|
||||
|
||||
BigInteger s;
|
||||
var encodedBigR = bigR.EncodePoint().ToArray();
|
||||
await using (var sTemp = new MemoryStream(encodedBigR.Length + publicKey.Length + message.Length))
|
||||
{
|
||||
await sTemp.WriteAsync(encodedBigR);
|
||||
await sTemp.WriteAsync(publicKey);
|
||||
await sTemp.WriteAsync(message);
|
||||
await sTemp.FlushAsync();
|
||||
|
||||
s = (r + await sTemp.HashIntAsync() * privateKeyBits).Mod(Constants.L);
|
||||
}
|
||||
|
||||
await using (var nOut = new MemoryStream(64))
|
||||
{
|
||||
await nOut.WriteAsync(encodedBigR);
|
||||
await nOut.WriteAsync(s.EncodeInt().ToArray());
|
||||
await nOut.FlushAsync();
|
||||
|
||||
return nOut.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a given signature by means of the given public key.
|
||||
/// </summary>
|
||||
@ -141,5 +230,46 @@ namespace Ed25519
|
||||
|
||||
return ra.X.Equals(rb.X) && ra.Y.Equals(rb.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a given signature by means of the given public key.
|
||||
/// </summary>
|
||||
/// <param name="signature">The signature to validate.</param>
|
||||
/// <param name="message">The corresponding message.</param>
|
||||
/// <param name="publicKey">The used public key.</param>
|
||||
/// <returns>Returns true when the combination of signature + message is valid.</returns>
|
||||
public static async Task<bool> ValidateAsync(byte[] signature, byte[] message, byte[] publicKey)
|
||||
{
|
||||
if (signature.Length != Constants.BIT_LENGTH / 4)
|
||||
throw new ArgumentException($"Signature length is wrong. Got {signature.Length} instead of {Constants.BIT_LENGTH / 4}.");
|
||||
|
||||
if (publicKey.Length != Constants.BIT_LENGTH / 8)
|
||||
throw new ArgumentException($"Public key length is wrong. Got {publicKey.Length} instead of {Constants.BIT_LENGTH / 8}.");
|
||||
|
||||
var signatureSliceLeft = signature[..(Constants.BIT_LENGTH / 8)];
|
||||
var pointSignatureLeft = EdPoint.DecodePoint(signatureSliceLeft);
|
||||
var pointPublicKey = EdPoint.DecodePoint(publicKey);
|
||||
|
||||
var signatureSliceRight = signature[(Constants.BIT_LENGTH / 8)..];
|
||||
var signatureRight = signatureSliceRight.DecodeInt();
|
||||
var encodedSignatureLeftPoint = pointSignatureLeft.EncodePoint().ToArray();
|
||||
|
||||
BigInteger h;
|
||||
await using (var sTemp = new MemoryStream(encodedSignatureLeftPoint.Length + publicKey.Length + message.Length))
|
||||
{
|
||||
await sTemp.WriteAsync(encodedSignatureLeftPoint);
|
||||
await sTemp.WriteAsync(publicKey);
|
||||
await sTemp.WriteAsync(message);
|
||||
await sTemp.FlushAsync();
|
||||
|
||||
h = await sTemp.HashIntAsync();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Ed25519
|
||||
@ -191,7 +192,7 @@ namespace Ed25519
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWritingKeys()
|
||||
public void TestWritingAndLoadingKeys()
|
||||
{
|
||||
var tempFilePrivate = Path.GetTempFileName();
|
||||
var tempFilePublic = Path.GetTempFileName();
|
||||
@ -231,6 +232,22 @@ namespace Ed25519
|
||||
Assert.That(publicKeyFromDecrypted.ToArray(), Is.Not.EqualTo(publicKeyFromEncrypted.ToArray()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExtractPublicKeyFromTooShortPrivateKey()
|
||||
{
|
||||
var privateKey= Signer.GeneratePrivateKey()[..6]; // Six first bytes from private key!
|
||||
var publicKey = privateKey.ExtractPublicKey();
|
||||
Assert.That(publicKey == ReadOnlySpan<byte>.Empty, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestExtractPublicKeyFromTooShortPrivateKeyAsync()
|
||||
{
|
||||
var privateKey = (await Signer.GeneratePrivateKeyAsync())[..6]; // Six first bytes from private key!
|
||||
var publicKey = await privateKey.ExtractPublicKeyAsync();
|
||||
Assert.That(publicKey, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSigningBehaviourPrivateKeyWithPassword()
|
||||
{
|
||||
@ -308,6 +325,20 @@ namespace Ed25519
|
||||
Assert.That(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoadingNotExistingKey()
|
||||
{
|
||||
var key = Signer.LoadKey(Guid.NewGuid().ToString());
|
||||
Assert.That(key == ReadOnlySpan<byte>.Empty, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestLoadingNotExistingKeyAsync()
|
||||
{
|
||||
var key = await Signer.LoadKeyAsync(Guid.NewGuid().ToString());
|
||||
Assert.That(key, Is.Null);
|
||||
}
|
||||
|
||||
// See https://tools.ietf.org/html/rfc8032#section-7.1
|
||||
[Test]
|
||||
public void TestRFC8032Test01EmptyMessage()
|
||||
|
Loading…
Reference in New Issue
Block a user