Added async methods
This commit is contained in:
parent
b5f780343b
commit
d1305e606c
@ -33,6 +33,28 @@ namespace Ed25519
|
|||||||
return point;
|
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()
|
public ReadOnlySpan<byte> EncodePoint()
|
||||||
{
|
{
|
||||||
var nout = this.Y.EncodeInt();
|
var nout = this.Y.EncodeInt();
|
||||||
|
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Encrypter;
|
using Encrypter;
|
||||||
|
|
||||||
namespace Ed25519
|
namespace Ed25519
|
||||||
@ -16,14 +17,36 @@ namespace Ed25519
|
|||||||
return sha512.ComputeHash(data.ToArray());
|
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)
|
internal static ReadOnlySpan<byte> ComputeHash(this Stream inputStream)
|
||||||
{
|
{
|
||||||
|
if(inputStream.CanSeek)
|
||||||
inputStream.Seek(0, SeekOrigin.Begin);
|
inputStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
using var sha512 = SHA512.Create();
|
using var sha512 = SHA512.Create();
|
||||||
return sha512.ComputeHash(inputStream);
|
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)
|
internal static BigInteger Mod(this BigInteger number, BigInteger modulo)
|
||||||
{
|
{
|
||||||
var result = number % modulo;
|
var result = number % modulo;
|
||||||
@ -31,10 +54,7 @@ namespace Ed25519
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static BigInteger Inv(this BigInteger number)
|
internal static BigInteger Inv(this BigInteger number) => number.ExpMod(Constants.QM2, Constants.Q);
|
||||||
{
|
|
||||||
return number.ExpMod(Constants.QM2, Constants.Q);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static BigInteger RecoverX(this BigInteger y)
|
internal static BigInteger RecoverX(this BigInteger y)
|
||||||
{
|
{
|
||||||
@ -96,10 +116,9 @@ namespace Ed25519
|
|||||||
return nout;
|
return nout;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data)
|
internal static BigInteger DecodeInt(this ReadOnlySpan<byte> data) => new BigInteger(data) & Constants.U_N;
|
||||||
{
|
|
||||||
return 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)
|
internal static BigInteger HashInt(this MemoryStream data)
|
||||||
{
|
{
|
||||||
@ -120,16 +139,34 @@ namespace Ed25519
|
|||||||
return hashSum;
|
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>
|
/// <summary>
|
||||||
/// Extracts the public key out of the given private key. The private key must be valid, i.e. must consist of 32 bytes.
|
/// Extracts the public key out of the given private key. The private key must be valid, i.e. must consist of 32 bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="privateKey">The private key.</param>
|
/// <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)
|
public static ReadOnlySpan<byte> ExtractPublicKey(this ReadOnlySpan<byte> privateKey)
|
||||||
{
|
{
|
||||||
if(privateKey.Length != 32)
|
if(privateKey.Length != 32)
|
||||||
@ -155,9 +192,31 @@ namespace Ed25519
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="privateKey">The private key.</param>
|
/// <param name="privateKey">The private key.</param>
|
||||||
/// <returns>The corresponding public key.</returns>
|
/// <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>
|
/// <summary>
|
||||||
@ -165,10 +224,14 @@ namespace Ed25519
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The chosen key</param>
|
/// <param name="key">The chosen key</param>
|
||||||
/// <param name="filename">The desired file</param>
|
/// <param name="filename">The desired file</param>
|
||||||
public static void WriteKey(this ReadOnlySpan<byte> key, string filename)
|
public static void WriteKey(this ReadOnlySpan<byte> key, string filename) => File.WriteAllBytes(filename, key.ToArray());
|
||||||
{
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Decrypts an encrypted private key.
|
/// Decrypts an encrypted private key.
|
||||||
@ -184,5 +247,20 @@ namespace Ed25519
|
|||||||
|
|
||||||
return outputStream.ToArray();
|
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;
|
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>
|
/// <summary>
|
||||||
/// Loads a key (public or private key) from a file.
|
/// Loads a key (public or private key) from a file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filename">The entire path to the corresponding file.</param>
|
/// <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);
|
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>
|
/// <summary>
|
||||||
/// Signs a message with the given private and public keys.
|
/// Signs a message with the given private and public keys.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Validates a given signature by means of the given public key.
|
/// Validates a given signature by means of the given public key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -141,5 +230,46 @@ namespace Ed25519
|
|||||||
|
|
||||||
return ra.X.Equals(rb.X) && ra.Y.Equals(rb.Y);
|
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.IO;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace Ed25519
|
namespace Ed25519
|
||||||
@ -191,7 +192,7 @@ namespace Ed25519
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestWritingKeys()
|
public void TestWritingAndLoadingKeys()
|
||||||
{
|
{
|
||||||
var tempFilePrivate = Path.GetTempFileName();
|
var tempFilePrivate = Path.GetTempFileName();
|
||||||
var tempFilePublic = Path.GetTempFileName();
|
var tempFilePublic = Path.GetTempFileName();
|
||||||
@ -231,6 +232,22 @@ namespace Ed25519
|
|||||||
Assert.That(publicKeyFromDecrypted.ToArray(), Is.Not.EqualTo(publicKeyFromEncrypted.ToArray()));
|
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]
|
[Test]
|
||||||
public void TestSigningBehaviourPrivateKeyWithPassword()
|
public void TestSigningBehaviourPrivateKeyWithPassword()
|
||||||
{
|
{
|
||||||
@ -308,6 +325,20 @@ namespace Ed25519
|
|||||||
Assert.That(true);
|
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
|
// See https://tools.ietf.org/html/rfc8032#section-7.1
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRFC8032Test01EmptyMessage()
|
public void TestRFC8032Test01EmptyMessage()
|
||||||
|
Loading…
Reference in New Issue
Block a user