From 44044eb0fb28b774773e3284fd147c91d59d64e3 Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 18 Oct 2024 22:10:17 -0400 Subject: refactor: Wire up unit testing and refactor c# api --- .../tests/LibNoscryptTests.cs | 272 +++++++-------------- .../tests/NVault.Crypto.NoscryptTests.csproj | 32 --- .../tests/NoscryptVectorTests.cs | 185 ++++++++++++++ .../VNLib.Utils.Cryptography.NoscryptTests.csproj | 30 +++ 4 files changed, 301 insertions(+), 218 deletions(-) delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj (limited to 'wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests') diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs index ffa9cb6..b8d9623 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs @@ -1,25 +1,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Text; -using System.Text.Json; - using VNLib.Hashing; using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; -using VNLib.Utils.Cryptography.Noscrypt.Encryption; using VNLib.Utils.Cryptography.Noscrypt.Random; +using VNLib.Utils.Cryptography.Noscrypt.Singatures; namespace VNLib.Utils.Cryptography.Noscrypt.Tests { [TestClass()] public class LibNoscryptTests : IDisposable { - - const string NoscryptLibWinDebug = @"../../../../../../../build/windows/Debug/noscrypt.dll"; - const string NoscryptLinuxDebug = @"../../../../../../../build/linux/libnoscrypt.so"; - - //Keys generated using npx noskey package const string TestPrivateKeyHex = "98c642360e7163a66cee5d9a842b252345b6f3f3e21bd3b7635d5e6c20c7ea36"; const string TestPublicKeyHex = "0db15182c4ad3418b4fbab75304be7ade9cfa430a21c1c5320c9298f54ea5406"; @@ -27,56 +17,40 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests const string TestPrivateKeyHex2 = "3032cb8da355f9e72c9a94bbabae80ca99d3a38de1aed094b432a9fe3432e1f2"; const string TestPublicKeyHex2 = "421181660af5d39eb95e48a0a66c41ae393ba94ffeca94703ef81afbed724e5a"; - const string Nip44VectorTestFile = "nip44.vectors.json"; - #nullable disable private NoscryptLibrary _testLib; - private JsonDocument _testVectors; #nullable enable [TestInitialize] public void Initialize() { - _testLib = Environment.OSVersion.Platform switch - { - PlatformID.Win32NT => NoscryptLibrary.Load(NoscryptLibWinDebug), - PlatformID.Unix => NoscryptLibrary.Load(NoscryptLinuxDebug), - _ => throw new PlatformNotSupportedException() - }; - - _testVectors = JsonDocument.Parse(File.ReadAllText(Nip44VectorTestFile)); + _testLib = NoscryptLibrary.LoadDefault(); } - [TestMethod()] public void InitializeTest() { - //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); - //Init new context and interface - NCContext context = _testLib.Initialize(MemoryUtil.Shared, seed); - - using NostrCrypto crypto = new(context, true); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); } [TestMethod()] public void ValidateSecretKeyTest() { //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); ReadOnlySpan secretKey = RandomHash.GetRandomBytes(32); Span publicKey = stackalloc byte[32]; - using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); //validate the secret key - Assert.IsTrue(crypto.ValidateSecretKey(in NCUtil.AsSecretKey(secretKey))); + Assert.IsTrue(NCKeyUtil.ValidateSecretKey(context, in NCKeyUtil.AsSecretKey(secretKey))); //Generate the public key - crypto.GetPublicKey( - in NCUtil.AsSecretKey(secretKey), - ref NCUtil.AsPublicKey(publicKey) + NCKeyUtil.GetPublicKey( + context, + in NCKeyUtil.AsSecretKey(secretKey), + ref NCKeyUtil.AsPublicKey(publicKey) ); //Make sure the does not contain all zeros @@ -86,33 +60,31 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests [TestMethod()] public void TestGetPublicKey() { - //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); - - using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); //Test known key 1 TestKnownKeys( - crypto, + context, Convert.FromHexString(TestPrivateKeyHex), Convert.FromHexString(TestPublicKeyHex) ); //Test known key 2 TestKnownKeys( - crypto, + context, Convert.FromHexString(TestPrivateKeyHex2), Convert.FromHexString(TestPublicKeyHex2) ); - static void TestKnownKeys(NostrCrypto lib, ReadOnlySpan knownSec, ReadOnlySpan kownPub) + static void TestKnownKeys(NCContext context, ReadOnlySpan knownSec, ReadOnlySpan kownPub) { NCPublicKey pubKey; //Invoke test function - lib.GetPublicKey( - in NCUtil.AsSecretKey(knownSec), + NCKeyUtil.GetPublicKey( + context, + in NCKeyUtil.AsSecretKey(knownSec), ref pubKey ); @@ -125,176 +97,104 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests [TestMethod()] public void TestPublicApiArgValidations() { - //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); - - using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + byte[] bin16 = new byte[16]; + byte[] bin32 = new byte[32]; + byte[] bin64 = new byte[64]; NCSecretKey secKey = default; NCPublicKey pubKey = default; //noThrow (its a bad sec key but it should not throw) - crypto.ValidateSecretKey(ref secKey); - Assert.ThrowsException(() => crypto.ValidateSecretKey(ref NCSecretKey.NullRef)); + NCKeyUtil.ValidateSecretKey(context, in secKey); + Assert.ThrowsException(() => NCKeyUtil.ValidateSecretKey(null!, ref NCSecretKey.NullRef)); + Assert.ThrowsException(() => NCKeyUtil.ValidateSecretKey(context, ref NCSecretKey.NullRef)); //public key - Assert.ThrowsException(() => crypto.GetPublicKey(ref NCSecretKey.NullRef, ref pubKey)); - Assert.ThrowsException(() => crypto.GetPublicKey(in secKey, ref NCPublicKey.NullRef)); - } - - [TestMethod()] - public void CorrectEncryptionTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); - - using NoscryptCipher cipher = nc.AllocCipher(NoscryptCipherVersion.Nip44, NoscryptCipherFlags.EncryptDefault); - - using IMemoryHandle ctBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); - - foreach (EncryptionVector v in GetEncryptionVectors()) - { - - ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); - ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); - ReadOnlySpan plainText = Encoding.UTF8.GetBytes(v.plaintext); - ReadOnlySpan nonce = Convert.FromHexString(v.nonce); - ReadOnlySpan message = Convert.FromBase64String(v.payload); - - NCPublicKey pub2; - - //Recover public keys - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); - - //Assign existing nonce - nonce.CopyTo(cipher.IvBuffer); - - cipher.Update( - in NCUtil.AsSecretKey(secKey1), - in pub2, - plainText - ); - - Span outputBuffer = ctBuffer.AsSpan(0, cipher.GetOutputSize()); - - Assert.AreEqual(cipher.ReadOutput(outputBuffer), message.Length); - - //Make sure the cipher text matches the expected payload - if (!outputBuffer.SequenceEqual(message)) - { - Console.WriteLine($"Input data: {v.plaintext}"); - Console.WriteLine($" \n{Convert.ToHexString(outputBuffer)}\n{Convert.ToHexString(message)}"); - Assert.Fail($"Cipher text does not match expected message"); - } - } - } - - [TestMethod()] - public void CorrectDecryptionTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, NCFallbackRandom.Shared); - - using NoscryptCipher msgCipher = nc.AllocCipher(NoscryptCipherVersion.Nip44, NoscryptCipherFlags.DecryptDefault); - - using IMemoryHandle ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); - - foreach (EncryptionVector vector in GetEncryptionVectors()) - { - ReadOnlySpan secKey1 = Convert.FromHexString(vector.sec1); - ReadOnlySpan secKey2 = Convert.FromHexString(vector.sec2); - ReadOnlySpan expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); - ReadOnlySpan message = Convert.FromBase64String(vector.payload); - - NCPublicKey pub2 = default; + Assert.ThrowsException(() => NCKeyUtil.GetPublicKey(null!, in secKey, ref pubKey)); + Assert.ThrowsException(() => NCKeyUtil.GetPublicKey(context, ref NCSecretKey.NullRef, ref pubKey)); + Assert.ThrowsException(() => NCKeyUtil.GetPublicKey(context, in secKey, ref NCPublicKey.NullRef)); + + /* + * VERIFY DATA + */ + //Null context + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(null!, ref pubKey, bin32, bin64) + ); - //Recover public keys - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); + //Null pubkey + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref NCPublicKey.NullRef, bin32, bin64) + ); - //update performs the decryption operation (mac is also verified by default) - msgCipher.Update( - in NCUtil.AsSecretKey(secKey1), - in pub2, - message - ); + //No data buffer + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref pubKey, [], bin64) + ); - int outLen = msgCipher.GetOutputSize(); - Assert.IsTrue(outLen == expectedPt.Length); + //No signature + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref pubKey, bin32, []) + ); - Span plaintext = ptBuffer.AsSpan(0, outLen); + //Signature too small + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref pubKey, bin32, bin32) + ); - msgCipher.ReadOutput(plaintext); + /* + * SIGN DATA + */ - if (!plaintext.SequenceEqual(expectedPt)) - { - Console.WriteLine($"Input data: {vector.plaintext}"); - Console.WriteLine($" \n{Convert.ToHexString(plaintext)}\n{Convert.ToHexString(expectedPt)}"); - Assert.Fail("Decrypted data does not match expected plaintext"); - } - } - } + //Null context + Assert.ThrowsException(() => + NCSignatureUtil.SignData(null!, ref secKey, bin32, bin32, bin64) + ); + //Null secret key + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref NCSecretKey.NullRef, bin32, bin32, bin64) + ); - //Converstation key is only available in debug builds -#if DEBUG + //No entropy + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, [], bin32, bin64) + ); - [TestMethod()] - public void ConverstationKeyTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); - - Span convKeyOut = stackalloc byte[32]; + //No data + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, [], bin64) + ); - foreach (EncryptionVector v in GetEncryptionVectors()) - { - ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); - ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); - ReadOnlySpan conversationKey = Convert.FromHexString(v.conversation_key); + //No signature + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, bin32, []) + ); - NCPublicKey pubkey2 = default; - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pubkey2); + //Signature too small + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, bin32, bin32) + ); - nc.GetConverstationKey( - in NCUtil.AsSecretKey(secKey1), - in pubkey2, - convKeyOut - ); + //Entropy too small + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin16, bin32, bin32) + ); - Assert.IsTrue(conversationKey.SequenceEqual(convKeyOut)); + /* + * Cipher api + */ - MemoryUtil.InitializeBlock(convKeyOut); - } - } -#endif + NoscryptSigner signer = new(context, NCFallbackRandom.Shared); - private EncryptionVector[] GetEncryptionVectors() - { - return _testVectors.RootElement.GetProperty("v2") - .GetProperty("valid") - .GetProperty("encrypt_decrypt") - .EnumerateArray() - .Select(v => v.Deserialize()!) - .ToArray(); + } void IDisposable.Dispose() { _testLib.Dispose(); - _testVectors.Dispose(); GC.SuppressFinalize(this); } - - private sealed class EncryptionVector - { - public string sec1 { get; set; } = string.Empty; - - public string sec2 { get; set; } = string.Empty; - - public string nonce { get; set; } = string.Empty; - - public string plaintext { get; set; } = string.Empty; - - public string payload { get; set; } = string.Empty; - - public string conversation_key { get; set; } = string.Empty; - } } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj deleted file mode 100644 index 5917b15..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - Always - - - - diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs new file mode 100644 index 0000000..1a2f5d2 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs @@ -0,0 +1,185 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System; +using System.Text; +using System.Text.Json; +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; +using VNLib.Utils.Cryptography.Noscrypt.Encryption; +using VNLib.Utils.Cryptography.Noscrypt.Random; + +namespace VNLib.Utils.Cryptography.Noscrypt.Tests +{ + + [TestClass()] + public class NoscryptVectorTests : IDisposable + { + const string Nip44VectorTestFile = "nip44.vectors.json"; + +#nullable disable + private NoscryptLibrary _testLib; + private JsonDocument _testVectors; +#nullable enable + + [TestInitialize] + public void Initialize() + { + _testLib = NoscryptLibrary.LoadDefault(); + _testVectors = JsonDocument.Parse(File.ReadAllText(Nip44VectorTestFile)); + } + + [TestMethod()] + public void CorrectEncryptionTest() + { + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + using NoscryptMessageCipher cipher = NoscryptMessageCipher.Create(context, NoscryptCipherVersion.Nip44, NoscryptCipherFlags.EncryptDefault); + + using IMemoryHandle ctBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + + ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan plainText = Encoding.UTF8.GetBytes(v.plaintext); + ReadOnlySpan nonce = Convert.FromHexString(v.nonce); + ReadOnlySpan message = Convert.FromBase64String(v.payload); + + NCPublicKey pub2; + + //Recover public keys + NCKeyUtil.GetPublicKey(context, in NCKeyUtil.AsSecretKey(secKey2), ref pub2); + + //Assign existing nonce + nonce.CopyTo(cipher.IvBuffer); + + cipher.Update( + in NCKeyUtil.AsSecretKey(secKey1), + in pub2, + plainText + ); + + Span outputBuffer = ctBuffer.AsSpan(0, cipher.GetOutputSize()); + + Assert.AreEqual(cipher.ReadOutput(outputBuffer), message.Length); + + //Make sure the cipher text matches the expected payload + if (!outputBuffer.SequenceEqual(message)) + { + Console.WriteLine($"Input data: {v.plaintext}"); + Console.WriteLine($" \n{Convert.ToHexString(outputBuffer)}\n{Convert.ToHexString(message)}"); + Assert.Fail($"Cipher text does not match expected message"); + } + } + } + + [TestMethod()] + public void CorrectDecryptionTest() + { + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + using NoscryptMessageCipher msgCipher = NoscryptMessageCipher.Create(context, NoscryptCipherVersion.Nip44, NoscryptCipherFlags.DecryptDefault); + + using IMemoryHandle ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); + + foreach (EncryptionVector vector in GetEncryptionVectors()) + { + ReadOnlySpan secKey1 = Convert.FromHexString(vector.sec1); + ReadOnlySpan secKey2 = Convert.FromHexString(vector.sec2); + ReadOnlySpan expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); + ReadOnlySpan message = Convert.FromBase64String(vector.payload); + + NCPublicKey pub2 = default; + + //Recover public keys + NCKeyUtil.GetPublicKey(context, in NCKeyUtil.AsSecretKey(secKey2), ref pub2); + + //update performs the decryption operation (mac is also verified by default) + msgCipher.Update( + in NCKeyUtil.AsSecretKey(secKey1), + in pub2, + message + ); + + int outLen = msgCipher.GetOutputSize(); + Assert.IsTrue(outLen == expectedPt.Length); + + Span plaintext = ptBuffer.AsSpan(0, outLen); + + msgCipher.ReadOutput(plaintext); + + if (!plaintext.SequenceEqual(expectedPt)) + { + Console.WriteLine($"Input data: {vector.plaintext}"); + Console.WriteLine($" \n{Convert.ToHexString(plaintext)}\n{Convert.ToHexString(expectedPt)}"); + Assert.Fail("Decrypted data does not match expected plaintext"); + } + } + } + + + //Converstation key is only available in debug builds +#if DEBUG + + [TestMethod()] + public void ConverstationKeyTest() + { + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + + Span convKeyOut = stackalloc byte[32]; + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan conversationKey = Convert.FromHexString(v.conversation_key); + + NCPublicKey pubkey2 = default; + NCKeyUtil.GetPublicKey(context, in NCKeyUtil.AsSecretKey(secKey2), ref pubkey2); + + NCCipherUtil.GetConverstationKey( + context, + in NCKeyUtil.AsSecretKey(secKey1), + in pubkey2, + convKeyOut + ); + + Assert.IsTrue(conversationKey.SequenceEqual(convKeyOut)); + + MemoryUtil.InitializeBlock(convKeyOut); + } + } +#endif + + private EncryptionVector[] GetEncryptionVectors() + { + return _testVectors.RootElement.GetProperty("v2") + .GetProperty("valid") + .GetProperty("encrypt_decrypt") + .EnumerateArray() + .Select(v => v.Deserialize()!) + .ToArray(); + } + + void IDisposable.Dispose() + { + _testLib.Dispose(); + _testVectors.Dispose(); + GC.SuppressFinalize(this); + } + + private sealed class EncryptionVector + { + public string sec1 { get; set; } = string.Empty; + + public string sec2 { get; set; } = string.Empty; + + public string nonce { get; set; } = string.Empty; + + public string plaintext { get; set; } = string.Empty; + + public string payload { get; set; } = string.Empty; + + public string conversation_key { get; set; } = string.Empty; + } + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj new file mode 100644 index 0000000..a4542a6 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj @@ -0,0 +1,30 @@ + + + net8.0 + enable + enable + false + true + 0.2.0.0 + 0.2.0.0 + 0.2.0-c-sharp.95+Branch.c-sharp.Sha.6a4a464d9fdc7821cd5c5695656a3fe385497cc5 + 0.2.0-c-sharp0095 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + Always + + + \ No newline at end of file -- cgit