diff options
Diffstat (limited to 'lib/NVault.Crypto.Noscrypt')
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs | 11 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs | 1 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs | 3 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/NCMacVerifyArgs.cs | 6 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj | 4 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/Nip44Message.cs | 36 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs | 157 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs | 168 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/tests/LibNoscryptTests.cs | 224 | ||||
-rw-r--r-- | lib/NVault.Crypto.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj | 2 |
10 files changed, 410 insertions, 202 deletions
diff --git a/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs b/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs index 625f844..6ca7dea 100644 --- a/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs +++ b/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs @@ -38,6 +38,10 @@ namespace NVault.Crypto.Noscrypt public readonly NCDecryptDelegate NCDecrypt; public readonly NCVerifyMacDelegate NCVerifyMac; +#if DEBUG + public readonly NCGetConversationKeyDelegate NCGetConversationKey; +#endif + private FunctionTable(SafeLibraryHandle library) { //Load the required high-level api functions @@ -54,6 +58,10 @@ namespace NVault.Crypto.Noscrypt NCEncrypt = library.DangerousGetFunction<NCEncryptDelegate>(); NCDecrypt = library.DangerousGetFunction<NCDecryptDelegate>(); NCVerifyMac = library.DangerousGetFunction<NCVerifyMacDelegate>(); + +#if DEBUG + NCGetConversationKey = library.DangerousGetFunction<NCGetConversationKeyDelegate>(); +#endif } /// <summary> @@ -99,5 +107,8 @@ namespace NVault.Crypto.Noscrypt [SafeMethodName("NCVerifyMac")] internal delegate NCResult NCVerifyMacDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCMacVerifyArgs* args); + [SafeMethodName("NCGetConversationKey")] + internal delegate NCResult NCGetConversationKeyDelegate(nint ctx, NCSecretKey* sk, NCPublicKey* pk, byte* keyOut32); + } } diff --git a/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs b/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs index b155810..996681c 100644 --- a/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs +++ b/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs @@ -44,6 +44,7 @@ namespace NVault.Crypto.Noscrypt public const int NC_MESSAGE_KEY_SIZE = 32; public const int NC_HMAC_KEY_SIZE = 32; public const int NC_ENCRYPTION_MAC_SIZE = 32; + public const int NC_CONVERSATION_KEY_SIZE = 32; public const int CTX_ENTROPY_SIZE = 32; public const NCResult NC_SUCCESS = 0; diff --git a/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs b/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs index 1224fa6..72b43b2 100644 --- a/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs +++ b/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs @@ -20,8 +20,7 @@ namespace NVault.Crypto.Noscrypt [StructLayout(LayoutKind.Sequential)] internal unsafe struct NCCryptoData { - public fixed byte nonce[LibNoscrypt.NC_ENCRYPTION_NONCE_SIZE]; - + public byte* nonce; public void* inputData; public void* outputData; public uint dataSize; diff --git a/lib/NVault.Crypto.Noscrypt/src/NCMacVerifyArgs.cs b/lib/NVault.Crypto.Noscrypt/src/NCMacVerifyArgs.cs index b6a8f8c..d2867f6 100644 --- a/lib/NVault.Crypto.Noscrypt/src/NCMacVerifyArgs.cs +++ b/lib/NVault.Crypto.Noscrypt/src/NCMacVerifyArgs.cs @@ -13,17 +13,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -using static NVault.Crypto.Noscrypt.LibNoscrypt; - namespace NVault.Crypto.Noscrypt { internal unsafe struct NCMacVerifyArgs { /* The message authentication code certifying the Nip44 payload */ - public fixed byte mac[NC_ENCRYPTION_MAC_SIZE]; + public byte* mac; /* The nonce used for the original message encryption */ - public fixed byte nonce[NC_ENCRYPTION_NONCE_SIZE]; + public byte* nonce; /* The message payload data */ public byte* payload; diff --git a/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj b/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj index 7d50f7c..00c2fec 100644 --- a/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj +++ b/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj @@ -20,8 +20,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0116" /> - <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0116" /> + <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0118" /> + <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0118" /> </ItemGroup> </Project> diff --git a/lib/NVault.Crypto.Noscrypt/src/Nip44Message.cs b/lib/NVault.Crypto.Noscrypt/src/Nip44Message.cs new file mode 100644 index 0000000..839d970 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/Nip44Message.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace NVault.Crypto.Noscrypt +{ + public readonly ref struct Nip44Message(ReadOnlySpan<byte> payload) + { + readonly ReadOnlySpan<byte> _payload = payload; + + public ReadOnlySpan<byte> Payload => _payload; + + public ReadOnlySpan<byte> Nonce => Nip44Util.GetNonceFromPayload(_payload); + + public ReadOnlySpan<byte> Ciphertext => Nip44Util.GetCiphertextFromPayload(_payload); + + public ReadOnlySpan<byte> Mac => Nip44Util.GetMacFromPayload(_payload); + + public ReadOnlySpan<byte> NonceAndCiphertext => Nip44Util.GetNonceAndCiphertext(_payload); + + public byte Version => Nip44Util.GetMessageVersion(_payload); + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs b/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs index 5ea2e7e..1f3248b 100644 --- a/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs +++ b/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs @@ -52,7 +52,7 @@ namespace NVault.Crypto.Noscrypt int chunk = nexPower <= 256 ? 32 : nexPower / 8; - return chunk * ((int)Math.Floor((double)((minSize - 1) / chunk)) + 1); + return (chunk * ((int)Math.Floor((double)((minSize - 1) / chunk)) + 1)) + sizeof(ushort); } /// <summary> @@ -84,25 +84,63 @@ namespace NVault.Crypto.Noscrypt in MemoryMarshal.GetReference(plaintextData), 0, ref MemoryMarshal.GetReference(output), - sizeof(ushort), + sizeof(ushort), (uint)plaintextData.Length ); //We assume the remaining buffer is zeroed out } + public static ReadOnlySpan<byte> GetNonceFromPayload(ReadOnlySpan<byte> message) + { + //The nonce is 32 bytes following the 1st byte version number of the message + return message.Slice(1, NC_ENCRYPTION_NONCE_SIZE); + } + + public static ReadOnlySpan<byte> GetCiphertextFromPayload(ReadOnlySpan<byte> message) + { + //Message is between the nonce and the trailing mac + int payloadSize = message.Length - (1 + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE); + return message.Slice(1 + NC_ENCRYPTION_NONCE_SIZE, payloadSize); + } + + public static ReadOnlySpan<byte> GetMacFromPayload(ReadOnlySpan<byte> message) + { + //The mac is the last 32 bytes of the message + return message[^NC_ENCRYPTION_MAC_SIZE..]; + } + + public static ReadOnlySpan<byte> GetNonceAndCiphertext(ReadOnlySpan<byte> message) + { + //The nonce is 32 bytes following the 1st byte version number of the message + return message.Slice(1, NC_ENCRYPTION_NONCE_SIZE + GetCiphertextFromPayload(message).Length); + } + + public static byte GetMessageVersion(ReadOnlySpan<byte> message) + { + //The first byte is the message version + return message[0]; + } + + public static ReadOnlySpan<byte> GetPlaintextMessage(ReadOnlySpan<byte> plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return plaintextPayload.Slice(sizeof(ushort), ptLength); + } + + public static void Encrypt( - this INostrCrypto lib, - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - ReadOnlySpan<byte> nonce32, - ReadOnlySpan<byte> plainText, + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> plainText, Span<byte> hmackKeyOut32, Span<byte> cipherText ) { ArgumentNullException.ThrowIfNull(lib); - + //Chacha requires the output buffer to be at-least the size of the input buffer ArgumentOutOfRangeException.ThrowIfGreaterThan(plainText.Length, cipherText.Length, nameof(plainText)); @@ -113,11 +151,11 @@ namespace NVault.Crypto.Noscrypt //Encrypt data, use the plaintext buffer size as the data size lib.Encrypt( - in secretKey, + in secretKey, in publicKey, in MemoryMarshal.GetReference(nonce32), in MemoryMarshal.GetReference(plainText), - ref MemoryMarshal.GetReference(cipherText), + ref MemoryMarshal.GetReference(cipherText), (uint)plainText.Length, ref MemoryMarshal.GetReference(hmackKeyOut32) ); @@ -128,7 +166,7 @@ namespace NVault.Crypto.Noscrypt ref NCSecretKey secretKey, ref NCPublicKey publicKey, void* nonce32, - void* hmacKeyOut32, + void* hmacKeyOut32, void* plainText, void* cipherText, uint size @@ -149,14 +187,14 @@ namespace NVault.Crypto.Noscrypt new Span<byte>(cipherText, (int)size) ); } - + public static void Decrypt( - this INostrCrypto lib, - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - ReadOnlySpan<byte> nonce32, - ReadOnlySpan<byte> cipherText, + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> cipherText, Span<byte> plainText ) { @@ -170,28 +208,28 @@ namespace NVault.Crypto.Noscrypt //Decrypt data, use the ciphertext buffer size as the data size lib.Decrypt( - in secretKey, - in publicKey, - in MemoryMarshal.GetReference(nonce32), - in MemoryMarshal.GetReference(cipherText), - ref MemoryMarshal.GetReference(plainText), + in secretKey, + in publicKey, + in MemoryMarshal.GetReference(nonce32), + in MemoryMarshal.GetReference(cipherText), + ref MemoryMarshal.GetReference(plainText), (uint)cipherText.Length ); } public static unsafe void Decrypt( - this INostrCrypto lib, - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - void* nonce32, - void* cipherText, - void* plainText, + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + void* nonce32, + void* cipherText, + void* plainText, uint size ) { ArgumentNullException.ThrowIfNull(nonce32); ArgumentNullException.ThrowIfNull(cipherText); - ArgumentNullException.ThrowIfNull(plainText); + ArgumentNullException.ThrowIfNull(plainText); //Spans are easer to forward references from pointers without screwing up arguments Decrypt( @@ -205,11 +243,11 @@ namespace NVault.Crypto.Noscrypt } public static bool VerifyMac( - this INostrCrypto lib, - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - ReadOnlySpan<byte> nonce32, - ReadOnlySpan<byte> mac32, + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> mac32, ReadOnlySpan<byte> payload ) { @@ -220,22 +258,22 @@ namespace NVault.Crypto.Noscrypt //Verify the HMAC return lib.VerifyMac( - in secretKey, - in publicKey, - in MemoryMarshal.GetReference(nonce32), - in MemoryMarshal.GetReference(mac32), - in MemoryMarshal.GetReference(payload), + in secretKey, + in publicKey, + in MemoryMarshal.GetReference(nonce32), + in MemoryMarshal.GetReference(mac32), + in MemoryMarshal.GetReference(payload), payload.Length ); } public static unsafe bool VerifyMac( - this INostrCrypto lib, - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - void* nonce32, - void* mac32, - void* payload, + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + void* nonce32, + void* mac32, + void* payload, uint payloadSize ) { @@ -253,5 +291,34 @@ namespace NVault.Crypto.Noscrypt new Span<byte>(payload, (int)payloadSize) ); } + +#if DEBUG + /* + * Conversation key is not meant to be a public api. Callers + * should use Encrypt/Decrypt methods to handle encryption. + * + * This method exists for vector testing purposes only. + */ + public static void GetConverstationKey( + this NostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + Span<byte> conversationKeyOut32 + ) + { + ArgumentNullException.ThrowIfNull(lib); + ArgumentOutOfRangeException.ThrowIfNotEqual(conversationKeyOut32.Length, NC_CONVERSATION_KEY_SIZE, nameof(conversationKeyOut32)); + + //Get the conversation key + lib.GetConverstationKey( + in secretKey, + in publicKey, + ref MemoryMarshal.GetReference(conversationKeyOut32) + ); + + } +#endif + } + } diff --git a/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs b/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs index 1bfccb0..30205a1 100644 --- a/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs +++ b/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs @@ -14,13 +14,16 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; using VNLib.Utils; using NCResult = System.Int64; using static NVault.Crypto.Noscrypt.LibNoscrypt; + namespace NVault.Crypto.Noscrypt { @@ -41,7 +44,7 @@ namespace NVault.Crypto.Noscrypt public void Decrypt( ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, - ref readonly byte nonce, + ref readonly byte nonce32, ref readonly byte cipherText, ref byte plainText, uint size @@ -49,27 +52,22 @@ namespace NVault.Crypto.Noscrypt { Check(); - IntPtr libCtx = context.DangerousGetHandle(); - - NCCryptoData data = default; - data.dataSize = size; - - //Copy nonce to struct memory buffer - Unsafe.CopyBlock( - ref Unsafe.AsRef<byte>(data.nonce), - in nonce, - NC_ENCRYPTION_NONCE_SIZE - ); - + ThrowIfNullRef(in nonce32, nameof(nonce32)); + fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) - fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pNonce = &nonce32) { - //Set input data to the cipher text to decrypt and the output data to the plaintext buffer - data.inputData = pCipherText; - data.outputData = pTextPtr; - - NCResult result = Functions.NCDecrypt.Invoke(libCtx, pSecKey, pPubKey, &data); + NCCryptoData data = new() + { + //Set input data to the cipher text to decrypt and the output data to the plaintext buffer + dataSize = size, + inputData = pCipherText, + outputData = pTextPtr, + nonce = pNonce + }; + + NCResult result = Functions.NCDecrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); NCUtil.CheckResult<FunctionTable.NCDecryptDelegate>(result, true); } } @@ -77,37 +75,32 @@ namespace NVault.Crypto.Noscrypt ///<inheritdoc/> public void Encrypt( ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - ref readonly byte nonce, - ref readonly byte plainText, - ref byte cipherText, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte plainText, + ref byte cipherText, uint size, ref byte hmackKeyOut32 ) { Check(); - IntPtr libCtx = context.DangerousGetHandle(); - - NCCryptoData data = default; - data.dataSize = size; - - //Copy nonce to struct memory buffer - Unsafe.CopyBlock( - ref Unsafe.AsRef<byte>(data.nonce), - in nonce, - NC_ENCRYPTION_NONCE_SIZE - ); - + ThrowIfNullRef(in nonce32, nameof(nonce32)); + fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) - fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pHmacKeyOut = &hmackKeyOut32) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pHmacKeyOut = &hmackKeyOut32, pNonce = &nonce32) { - //Set input data to the plaintext to encrypt and the output data to the cipher text buffer - data.inputData = pTextPtr; - data.outputData = pCipherText; - - NCResult result = Functions.NCEncrypt.Invoke(libCtx, pSecKey, pPubKey, pHmacKeyOut, &data); + NCCryptoData data = new() + { + dataSize = size, + //Set input data to the plaintext to encrypt and the output data to the cipher text buffer + inputData = pTextPtr, + outputData = pCipherText, + nonce = pNonce + }; + + NCResult result = Functions.NCEncrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, pHmacKeyOut, &data); NCUtil.CheckResult<FunctionTable.NCEncryptDelegate>(result, true); } } @@ -117,12 +110,10 @@ namespace NVault.Crypto.Noscrypt { Check(); - IntPtr libCtx = context.DangerousGetHandle(); - fixed(NCSecretKey* pSecKey = &secretKey) fixed(NCPublicKey* pPubKey = &publicKey) { - NCResult result = Functions.NCGetPublicKey.Invoke(libCtx, pSecKey, pPubKey); + NCResult result = Functions.NCGetPublicKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey); NCUtil.CheckResult<FunctionTable.NCGetPublicKeyDelegate>(result, true); } } @@ -137,13 +128,11 @@ namespace NVault.Crypto.Noscrypt ) { Check(); - - IntPtr libCtx = context.DangerousGetHandle(); - + fixed (NCSecretKey* pSecKey = &secretKey) fixed(byte* pData = &data, pSig = &sig64, pRandom = &random32) { - NCResult result = Functions.NCSignData.Invoke(libCtx, pSecKey, pRandom, pData, dataSize, pSig); + NCResult result = Functions.NCSignData.Invoke(context.DangerousGetHandle(), pSecKey, pRandom, pData, dataSize, pSig); NCUtil.CheckResult<FunctionTable.NCSignDataDelegate>(result, true); } } @@ -178,13 +167,11 @@ namespace NVault.Crypto.Noscrypt ) { Check(); - - IntPtr libCtx = context.DangerousGetHandle(); fixed(NCPublicKey* pPubKey = &pubKey) fixed (byte* pData = &data, pSig = &sig64) { - NCResult result = Functions.NCVerifyData.Invoke(libCtx, pPubKey, pData, dataSize, pSig); + NCResult result = Functions.NCVerifyData.Invoke(context.DangerousGetHandle(), pPubKey, pData, dataSize, pSig); NCUtil.CheckResult<FunctionTable.NCVerifyDataDelegate>(result, false); return result == NC_SUCCESS; @@ -204,50 +191,25 @@ namespace NVault.Crypto.Noscrypt Check(); //Check pointers we need to use - if(Unsafe.IsNullRef(in nonce32)) - { - throw new ArgumentNullException(nameof(nonce32)); - } - - if(Unsafe.IsNullRef(in mac32)) - { - throw new ArgumentNullException(nameof(mac32)); - } + ThrowIfNullRef(in nonce32, nameof(nonce32)); + ThrowIfNullRef(in mac32, nameof(mac32)); + ThrowIfNullRef(in payload, nameof(payload)); - if(Unsafe.IsNullRef(in payload)) - { - throw new ArgumentNullException(nameof(payload)); - } - - IntPtr libCtx = context.DangerousGetHandle(); - - NCMacVerifyArgs args = new() + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pPayload = &payload, pMac = &mac32, pNonce = &nonce32) { - payloadSize = payloadSize, - }; - - //Copy nonce to struct memory buffer - Unsafe.CopyBlock( - ref Unsafe.AsRef<byte>(args.nonce), - in nonce32, - NC_ENCRYPTION_NONCE_SIZE - ); - //Copy mac to struct memory buffer - Unsafe.CopyBlock( - ref Unsafe.AsRef<byte>(args.mac), - in mac32, - NC_ENCRYPTION_MAC_SIZE - ); - - fixed(NCSecretKey* pSecKey = &secretKey) - fixed(NCPublicKey* pPubKey = &publicKey) - fixed (byte* pPayload = &payload) - { - args.payload = pPayload; + NCMacVerifyArgs args = new() + { + payloadSize = payloadSize, + payload = pPayload, + mac = pMac, + nonce = pNonce + }; //Exec and bypass failure - NCResult result = Functions.NCVerifyMac.Invoke(libCtx, pSecKey, pPubKey, &args); + NCResult result = Functions.NCVerifyMac.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &args); NCUtil.CheckResult<FunctionTable.NCVerifyMacDelegate>(result, false); //Result should be success if the hmac is valid @@ -255,7 +217,23 @@ namespace NVault.Crypto.Noscrypt } } - + [Conditional("DEBUG")] + public void GetConverstationKey( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref byte key32 + ) + { + Check(); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pKey = &key32) + { + NCResult result = Functions.NCGetConversationKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, pKey); + NCUtil.CheckResult<FunctionTable.NCGetConversationKeyDelegate>(result, true); + } + } ///<inheritdoc/> protected override void Free() @@ -265,5 +243,13 @@ namespace NVault.Crypto.Noscrypt context.Dispose(); } } + + private static void ThrowIfNullRef([DoesNotReturnIf(false)] ref readonly byte value, string name) + { + if(Unsafe.IsNullRef(in value)) + { + throw new ArgumentNullException(name); + } + } } } diff --git a/lib/NVault.Crypto.Noscrypt/tests/LibNoscryptTests.cs b/lib/NVault.Crypto.Noscrypt/tests/LibNoscryptTests.cs index 128751e..a575ab5 100644 --- a/lib/NVault.Crypto.Noscrypt/tests/LibNoscryptTests.cs +++ b/lib/NVault.Crypto.Noscrypt/tests/LibNoscryptTests.cs @@ -2,9 +2,9 @@ using System; using System.Buffers.Binary; -using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using System.Runtime.CompilerServices; using VNLib.Hashing; using VNLib.Utils.Memory; @@ -14,7 +14,7 @@ namespace NVault.Crypto.Noscrypt.Tests [TestClass()] public class LibNoscryptTests : IDisposable { - + const string NoscryptLibWinDebug = @"../../../../../../../noscrypt/out/build/x64-debug/Debug/noscrypt.dll"; @@ -46,11 +46,9 @@ namespace NVault.Crypto.Noscrypt.Tests //Random context seed ReadOnlySpan<byte> seed = RandomHash.GetRandomBytes(32); - using LibNoscrypt library = LibNoscrypt.Load(NoscryptLibWinDebug); - //Init new context and interface - NCContext context = library.Initialize(MemoryUtil.Shared, seed); - + NCContext context = _testLib.Initialize(MemoryUtil.Shared, seed); + using NostrCrypto crypto = new(context, true); } @@ -69,9 +67,9 @@ namespace NVault.Crypto.Noscrypt.Tests //Generate the public key crypto.GetPublicKey( - in NCUtil.AsSecretKey(secretKey), + in NCUtil.AsSecretKey(secretKey), ref NCUtil.AsPublicKey(publicKey) - ); + ); //Make sure the does not contain all zeros Assert.IsTrue(publicKey.ToArray().Any(b => b != 0)); @@ -134,7 +132,6 @@ namespace NVault.Crypto.Noscrypt.Tests //public key Assert.ThrowsException<ArgumentNullException>(() => crypto.GetPublicKey(ref Unsafe.NullRef<NCSecretKey>(), ref pubKey)); Assert.ThrowsException<ArgumentNullException>(() => crypto.GetPublicKey(in secKey, ref Unsafe.NullRef<NCPublicKey>())); - } [TestMethod()] @@ -154,40 +151,35 @@ namespace NVault.Crypto.Noscrypt.Tests foreach ((int len, int paddedLen) in paddedSizes) { - Assert.AreEqual<int>(paddedLen, Nip44Util.CalcBufferSize(len)); + Assert.AreEqual<int>(paddedLen, Nip44Util.CalcBufferSize(len) - 2); } } [TestMethod()] - public void EncryptionTest() + public void CorrectEncryptionTest() { - //get valid encryption test vectors from vector file - EncryptionVector[] vectors = _testVectors.RootElement.GetProperty("v2") - .GetProperty("valid") - .GetProperty("encrypt_decrypt") - .EnumerateArray() - .Select(v => v.Deserialize<EncryptionVector>()!) - .ToArray(); - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); Span<byte> hmacKeyOut = stackalloc byte[LibNoscrypt.NC_HMAC_KEY_SIZE]; - foreach (EncryptionVector v in vectors) - { + foreach (EncryptionVector v in GetEncryptionVectors()) + { ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); ReadOnlySpan<byte> plainText = Encoding.UTF8.GetBytes(v.plaintext); ReadOnlySpan<byte> nonce = Convert.FromHexString(v.nonce); - ReadOnlySpan<byte> payload = Convert.FromBase64String(v.payload); + ReadOnlySpan<byte> message = Convert.FromBase64String(v.payload); ReadOnlySpan<byte> conversationKey = Convert.FromHexString(v.conversation_key); + Nip44Message nip44Message = new(message); + //Convert the plaintext data to a valid input buffer - ReadOnlySpan<byte> pt = ToInputData(plainText); - Span<byte> cipherText = new byte[pt.Length]; + ReadOnlySpan<byte> pt = CreateAndFormatPlaintextOutputBuffer(plainText); + Span<byte> actualCiphertext = new byte[pt.Length]; - ReadOnlySpan<byte> mac = payload[..32]; //Last 32 bytes of the payload - ReadOnlySpan<byte> validCipherText = payload.Slice(33, pt.Length); + Assert.AreEqual<byte>(nip44Message.Version, 0x02); + Assert.IsTrue(nonce.SequenceEqual(nip44Message.Nonce)); + Assert.AreEqual<int>(nip44Message.Ciphertext.Length, pt.Length); NCPublicKey pub1; NCPublicKey pub2; @@ -196,17 +188,6 @@ namespace NVault.Crypto.Noscrypt.Tests nc.GetPublicKey(in NCUtil.AsSecretKey(secKey1), ref pub1); nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); - //Verify mac - bool macValid = nc.VerifyMac( - in NCUtil.AsSecretKey(secKey1), - in pub2, - nonce, - mac, - BuildMacData(cipherText, nonce) - ); - - Assert.IsTrue(macValid); - //Encrypt the plaintext nc.Encrypt( in NCUtil.AsSecretKey(secKey1), @@ -214,45 +195,173 @@ namespace NVault.Crypto.Noscrypt.Tests nonce, pt, hmacKeyOut, - cipherText + actualCiphertext ); //Make sure the cipher text matches the expected payload - if (!cipherText.SequenceEqual(validCipherText)) + if (!actualCiphertext.SequenceEqual(nip44Message.Ciphertext)) { - Console.WriteLine($"Input data {v.plaintext}"); - Console.WriteLine($"Expected size: {BinaryPrimitives.ReadUInt16BigEndian(validCipherText)}, {plainText.Length}"); - Console.WriteLine($"Actual size {BinaryPrimitives.ReadUInt16BigEndian(pt)}, {plainText.Length}"); - Console.WriteLine($" \n{Convert.ToHexString(cipherText)}.\n{Convert.ToHexString(validCipherText)}"); + Console.WriteLine($"Input data: {v.plaintext}"); + Console.WriteLine($" \n{Convert.ToHexString(actualCiphertext)}\n{Convert.ToHexString(nip44Message.Ciphertext)}"); Assert.Fail($"Cipher text does not match expected payload"); } } + } - static byte[] ToInputData(ReadOnlySpan<byte> plaintext) + [TestMethod()] + public void ValidateMessageMacs() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + + foreach (EncryptionVector v in GetEncryptionVectors()) { - //Compute the required plaintext buffer size - int paddedSize = Nip44Util.CalcBufferSize(plaintext.Length + sizeof(ushort)); + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan<byte> message = Convert.FromBase64String(v.payload); - byte[] data = new byte[paddedSize]; + Nip44Message nip44Message = new(message); + Assert.AreEqual<byte>(nip44Message.Version, 0x02); - //Format the plaintext buffer - Nip44Util.FormatBuffer(plaintext, data, true); + NCPublicKey pub2; + + //Recover public key2 + nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); - return data; + bool success = nc.VerifyMac( + in NCUtil.AsSecretKey(secKey1), + in pub2, + nip44Message.Nonce, + nip44Message.Mac, + nip44Message.NonceAndCiphertext + ); + + if (!success) + { + Console.WriteLine($"Failed to validate MAC for message: {v.payload}"); + Console.Write($"Mac hex value: {Convert.ToHexString(nip44Message.Mac)}"); + Assert.Fail("Failed to validate MAC for message"); + } } + } + + //Converstation key is only available in debug builds +#if DEBUG + + [TestMethod()] + public void ConverstationKeyTest() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + + Span<byte> convKeyOut = stackalloc byte[32]; - static byte[] BuildMacData(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> nonce) + foreach (EncryptionVector v in GetEncryptionVectors()) { - byte[] macData = new byte[ciphertext.Length + nonce.Length]; + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan<byte> conversationKey = Convert.FromHexString(v.conversation_key); + + NCPublicKey pubkey2 = default; + nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pubkey2); + + nc.GetConverstationKey( + in NCUtil.AsSecretKey(secKey1), + in pubkey2, + convKeyOut + ); + + Assert.IsTrue(conversationKey.SequenceEqual(convKeyOut)); + + MemoryUtil.InitializeBlock(convKeyOut); + } + } +#endif + + + [TestMethod()] + public void CorrectDecryptionTest() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + + Span<byte> hmacKeyOut = stackalloc byte[LibNoscrypt.NC_HMAC_KEY_SIZE]; + + foreach (EncryptionVector vector in GetEncryptionVectors()) + { + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(vector.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(vector.sec2); + ReadOnlySpan<byte> expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); + ReadOnlySpan<byte> nonce = Convert.FromHexString(vector.nonce); + ReadOnlySpan<byte> message = Convert.FromBase64String(vector.payload); + + Nip44Message nip44Message = new(message); + + Assert.IsTrue(nip44Message.Version == 0x02); + Assert.IsTrue(nonce.SequenceEqual(nip44Message.Nonce)); + + NCPublicKey pub1; + + //Recover public keys + nc.GetPublicKey(in NCUtil.AsSecretKey(secKey1), ref pub1); + + Span<byte> plaintextOut = new byte[ Nip44Util.CalcBufferSize(expectedPt.Length) ]; + + Assert.IsTrue(nip44Message.Ciphertext.Length == plaintextOut.Length); + + /* + * Decrypting messages requires the public key of the sender + * and the secret key of the receiver + */ + nc.Decrypt( + in NCUtil.AsSecretKey(secKey2), + in pub1, + nip44Message.Nonce, + nip44Message.Ciphertext, + plaintextOut + ); - //Nonce then cipher text - nonce.CopyTo(macData); - ciphertext.CopyTo(macData.AsSpan(nonce.Length)); + ReadOnlySpan<byte> actualPt = Nip44Util.GetPlaintextMessage(plaintextOut); - return macData; + Assert.AreEqual<int>(expectedPt.Length, actualPt.Length); + Assert.IsTrue(actualPt.SequenceEqual(expectedPt)); + + MemoryUtil.InitializeBlock(hmacKeyOut); } } + + static byte[] CreateAndFormatPlaintextOutputBuffer(ReadOnlySpan<byte> plaintext) + { + //Compute the required plaintext buffer size + int paddedSize = Nip44Util.CalcBufferSize(plaintext.Length); + + byte[] data = new byte[paddedSize]; + + //Format the plaintext buffer + Nip44Util.FormatBuffer(plaintext, data, true); + + return data; + } + + static byte[] BuildMacData(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> nonce) + { + byte[] macData = new byte[ciphertext.Length + nonce.Length]; + + //Nonce then cipher text + nonce.CopyTo(macData); + ciphertext.CopyTo(macData.AsSpan(nonce.Length)); + + return macData; + } + + EncryptionVector[] GetEncryptionVectors() + { + return _testVectors.RootElement.GetProperty("v2") + .GetProperty("valid") + .GetProperty("encrypt_decrypt") + .EnumerateArray() + .Select(v => v.Deserialize<EncryptionVector>()!) + .ToArray(); + } + void IDisposable.Dispose() { _testLib.Dispose(); @@ -271,7 +380,8 @@ namespace NVault.Crypto.Noscrypt.Tests public string plaintext { get; set; } = string.Empty; public string payload { get; set; } = string.Empty; + public string conversation_key { get; set; } = string.Empty; } } -}
\ No newline at end of file +} diff --git a/lib/NVault.Crypto.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj b/lib/NVault.Crypto.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj index ba6d289..ea65755 100644 --- a/lib/NVault.Crypto.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj +++ b/lib/NVault.Crypto.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj @@ -13,7 +13,7 @@ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-preview-24080-01" /> <PackageReference Include="MSTest.TestAdapter" Version="3.2.2" /> <PackageReference Include="MSTest.TestFramework" Version="3.2.2" /> - <PackageReference Include="coverlet.collector" Version="6.0.1"> + <PackageReference Include="coverlet.collector" Version="6.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> |