From eefbfce0af26be62ec3b329e4ef78f12f5f71c98 Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 20 Jun 2024 21:38:00 -0400 Subject: push latest c-sharp changes --- .../src/Base64SignatureEncoder.cs | 31 ++ .../src/HexSignatureEncoder.cs | 31 ++ .../src/IEncryptionVersion.cs | 36 -- .../src/INostrEncryptionVersion.cs | 43 +++ .../src/INostrSignatureEncoder.cs | 33 ++ .../src/LibNoscrypt.cs | 210 ---------- .../src/NCContext.cs | 13 +- .../src/NCNip04EncryptionVersion.cs | 13 +- .../src/NCNip44EncryptionVersion.cs | 10 +- .../src/NCPublicKey.cs | 8 +- .../src/NCSecretKey.cs | 8 +- .../src/NCUtil.cs | 2 +- .../src/NcFallbackRandom.cs | 36 ++ .../src/Nip04Util.cs | 9 + .../src/Nip44Message.cs | 36 -- .../src/Nip44MessageSegments.cs | 36 ++ .../src/Nip44Util.cs | 58 ++- .../src/NoscryptExtensions.cs | 2 +- .../src/NoscryptLibrary.cs | 242 ++++++++++++ .../src/NoscryptSigner.cs | 133 +++++++ .../src/NostrCrypto.cs | 16 +- .../src/NostrEncryptedMessage.cs | 234 ------------ .../src/NostrMessageCipher.cs | 424 +++++++++++++++++++++ .../tests/LibNoscryptTests.cs | 97 ++--- 24 files changed, 1161 insertions(+), 600 deletions(-) create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Base64SignatureEncoder.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NcFallbackRandom.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Message.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Base64SignatureEncoder.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Base64SignatureEncoder.cs new file mode 100644 index 0000000..c5078a4 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Base64SignatureEncoder.cs @@ -0,0 +1,31 @@ +// 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 . + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public sealed class Base64SignatureEncoder : INostrSignatureEncoder + { + /// + /// Shared formatter instance for base64 signatures + /// + public static Base64SignatureEncoder Instance { get; } = new Base64SignatureEncoder(); + + /// + public string GetString(ReadOnlySpan signature) => Convert.ToBase64String(signature); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs new file mode 100644 index 0000000..6a60c73 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs @@ -0,0 +1,31 @@ +// 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 . + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public sealed class HexSignatureEncoder : INostrSignatureEncoder + { + /// + /// Shared formatter instance for hex signatures + /// + public static HexSignatureEncoder Instance { get; } = new HexSignatureEncoder(); + + /// + public string GetString(ReadOnlySpan signature) => Convert.ToHexString(signature); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.cs deleted file mode 100644 index e9aab2b..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 . - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - /// - /// Represents a message encryption version used by the Nostr protocol - /// - public interface IEncryptionVersion - { - /// - /// The noscrypt compatible encryption version - /// - internal uint Version { get; } - - /// - /// Calculates the required buffer size for the specified data size - /// - /// The size of the input data - /// The estimated size of the buffer required to complete the opeation - internal int CalcBufferSize(int dataSize); - } - -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs new file mode 100644 index 0000000..3a26466 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs @@ -0,0 +1,43 @@ +// 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 . + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// + /// Represents a message encryption version used by the Nostr protocol + /// + public interface INostrEncryptionVersion + { + /// + /// The noscrypt compatible encryption version + /// + internal uint Version { get; } + + /// + /// Calculates the required payload buffer size for the specified data size + /// + /// The size of the input data + /// The estimated size of the buffer required to complete the opeation + internal int GetPayloadBufferSize(int dataSize); + + /// + /// Calculates the required message buffer size for the specified data size + /// + /// Plain text data size + /// The estimated size of the buffer required to complete the opeation + internal int GetMessageBufferSize(int dataSize); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs new file mode 100644 index 0000000..b8c69f5 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs @@ -0,0 +1,33 @@ +// 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 . + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// + /// Encodes a message signature into it's string representation + /// + public interface INostrSignatureEncoder + { + /// + /// Creates a string of the encoded signature data + /// + /// The signature data to encode into the string + /// The encoded signature string + string GetString(ReadOnlySpan signature); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs deleted file mode 100644 index 32a07f4..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs +++ /dev/null @@ -1,210 +0,0 @@ -// 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 . - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -using VNLib.Utils; -using VNLib.Utils.Extensions; -using VNLib.Utils.Memory; -using VNLib.Utils.Native; - -using VNLib.Utils.Cryptography.Noscrypt.@internal; - -using NCResult = System.Int64; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - - /// - /// Initializes the native library and provides access to the native functions - /// - /// An existing noscrypt library handle - /// A value that indicates if the instance owns the library handle - public unsafe sealed class LibNoscrypt(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable - { - //Values that match the noscrypt.h header - public const int NC_SEC_KEY_SIZE = 32; - public const int NC_SEC_PUBKEY_SIZE = 32; - public const int NC_ENCRYPTION_NONCE_SIZE = 32; - public const int NC_PUBKEY_SIZE = 32; - public const int NC_SIGNATURE_SIZE = 64; - public const int NC_CONV_KEY_SIZE = 32; - 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 uint NC_ENC_VERSION_NIP04 = 0x00000004u; - public const uint NC_ENC_VERSION_NIP44 = 0x00000002c; - - public const uint NC_ENC_SET_VERSION = 0x01u; - public const uint NC_ENC_SET_NIP44_NONCE = 0x02u; - public const uint NC_ENC_SET_NIP44_MAC_KEY = 0x03u; - public const uint NC_ENC_SET_NIP04_KEY = 0x04u; - public const uint NC_ENC_SET_NIP04_IV = 0x05u; - - public const NCResult NC_SUCCESS = 0; - public const byte E_NULL_PTR = 0x01; - public const byte E_INVALID_ARG = 0x02; - public const byte E_INVALID_CTX = 0x03; - public const byte E_ARGUMENT_OUT_OF_RANGE = 0x04; - public const byte E_OPERATION_FAILED = 0x05; - public const byte E_VERSION_NOT_SUPPORTED = 0x06; - - private readonly FunctionTable _functions = FunctionTable.BuildFunctionTable(Library); - - /// - /// Gets a reference to the loaded function table for - /// the native library - /// - internal ref readonly FunctionTable Functions - { - get - { - Check(); - Library.ThrowIfClosed(); - return ref _functions; - } - } - - /// - /// Gets a value that determines if the library has been released - /// - internal bool IsClosed => Library.IsClosed || Library.IsInvalid; - - /// - /// Initialize a new NCContext for use. This may be done once at app startup - /// and is thread-safe for the rest of the application lifetime. - /// - /// - /// Initialization entropy buffer - /// The size of the buffer (must be 32 bytes) - /// The inialized context - /// - /// - /// - public NCContext Initialize(IUnmangedHeap heap, ref readonly byte entropy32, int size) - { - ArgumentNullException.ThrowIfNull(heap); - - //Entropy must be exactly 32 bytes - ArgumentOutOfRangeException.ThrowIfNotEqual(size, CTX_ENTROPY_SIZE); - - //Get struct size - nuint ctxSize = Functions.NCGetContextStructSize.Invoke(); - - //Allocate the context with the struct alignment on a heap - IntPtr ctx = heap.Alloc(1, ctxSize, true); - try - { - NCResult result; - fixed (byte* p = &entropy32) - { - result = Functions.NCInitContext.Invoke(ctx, p); - } - - NCUtil.CheckResult(result, true); - - Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}"); - - return new NCContext(ctx, heap, this); - } - catch - { - heap.Free(ref ctx); - throw; - } - } - - /// - /// Initialize a new NCContext for use. This may be done once at app startup - /// and is thread-safe for the rest of the application lifetime. - /// - /// - /// The 32byte random seed/nonce for the noscrypt context - /// The inialized context - /// - /// - /// - public NCContext Initialize(IUnmangedHeap heap, ReadOnlySpan enropy32) - { - return Initialize( - heap, - ref MemoryMarshal.GetReference(enropy32), - enropy32.Length - ); - } - - /// - /// Initializes a new NostrCrypto context wraper directly that owns the internal context. - /// This may be done once at app startup and is thread-safe for the rest of the - /// application lifetime. - /// - /// - /// The heap to allocate the context from - /// The random entropy data to initialize the context with - /// The library wrapper handle - public NostrCrypto InitializeCrypto(IUnmangedHeap heap, ReadOnlySpan entropy32) - { - ArgumentNullException.ThrowIfNull(heap); - - //Create the crypto interface from the new context object - return new NostrCrypto( - context: Initialize(heap, entropy32), - ownsContext: true - ); - } - - /// - protected override void Free() - { - if (OwnsHandle) - { - Library.Dispose(); - Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}"); - } - } - - /// - /// Loads the native library from the specified path and initializes the - /// function table for use. - /// - /// The native library path or name to load - /// The search path options - /// The loaded library instance - public static LibNoscrypt Load(string path, DllImportSearchPath search) - { - //Load the native library - SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search); - - Trace.WriteLine($"Loaded noscrypt library 0x{handle.DangerousGetHandle():x} from {path}"); - - //Create the wrapper - return new LibNoscrypt(handle, true); - } - - /// - /// Loads the native library from the specified path and initializes the - /// function table for use. - /// - /// The native library path or name to load - /// The loaded library instance - public static LibNoscrypt Load(string path) => Load(path, DllImportSearchPath.SafeDirectories); - - } -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs index dbd9372..8f8c6b4 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs @@ -21,17 +21,16 @@ using Microsoft.Win32.SafeHandles; using VNLib.Utils.Extensions; using VNLib.Utils.Memory; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using VNLib.Utils.Cryptography.Noscrypt.@internal; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; using NCResult = System.Int64; namespace VNLib.Utils.Cryptography.Noscrypt { /// - /// Represents a context for the native library + /// The noscrypt library context /// - /// The heap the handle was allocated from - /// A reference to the native library public sealed class NCContext : SafeHandleZeroOrMinusOneIsInvalid { private readonly IUnmangedHeap Heap; @@ -39,9 +38,9 @@ namespace VNLib.Utils.Cryptography.Noscrypt /// /// The library this context was created from /// - public LibNoscrypt Library { get; } + public NoscryptLibrary Library { get; } - internal NCContext(IntPtr handle, IUnmangedHeap heap, LibNoscrypt library) :base(true) + internal NCContext(IntPtr handle, IUnmangedHeap heap, NoscryptLibrary library) :base(true) { ArgumentNullException.ThrowIfNull(heap); ArgumentNullException.ThrowIfNull(library); @@ -61,7 +60,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt public unsafe void Reinitalize(ref byte entropy, int size) { //Entropy must be exactly 32 bytes - ArgumentOutOfRangeException.ThrowIfNotEqual(size, CTX_ENTROPY_SIZE); + ArgumentOutOfRangeException.ThrowIfNotEqual(size, NC_CTX_ENTROPY_SIZE); this.ThrowIfClosed(); fixed (byte* p = &entropy) diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs index 69234fe..beb21c2 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs @@ -13,14 +13,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; namespace VNLib.Utils.Cryptography.Noscrypt { /// /// The NIP04 encryption version used by the Nostr protocol /// - public sealed class NCNip04EncryptionVersion : IEncryptionVersion + public sealed class NCNip04EncryptionVersion : INostrEncryptionVersion { /// /// A static nip04 encryption version instance @@ -28,10 +28,13 @@ namespace VNLib.Utils.Cryptography.Noscrypt public static readonly NCNip04EncryptionVersion Instance = new(); /// - uint IEncryptionVersion.Version => NC_ENC_VERSION_NIP04; - + uint INostrEncryptionVersion.Version => NC_ENC_VERSION_NIP04; + + /// + int INostrEncryptionVersion.GetMessageBufferSize(int dataSize) => Nip04Util.CalcBufferSize(dataSize); + /// - int IEncryptionVersion.CalcBufferSize(int dataSize) => Nip04Util.CalcBufferSize(dataSize); + int INostrEncryptionVersion.GetPayloadBufferSize(int dataSize) => Nip04Util.CalcBufferSize(dataSize); } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs index e572dd7..0d5907a 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs @@ -13,14 +13,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; namespace VNLib.Utils.Cryptography.Noscrypt { /// /// The NIP44 encryption version used by the Nostr protocol /// - public sealed class NCNip44EncryptionVersion : IEncryptionVersion + public sealed class NCNip44EncryptionVersion : INostrEncryptionVersion { /// /// A static nip44 encryption version instance @@ -28,10 +28,12 @@ namespace VNLib.Utils.Cryptography.Noscrypt public static readonly NCNip44EncryptionVersion Instance = new(); /// - uint IEncryptionVersion.Version => NC_ENC_VERSION_NIP44; + uint INostrEncryptionVersion.Version => NC_ENC_VERSION_NIP44; + + int INostrEncryptionVersion.GetMessageBufferSize(int dataSize) => Nip44Util.CalcFinalBufferSize(dataSize); /// - int IEncryptionVersion.CalcBufferSize(int dataSize) => Nip44Util.CalcBufferSize(dataSize); + int INostrEncryptionVersion.GetPayloadBufferSize(int dataSize) => Nip44Util.CalcBufferSize(dataSize); } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs index 0699d7d..57d7c3f 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs @@ -14,9 +14,10 @@ // along with this program. If not, see . using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; namespace VNLib.Utils.Cryptography.Noscrypt { @@ -26,6 +27,11 @@ namespace VNLib.Utils.Cryptography.Noscrypt [StructLayout(LayoutKind.Sequential, Size = NC_SEC_PUBKEY_SIZE)] public unsafe struct NCPublicKey { + /// + /// Gets a null reference. + /// + public static ref NCPublicKey NullRef => ref Unsafe.NullRef(); + private fixed byte key[NC_SEC_PUBKEY_SIZE]; } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs index b586d26..18f025b 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs @@ -13,9 +13,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; namespace VNLib.Utils.Cryptography.Noscrypt { @@ -26,6 +27,11 @@ namespace VNLib.Utils.Cryptography.Noscrypt [StructLayout(LayoutKind.Sequential, Size = NC_SEC_KEY_SIZE)] public unsafe struct NCSecretKey { + /// + /// Gets a null reference to a secret key + /// + public static ref NCSecretKey NullRef => ref Unsafe.NullRef(); + private fixed byte key[NC_SEC_KEY_SIZE]; } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs index e212125..49c66c1 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs @@ -18,7 +18,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; using NCResult = System.Int64; diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NcFallbackRandom.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NcFallbackRandom.cs new file mode 100644 index 0000000..0949ad8 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NcFallbackRandom.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 . + +using System; + +using VNLib.Hashing; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// + /// A fallback crypographic random source used for default + /// rng if you wish + /// + public sealed class NcFallbackRandom : IRandomSource + { + /// + /// Gets the shared instance of the fallback random source + /// + public static NcFallbackRandom Shared { get; } = new NcFallbackRandom(); + + /// + public void GetRandomBytes(Span buffer) => RandomHash.GetRandomBytes(buffer); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs index 82704db..c1906f0 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs @@ -14,6 +14,7 @@ // along with this program. If not, see . using System; +using System.Buffers.Text; using VNLib.Utils.Extensions; @@ -36,6 +37,14 @@ namespace VNLib.Utils.Cryptography.Noscrypt { throw new NotImplementedException(); } + + static readonly int MaxEncodedIvLength = Base64.GetMaxEncodedToUtf8Length(16); + + public static int CalcMessageBufferSize(int dataSize) + { + int bufSize = CalcBufferSize(dataSize); + return bufSize + "?iv=".Length + MaxEncodedIvLength; + } } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Message.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Message.cs deleted file mode 100644 index b78530b..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Message.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 . - -using System; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - public readonly ref struct Nip44Message(ReadOnlySpan payload) - { - readonly ReadOnlySpan _payload = payload; - - public ReadOnlySpan Payload => _payload; - - public ReadOnlySpan Nonce => Nip44Util.GetNonceFromPayload(_payload); - - public ReadOnlySpan Ciphertext => Nip44Util.GetCiphertextFromPayload(_payload); - - public ReadOnlySpan Mac => Nip44Util.GetMacFromPayload(_payload); - - public ReadOnlySpan NonceAndCiphertext => Nip44Util.GetNonceAndCiphertext(_payload); - - public byte Version => Nip44Util.GetMessageVersion(_payload); - } -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.cs new file mode 100644 index 0000000..ddc2d68 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.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 . + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public readonly ref struct Nip44MessageSegments(ReadOnlySpan payload) + { + readonly ReadOnlySpan _payload = payload; + + public ReadOnlySpan Payload => _payload; + + public ReadOnlySpan Nonce => Nip44Util.GetNonceFromPayload(_payload); + + public ReadOnlySpan Ciphertext => Nip44Util.GetCiphertextFromPayload(_payload); + + public ReadOnlySpan Mac => Nip44Util.GetMacFromPayload(_payload); + + public ReadOnlySpan NonceAndCiphertext => Nip44Util.GetNonceAndCiphertext(_payload); + + public byte Version => Nip44Util.GetMessageVersion(_payload); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs index 23c9127..2aebee1 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs @@ -19,7 +19,7 @@ using System.Runtime.InteropServices; using VNLib.Utils.Memory; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; namespace VNLib.Utils.Cryptography.Noscrypt { @@ -92,16 +92,51 @@ namespace VNLib.Utils.Cryptography.Noscrypt //Copy the plaintext data to the output buffer after the data size MemoryUtil.Memmove( - in MemoryMarshal.GetReference(plaintextData), - 0, - ref MemoryMarshal.GetReference(output), - sizeof(ushort), - (uint)plaintextData.Length + src: in MemoryMarshal.GetReference(plaintextData), + srcOffset: 0, + dst: ref MemoryMarshal.GetReference(output), + dstOffset: sizeof(ushort), + elementCount: (uint)plaintextData.Length ); //We assume the remaining buffer is zeroed out } + public static void WriteNip44Message( + ReadOnlySpan payloadBuffer, + byte version, + ReadOnlySpan mac, + Span outBuffer + ) + { + int requiredBufferSize = CalcFinalBufferSize(payloadBuffer.Length); + + //Make sure the output buffer is large enough so we dont overrun it + ArgumentOutOfRangeException.ThrowIfLessThan(outBuffer.Length, requiredBufferSize, nameof(outBuffer)); + ArgumentOutOfRangeException.ThrowIfLessThan(mac.Length, NC_ENCRYPTION_MAC_SIZE, nameof(mac)); + + //Write the version number to the first byte + outBuffer[0] = version; + + //Copy the payload buffer to the output buffer after the version number + MemoryUtil.Memmove( + src: in MemoryMarshal.GetReference(payloadBuffer), + srcOffset: 0, + dst: ref MemoryMarshal.GetReference(outBuffer), + dstOffset: 1, + elementCount: (uint)payloadBuffer.Length + ); + + //Copy the mac to the end of the output buffer + MemoryUtil.Memmove( + src: in MemoryMarshal.GetReference(mac), + srcOffset: 0, + dst: ref MemoryMarshal.GetReference(outBuffer), + dstOffset: (uint)(requiredBufferSize - NC_ENCRYPTION_MAC_SIZE), + elementCount: NC_ENCRYPTION_MAC_SIZE + ); + } + public static ReadOnlySpan GetNonceFromPayload(ReadOnlySpan message) { //The nonce is 32 bytes following the 1st byte version number of the message @@ -139,6 +174,17 @@ namespace VNLib.Utils.Cryptography.Noscrypt return plaintextPayload.Slice(sizeof(ushort), ptLength); } + public static bool IsValidPlaintextMessage(ReadOnlySpan plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return ptLength == plaintextPayload.Length - sizeof(ushort); + } + + public static Range GetPlaintextRange(ReadOnlySpan plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return new Range(sizeof(ushort), ptLength); + } } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs index aaabc08..89bd057 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs @@ -16,7 +16,7 @@ using System; using System.Runtime.InteropServices; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; namespace VNLib.Utils.Cryptography.Noscrypt { diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs new file mode 100644 index 0000000..108a713 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs @@ -0,0 +1,242 @@ +// 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 . + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using VNLib.Utils.Memory; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +using VNLib.Utils.Cryptography.Noscrypt.@internal; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// + /// Initializes the native library and provides access to the native functions + /// + /// An existing noscrypt library handle + /// A value that indicates if the instance owns the library handle + public unsafe sealed class NoscryptLibrary(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable + { + public const string NoscryptDefaultLibraryName = "noscrypt"; + + //Constant values match the noscrypt.h header + public const int NC_SEC_KEY_SIZE = 0x20; + public const int NC_SEC_PUBKEY_SIZE = 0x20; + public const int NC_ENCRYPTION_NONCE_SIZE = 0x20; + public const int NC_PUBKEY_SIZE = 0x20; + public const int NC_SIGNATURE_SIZE = 0x40; + public const int NC_CONV_KEY_SIZE = 0x20; + public const int NC_MESSAGE_KEY_SIZE = 0x20; + public const int NC_HMAC_KEY_SIZE = 0x20; + public const int NC_ENCRYPTION_MAC_SIZE = 0x20; + public const int NC_CONVERSATION_KEY_SIZE = 0x20; + public const int NC_CTX_ENTROPY_SIZE = 0x20; + public const int NC_SIG_ENTROPY_SIZE = 0x20; + + public const uint NC_ENC_VERSION_NIP04 = 0x00000004u; + public const uint NC_ENC_VERSION_NIP44 = 0x00000002c; + + public const uint NC_ENC_SET_VERSION = 0x01u; + public const uint NC_ENC_SET_NIP44_NONCE = 0x02u; + public const uint NC_ENC_SET_NIP44_MAC_KEY = 0x03u; + public const uint NC_ENC_SET_NIP04_KEY = 0x04u; + public const uint NC_ENC_SET_NIP04_IV = 0x05u; + + //Noscrypt error codes + public const NCResult NC_SUCCESS = 0x00; + public const byte E_NULL_PTR = 0x01; + public const byte E_INVALID_ARG = 0x02; + public const byte E_INVALID_CTX = 0x03; + public const byte E_ARGUMENT_OUT_OF_RANGE = 0x04; + public const byte E_OPERATION_FAILED = 0x05; + public const byte E_VERSION_NOT_SUPPORTED = 0x06; + + private readonly FunctionTable _functions = FunctionTable.BuildFunctionTable(Library); + + /// + /// Gets a reference to the loaded function table for + /// the native library + /// + internal ref readonly FunctionTable Functions + { + get + { + Check(); + Library.ThrowIfClosed(); + return ref _functions; + } + } + + /// + /// Gets a value that determines if the library has been released + /// + internal bool IsClosed => Library.IsClosed || Library.IsInvalid; + + /// + /// Initialize a new NCContext for use. This may be done once at app startup + /// and is thread-safe for the rest of the application lifetime. + /// + /// + /// Initialization entropy buffer + /// The size of the buffer (must be 32 bytes) + /// The inialized context + /// + /// + /// + public NCContext Initialize(IUnmangedHeap heap, ref readonly byte entropy32, int size) + { + ArgumentNullException.ThrowIfNull(heap); + + //Entropy must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(size, NC_CTX_ENTROPY_SIZE); + + //Get struct size + nuint ctxSize = Functions.NCGetContextStructSize.Invoke(); + + //Allocate the context with the struct alignment on a heap + IntPtr ctx = heap.Alloc(1, ctxSize, true); + try + { + NCResult result; + fixed (byte* p = &entropy32) + { + result = Functions.NCInitContext.Invoke(ctx, p); + } + + NCUtil.CheckResult(result, true); + + Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}"); + + return new NCContext(ctx, heap, this); + } + catch + { + heap.Free(ref ctx); + throw; + } + } + + /// + /// Initialize a new NCContext for use. This may be done once at app startup + /// and is thread-safe for the rest of the application lifetime. + /// + /// + /// The 32byte random seed/nonce for the noscrypt context + /// The inialized context + /// + /// + /// + public NCContext Initialize(IUnmangedHeap heap, ReadOnlySpan enropy32) + { + return Initialize( + heap, + ref MemoryMarshal.GetReference(enropy32), + enropy32.Length + ); + } + + /// + /// Initializes a new NostrCrypto context wraper directly that owns the internal context. + /// This may be done once at app startup and is thread-safe for the rest of the + /// application lifetime. + /// + /// The heap to allocate the context from + /// The random entropy data to initialize the context with + /// The library wrapper handle + public NostrCrypto InitializeCrypto(IUnmangedHeap heap, ReadOnlySpan entropy32) + { + ArgumentNullException.ThrowIfNull(heap); + + //Create the crypto interface from the new context object + return new NostrCrypto( + context: Initialize(heap, entropy32), + ownsContext: true + ); + } + + /// + /// Initializes a new NostrCrypto context wraper directly that owns the internal context. + /// This may be done once at app startup and is thread-safe for the rest of the + /// application lifetime. + /// + /// The heap to allocate the context from + /// Random source used to generate context entropy + /// The library wrapper handle + public NostrCrypto InitializeCrypto(IUnmangedHeap heap, IRandomSource random) + { + ArgumentNullException.ThrowIfNull(random); + + //Get random bytes for context entropy + Span entropy = stackalloc byte[NC_CTX_ENTROPY_SIZE]; + random.GetRandomBytes(entropy); + + NostrCrypto nc = InitializeCrypto(heap, entropy); + + MemoryUtil.InitializeBlock(entropy); + + return nc; + } + + /// + protected override void Free() + { + if (OwnsHandle) + { + Library.Dispose(); + Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}"); + } + } + + /// + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// + /// The native library path or name to load + /// The search path options + /// The loaded library instance + /// + public static NoscryptLibrary Load(string path, DllImportSearchPath search) + { + //Load the native library + SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search); + + Trace.WriteLine($"Loaded noscrypt library 0x{handle.DangerousGetHandle():x} from {path}"); + + //Create the wrapper + return new NoscryptLibrary(handle, true); + } + + /// + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// + /// The native library path or name to load + /// The loaded library instance + /// + public static NoscryptLibrary Load(string path) => Load(path, DllImportSearchPath.SafeDirectories); + + /// + /// Attempts to load the default noscrypt library from the system search path + /// + /// The loaded library instance + /// + public static NoscryptLibrary LoadDefault() => Load(NoscryptDefaultLibraryName, DllImportSearchPath.SafeDirectories); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs new file mode 100644 index 0000000..586fa46 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs @@ -0,0 +1,133 @@ +// 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 . + +using System; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// + /// A simple wrapper class to sign nostr message data using + /// the noscrypt library + /// + /// The noscrypt library instance + /// A random entropy pool used to source random data for signature entropy + public class NoscryptSigner(INostrCrypto noscrypt, IRandomSource random) + { + /// + /// Gets the size of the buffer required to hold the signature + /// + public static int SignatureBufferSize => NC_SIGNATURE_SIZE; + + /// + /// Signs a message using the specified private key and message data + /// + /// The hexadecimal private key used to sign the message + /// The message data to sign + /// A encoder used to convert the signature data to an encoded string + /// The string encoded nostr signature + /// + /// + public string SignData(string hexPrivateKey, ReadOnlySpan message, INostrSignatureEncoder? format = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(hexPrivateKey); + ArgumentOutOfRangeException.ThrowIfNotEqual(hexPrivateKey.Length / 2, NC_SEC_KEY_SIZE, nameof(hexPrivateKey)); + + //Have to allocate array unfortunately + byte[] privKey = Convert.FromHexString(hexPrivateKey); + try + { + return SignData(privKey.AsSpan(), message, format); + } + finally + { + //Always zero key beofre leaving + MemoryUtil.InitializeBlock(privKey); + } + } + + /// + /// Signs a message using the specified secret key and message data + /// + /// The secret key data buffer + /// The message data to sign + /// A encoder used to convert the signature data to an encoded string + /// The string encoded nostr signature + /// + /// + public string SignData( + ReadOnlySpan secretKey, + ReadOnlySpan message, + INostrSignatureEncoder? format = null + ) + { + return SignData(in NCUtil.AsSecretKey(secretKey), message, format); + } + + /// + /// Signs a message using the specified secret key and message data + /// + /// A reference to the secret key structurer + /// The message data to sign + /// A encoder used to convert the signature data to an encoded string + /// The string encoded nostr signature + /// + /// + public string SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan message, + INostrSignatureEncoder? format = null + ) + { + //Default to hex encoding because that is the default NIP-01 format + format ??= HexSignatureEncoder.Instance; + + Span sigBuffer = stackalloc byte[SignatureBufferSize]; + + SignData(message, sigBuffer); + + return format.GetString(sigBuffer); + } + + + /// + /// Signs a message using the specified secret key and message data + /// + /// A reference to the secret key structurer + /// The message data to sign + /// A buffer to write signature data to + /// + /// + public void SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan data, + Span signature + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signature.Length, NC_SIGNATURE_SIZE, nameof(signature)); + + //Signature generation required random entropy to be secure + Span entropy = stackalloc byte[NC_SIG_ENTROPY_SIZE]; + random.GetRandomBytes(entropy); + + noscrypt.SignData(in secretkey, entropy, data, signature); + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs index ec2cf66..36e2381 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs @@ -18,7 +18,7 @@ using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; using VNLib.Utils.Cryptography.Noscrypt.@internal; -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; using NCResult = System.Int64; @@ -86,7 +86,11 @@ namespace VNLib.Utils.Cryptography.Noscrypt fixed (NCSecretKey* pSecKey = &secretKey) fixed (NCPublicKey* pPubKey = &publicKey) - fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pHmacKeyOut = &hmackKeyOut32, pNonce = &nonce32) + fixed (byte* pCipherText = &cipherText, + pTextPtr = &plainText, + pHmacKeyOut = &hmackKeyOut32, + pNonce = &nonce32 + ) { NCEncryptionArgs data = new(); @@ -227,7 +231,13 @@ namespace VNLib.Utils.Cryptography.Noscrypt } } - public void ComputeMac(ref readonly byte hmacKey32, ref readonly byte payload, uint payloadSize, ref byte hmacOut32) + /// + public void ComputeMac( + ref readonly byte hmacKey32, + ref readonly byte payload, + uint payloadSize, + ref byte hmacOut32 + ) { Check(); diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs deleted file mode 100644 index c70839c..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs +++ /dev/null @@ -1,234 +0,0 @@ -// 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 . - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using VNLib.Utils.Extensions; -using VNLib.Utils.Memory; - -using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - - public sealed class NostrEncryptedMessage(IEncryptionVersion version, INostrCrypto lib) : VnDisposeable - { - private readonly INostrCrypto library = lib; - - private NCSecretKey _fromKey; - private NCPublicKey _toKey; - private Buffer32 _nonce32; - - /// - /// The message encryption version used by this instance - /// - public uint Version { get; } = version.Version; - - /// - /// The message nonce created during encryption event - /// - public unsafe Span Nonce - { - get - { - Debug.Assert(NC_ENCRYPTION_NONCE_SIZE == sizeof(Buffer32)); - return MemoryMarshal.CreateSpan(ref GetNonceRef(), sizeof(Buffer32)); - } - } - - /// - /// Gets the size of the buffer required to encrypt the specified data size - /// - /// The size of the message raw plaintext message to send - /// The minimum number of bytes required for message encryption output - /// - public int GetOutputBufferSize(int dataSize) - => version.CalcBufferSize(dataSize); - - /// - /// Sets the encryption secret key for the message - /// - /// The secret key buffer - /// The current instance for chaining - /// - public NostrEncryptedMessage SetSecretKey(ReadOnlySpan secKey) - => SetSecretKey(in NCUtil.AsSecretKey(secKey)); - - /// - /// Sets the encryption secret key for the message - /// - /// The secret key structure to copy - /// The current instance for chaining - /// - public NostrEncryptedMessage SetSecretKey(ref readonly NCSecretKey secKey) - { - MemoryUtil.CloneStruct(in secKey, ref _fromKey); - return this; - } - - /// - /// Assigns the public key used to encrypt the message as the - /// receiver of the message - /// - /// The user's public key receiving the message - /// The current instance for chaining - /// - public NostrEncryptedMessage SetPublicKey(ReadOnlySpan pubKey) - => SetPublicKey(in NCUtil.AsPublicKey(pubKey)); - - /// - /// Assigns the public key used to encrypt the message as the - /// receiver of the message - /// - /// The user's public key receiving the message - /// The current instance for chaining - /// - public NostrEncryptedMessage SetPublicKey(ref readonly NCPublicKey pubKey) - { - MemoryUtil.CloneStruct(in pubKey, ref _toKey); - return this; - } - - /// - /// Assigns the nonce to the message. Must be - /// in length - /// - /// The nonce value to copy - /// The current instance for chaining - /// - public NostrEncryptedMessage SetNonce(ReadOnlySpan nonce) - { - MemoryUtil.CopyStruct(nonce, ref _nonce32); - return this; - } - - /// - /// Assigns a random nonce using the specified random source - /// - /// The random source to genrate a random nonce from - /// The current instance for chaining - public NostrEncryptedMessage SetRandomNonce(IRandomSource rng) - { - rng.GetRandomBytes(Nonce); - return this; - } - - /// - /// Encrypts the plaintext message and writes the encrypted message to the - /// specified buffer, along with a 32 byte mac of the message - /// - /// The plaintext data to encrypt - /// The message output buffer to write encrypted data to - /// A buffer to write the computed message mac to - /// The number of bytes writtn to the message output buffer - /// - /// The message buffer must be at-least the size of the output buffer, and it is not - /// initialized before the encryption operation. - /// - /// - public int EncryptMessage(ReadOnlySpan plaintext, Span message, Span macOut32) - { - return Version switch - { - NC_ENC_VERSION_NIP44 => EncryptNip44(plaintext, message, macOut32), - _ => throw new NotSupportedException("NIP04 encryption is not supported"), - }; - } - - private int EncryptNip44(ReadOnlySpan plaintext, Span message, Span macOut32) - { - int payloadSize = GetOutputBufferSize(plaintext.Length); - - ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); - ArgumentOutOfRangeException.ThrowIfZero(message.Length, nameof(message)); - ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, payloadSize, nameof(message)); - ArgumentOutOfRangeException.ThrowIfLessThan(macOut32.Length, NC_ENCRYPTION_MAC_SIZE, nameof(macOut32)); - - /* - * Alloc temp buffer to copy formatted payload to data to for the encryption - * operation. Encryption will write directly to the message buffer - */ - - using UnsafeMemoryHandle ptPayloadBuf = MemoryUtil.UnsafeAllocNearestPage(payloadSize, true); - using UnsafeMemoryHandle hmacKeyBuf = MemoryUtil.UnsafeAlloc(NC_HMAC_KEY_SIZE, true); - Debug.Assert(hmacKeyBuf.Length == NC_HMAC_KEY_SIZE); - - Nip44Util.FormatBuffer(plaintext, ptPayloadBuf.Span, false); - - library.EncryptNip44( - in _fromKey, - in _toKey, - in GetNonceRef(), - in ptPayloadBuf.GetReference(), - ref MemoryMarshal.GetReference(message), - (uint)payloadSize, //IMPORTANT: Format buffer will pad the buffer to the exact size - ref hmacKeyBuf.GetReference() //Must set the hmac key buffer - ); - - //Safe to clear the plain text copy buffer - MemoryUtil.InitializeBlock( - ref ptPayloadBuf.GetReference(), - ptPayloadBuf.GetIntLength() - ); - - - //Compute message mac, key should be set by the encryption operation - library.ComputeMac( - in hmacKeyBuf.GetReference(), - in MemoryMarshal.GetReference(message), - (uint)payloadSize, //Again set exact playload size - ref MemoryMarshal.GetReference(macOut32) - ); - - //Safe to clear the hmac key buffer - MemoryUtil.InitializeBlock( - ref hmacKeyBuf.GetReference(), - hmacKeyBuf.GetIntLength() - ); - - return payloadSize; - } - - private ref byte GetNonceRef() => ref Unsafe.As(ref _nonce32); - - protected override void Free() - { - //Zero all internal memory - MemoryUtil.ZeroStruct(ref _fromKey); - MemoryUtil.ZeroStruct(ref _toKey); - MemoryUtil.ZeroStruct(ref _nonce32); - } - - /// - /// Initializes a new with the nip44 encryption - /// method. - /// - /// The nostr crypto implementation instance to use - /// The intialzied message instance - public static NostrEncryptedMessage CreateNip44Cipher(INostrCrypto lib) - => new(NCNip44EncryptionVersion.Instance, lib); - - - [StructLayout(LayoutKind.Sequential, Size = 32)] - unsafe struct Buffer32 - { - fixed byte value[32]; - } - } - -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs new file mode 100644 index 0000000..0982d3a --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs @@ -0,0 +1,424 @@ +// 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 . + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Authentication; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + public sealed class NostrMessageCipher(INostrCrypto lib, INostrEncryptionVersion version) : VnDisposeable + { + const int Nip44MaxMessageSize = 65603; + + private readonly INostrCrypto library = lib; + + private NCSecretKey _fromKey; + private NCPublicKey _toKey; + private Buffer32 _nonce32; + private Buffer32 _mac32; + + /// + /// The message encryption version used by this instance + /// + public uint Version { get; } = version.Version; + + /// + /// The message nonce created during encryption event + /// + public unsafe Span Nonce => MemoryMarshal.CreateSpan(ref GetNonceRef(), sizeof(Buffer32)); + + /// + /// The message MAC set during encryption, and required for decryption + /// + public unsafe Span Mac => MemoryMarshal.CreateSpan(ref GetMacRef(), sizeof(Buffer32)); + + /// + /// Gets the size of the buffer required to encrypt the specified data size + /// + /// The size of the message raw plaintext message to send + /// The minimum number of bytes required for message encryption output + /// + public int GetPayloadBufferSize(int dataSize) + => version.GetPayloadBufferSize(dataSize); + + /// + /// Gets the size of the buffer required to hold the full encrypted message data + /// for the encryption version used + /// + /// The plaintext data size + /// The estimated size of the output buffer + public int GetMessageBufferSize(int dataSize) + => version.GetMessageBufferSize(dataSize); + + /// + /// Sets the encryption secret key for the message + /// + /// The secret key buffer + /// The current instance for chaining + /// + public NostrMessageCipher SetSecretKey(ReadOnlySpan secKey) + => SetSecretKey(in NCUtil.AsSecretKey(secKey)); + + /// + /// Sets the encryption secret key for the message + /// + /// The secret key structure to copy + /// The current instance for chaining + /// + public NostrMessageCipher SetSecretKey(ref readonly NCSecretKey secKey) + { + MemoryUtil.CloneStruct(in secKey, ref _fromKey); + return this; + } + + /// + /// Assigns the public key used to encrypt the message as the + /// receiver of the message + /// + /// The user's public key receiving the message + /// The current instance for chaining + /// + public NostrMessageCipher SetPublicKey(ReadOnlySpan pubKey) + => SetPublicKey(in NCUtil.AsPublicKey(pubKey)); + + /// + /// Assigns the public key used to encrypt the message as the + /// receiver of the message + /// + /// The user's public key receiving the message + /// The current instance for chaining + /// + public NostrMessageCipher SetPublicKey(ref readonly NCPublicKey pubKey) + { + MemoryUtil.CloneStruct(in pubKey, ref _toKey); + return this; + } + + /// + /// Assigns the nonce to the message. Must be + /// in length + /// + /// The nonce value to copy + /// The current instance for chaining + /// + public NostrMessageCipher SetNonce(ReadOnlySpan nonce) + { + MemoryUtil.CopyStruct(nonce, ref _nonce32); + return this; + } + + /// + /// Assigns a random nonce using the specified random source + /// + /// The random source to genrate a random nonce from + /// The current instance for chaining + public NostrMessageCipher SetRandomNonce(IRandomSource rng) + { + rng.GetRandomBytes(Nonce); + return this; + } + + /// + /// Configures a 32 byte mac for the message for nip44 decryption + /// + /// The message mac + /// The current instance for chaining + public NostrMessageCipher SetMac(ReadOnlySpan mac) + { + MemoryUtil.CopyStruct(mac, ref _mac32); + return this; + } + + /// + /// Decrypts a full nostr encrypted message and writes the plaintext + /// data to the output buffer + /// + /// The nostr message buffer to decrypt + /// The output plaintext buffer + /// The number of bytes written the the plaintext buffer + /// + /// + public int DecryptMessage(ReadOnlySpan message, Span plaintext) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => DecryptNip44Message(message, plaintext), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + /// + /// Encrypts the plaintext message and writes the encrypted message to the + /// specified buffer. The output matches the format of the full nostr message + /// for the specified encryption version + /// + /// The plaintext data to be encrypted + /// The buffer to write the encrypted message data to + /// The number of bytes written to the message buffer + /// + public int EncryptMessage(ReadOnlySpan plaintext, Span message) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44Message(plaintext, message), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44Message(ReadOnlySpan plaintext, Span message) + { + int minRequiredOutSize = Nip44Util.CalcFinalBufferSize(plaintext.Length); + + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, minRequiredOutSize, nameof(message)); + + ForwardOnlyWriter messageWriter = new(message); + + // From spec -> concat(version, nonce, ciphertext, mac) + messageWriter.Append(0x02); // Version + messageWriter.Append(Nonce); // nonce + + //Encrypt plaintext and write directly the message buffer + int written = EncryptPayload(plaintext, messageWriter.Remaining); + + messageWriter.Advance(written); + + //Append the message mac, it was writen after the encryption operation + messageWriter.Append(Mac); + + return messageWriter.Written; + } + + /// + /// Encrypts the plaintext message and writes the encrypted message to the + /// specified buffer, along with a 32 byte mac of the message + /// + /// The plaintext data to encrypt + /// The message output buffer to write encrypted data to + /// A buffer to write the computed message mac to + /// The number of bytes writtn to the message output buffer + /// + /// The message buffer must be at-least the size of the output buffer, and it is not + /// initialized before the encryption operation. + /// + /// + public int EncryptPayload(ReadOnlySpan plaintext, Span message) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44(plaintext, message), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44(ReadOnlySpan plaintext, Span message) + { + int payloadSize = GetPayloadBufferSize(plaintext.Length); + + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + ArgumentOutOfRangeException.ThrowIfZero(message.Length, nameof(message)); + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, payloadSize, nameof(message)); + + /* + * Alloc temp buffer to copy formatted payload to data to for the encryption + * operation. Encryption will write directly to the message buffer + */ + + using UnsafeMemoryHandle ptPayloadBuf = MemoryUtil.UnsafeAllocNearestPage(payloadSize, true); + using UnsafeMemoryHandle hmacKeyBuf = MemoryUtil.UnsafeAlloc(NC_HMAC_KEY_SIZE, true); + + Debug.Assert(hmacKeyBuf.Length == NC_HMAC_KEY_SIZE); + + Nip44Util.FormatBuffer(plaintext, ptPayloadBuf.Span, false); + + library.EncryptNip44( + secretKey: in _fromKey, + publicKey: in _toKey, + nonce32: in GetNonceRef(), + plainText: in ptPayloadBuf.GetReference(), + cipherText: ref MemoryMarshal.GetReference(message), + size: (uint)payloadSize, //IMPORTANT: Format buffer will pad the buffer to the exact size + hmacKeyOut32: ref hmacKeyBuf.GetReference() //Must set the hmac key buffer + ); + + + //Compute message mac, key should be set by the encryption operation + library.ComputeMac( + hmacKey32: in hmacKeyBuf.GetReference(), + payload: in MemoryMarshal.GetReference(message), + payloadSize: (uint)payloadSize, //Again set exact playload size + hmacOut32: ref GetMacRef() + ); + + //Clear buffers + MemoryUtil.InitializeBlock(ref hmacKeyBuf.GetReference(), hmacKeyBuf.IntLength); + MemoryUtil.InitializeBlock(ref ptPayloadBuf.GetReference(), ptPayloadBuf.IntLength); + + return payloadSize; + } + + private int DecryptNip44Message(ReadOnlySpan message, Span plaintext) + { + //Full Nip44 messages must be at-least 99 bytes in length + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, 99, nameof(message)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(message.Length, Nip44MaxMessageSize, nameof(message)); + + //Message decoder used to get the nip44 message segments + Nip44MessageSegments msg = new(message); + + if (msg.Version != 0x02) + { + return 0; + } + + SetNonce(msg.Nonce); + SetMac(msg.Mac); + + //Temporary buffer to write decrypted plaintext data to + using UnsafeMemoryHandle plaintextBuffer = MemoryUtil.UnsafeAllocNearestPage(msg.Ciphertext.Length, true); + + int written = DecryptPayload(msg.Ciphertext, plaintextBuffer.Span); + + Span ptOut = plaintextBuffer.AsSpan(0, written); + + //Must check message bounds before returning a range + if (!Nip44Util.IsValidPlaintextMessage(ptOut)) + { + throw new FormatException("Plaintext data was not properly encrypted because it was not properly formatted or decryption failed"); + } + + Range msgRange = Nip44Util.GetPlaintextRange(ptOut); + Debug.Assert(msgRange.Start.Value > 0); + Debug.Assert(msgRange.End.Value > 0); + + int ptLength = msgRange.End.Value - msgRange.Start.Value; + + Debug.Assert(ptLength > 0); + + //Write the wrapped plaintext (unpadded) to the output plaintext buffer + MemoryUtil.Memmove( + src: in plaintextBuffer.GetReference(), + srcOffset: (uint)msgRange.Start.Value, + dst: ref MemoryMarshal.GetReference(plaintext), + dstOffset: 0, + elementCount: (uint)ptLength + ); + + return ptLength; + } + + /// + /// Decrypts a nostr encrypted message in it's full binary from. + /// + /// + /// + /// The number of bytes written to the output buffer, or an error code if an error occured during the encryption + /// + public int DecryptPayload(ReadOnlySpan payload, Span plaintext) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => DecryptNip44Payload(payload, plaintext), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int DecryptNip44Payload(ReadOnlySpan message, Span plaintext) + { + ArgumentOutOfRangeException.ThrowIfZero(message.Length, nameof(message)); + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + + //Validate the incoming message for a nip44 message + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, 32, nameof(message)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(message.Length, Nip44MaxMessageSize, nameof(message)); + + //Plaintext buffer must be large enough to hold the decrypted message + ArgumentOutOfRangeException.ThrowIfLessThan(plaintext.Length, message.Length, nameof(plaintext)); + + bool macValid = library.VerifyMac( + in _fromKey, + in _toKey, + nonce32: in GetNonceRef(), + mac32: in GetMacRef(), + payload: ref MemoryMarshal.GetReference(message), + (uint)message.Length + ); + + if (!macValid) + { + throw new AuthenticationException("Message MAC is invalid"); + } + + library.DecryptNip44( + in _fromKey, + in _toKey, + nonce32: in GetNonceRef(), + cipherText: in MemoryMarshal.GetReference(message), + plainText: ref MemoryMarshal.GetReference(plaintext), + (uint)message.Length + ); + + //Return the number of bytes written to the output buffer + return message.Length; + } + + private unsafe ref byte GetNonceRef() + { + Debug.Assert(NC_ENCRYPTION_NONCE_SIZE == sizeof(Buffer32)); + return ref Unsafe.As(ref _nonce32); + } + + private unsafe ref byte GetMacRef() + { + Debug.Assert(NC_ENCRYPTION_MAC_SIZE == sizeof(Buffer32)); + return ref Unsafe.As(ref _mac32); + } + + protected override void Free() + { + //Zero all internal memory + MemoryUtil.ZeroStruct(ref _fromKey); + MemoryUtil.ZeroStruct(ref _toKey); + MemoryUtil.ZeroStruct(ref _nonce32); + MemoryUtil.ZeroStruct(ref _mac32); + } + + /// + /// Initializes a new with the nip44 encryption + /// method. + /// + /// The nostr crypto implementation instance to use + /// The intialzied message instance + public static NostrMessageCipher CreateNip44Cipher(INostrCrypto lib) + => new(lib, NCNip44EncryptionVersion.Instance); + + + [StructLayout(LayoutKind.Sequential, Size = 32)] + unsafe struct Buffer32 + { + fixed byte value[32]; + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs index b55e9ff..73a62d9 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs @@ -3,10 +3,10 @@ using System; using System.Text; using System.Text.Json; -using System.Runtime.CompilerServices; using VNLib.Hashing; using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; namespace VNLib.Utils.Cryptography.Noscrypt.Tests { @@ -14,7 +14,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests public class LibNoscryptTests : IDisposable { - const string NoscryptLibWinDebug = @"../../../../../../../out/build/x64-debug/noscrypt.dll"; + const string NoscryptLibWinDebug = @"../../../../../../../out/build/x64-debug/Debug/noscrypt.dll"; //Keys generated using npx noskey package @@ -27,14 +27,14 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests const string Nip44VectorTestFile = "nip44.vectors.json"; #nullable disable - private LibNoscrypt _testLib; + private NoscryptLibrary _testLib; private JsonDocument _testVectors; #nullable enable [TestInitialize] public void Initialize() { - _testLib = LibNoscrypt.Load(NoscryptLibWinDebug); + _testLib = NoscryptLibrary.Load(NoscryptLibWinDebug); _testVectors = JsonDocument.Parse(File.ReadAllText(Nip44VectorTestFile)); } @@ -126,11 +126,11 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests //noThrow (its a bad sec key but it should not throw) crypto.ValidateSecretKey(ref secKey); - Assert.ThrowsException(() => crypto.ValidateSecretKey(ref Unsafe.NullRef())); + Assert.ThrowsException(() => crypto.ValidateSecretKey(ref NCSecretKey.NullRef)); //public key - Assert.ThrowsException(() => crypto.GetPublicKey(ref Unsafe.NullRef(), ref pubKey)); - Assert.ThrowsException(() => crypto.GetPublicKey(in secKey, ref Unsafe.NullRef())); + Assert.ThrowsException(() => crypto.GetPublicKey(ref NCSecretKey.NullRef, ref pubKey)); + Assert.ThrowsException(() => crypto.GetPublicKey(in secKey, ref NCPublicKey.NullRef)); } [TestMethod()] @@ -159,48 +159,41 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests { using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); - Span macOut32 = stackalloc byte[32]; + using NostrMessageCipher cipher = NostrMessageCipher.CreateNip44Cipher(nc); foreach (EncryptionVector v in GetEncryptionVectors()) - { - using NostrEncryptedMessage msg = NostrEncryptedMessage.CreateNip44Cipher(nc); + { 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); - ReadOnlySpan conversationKey = Convert.FromHexString(v.conversation_key); - - Nip44Message nip44Message = new(message); - - int ptSize = msg.GetOutputBufferSize(plainText.Length); - - Assert.AreEqual(nip44Message.Ciphertext.Length, ptSize); - Assert.AreEqual(nip44Message.Version, 0x02); - Assert.IsTrue(nonce.SequenceEqual(nip44Message.Nonce)); NCPublicKey pub2; //Recover public keys nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); - Span actualCiphertext = new byte[ptSize + 32]; + int outBufferSize = cipher.GetMessageBufferSize(plainText.Length); + + Span encryptedNote = new byte[outBufferSize]; - msg.SetSecretKey(secKey1) + cipher.SetSecretKey(secKey1) .SetPublicKey(in pub2) .SetNonce(nonce); - int written = msg.EncryptMessage(plainText, actualCiphertext, macOut32); + int written = cipher.EncryptMessage(plainText, encryptedNote); + Assert.IsTrue(written > 0); - actualCiphertext = actualCiphertext[..written]; + encryptedNote = encryptedNote[..written]; //Make sure the cipher text matches the expected payload - if (!actualCiphertext.SequenceEqual(nip44Message.Ciphertext)) + if (!encryptedNote.SequenceEqual(message)) { 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"); + Console.WriteLine($" \n{Convert.ToHexString(encryptedNote)}\n{Convert.ToHexString(message)}"); + Assert.Fail($"Cipher text does not match expected message"); } } } @@ -216,7 +209,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); ReadOnlySpan message = Convert.FromBase64String(v.payload); - Nip44Message nip44Message = new(message); + Nip44MessageSegments nip44Message = new(message); Assert.AreEqual(nip44Message.Version, 0x02); NCPublicKey pub2; @@ -277,9 +270,11 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests [TestMethod()] public void CorrectDecryptionTest() { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, NcFallbackRandom.Shared); - Span hmacKeyOut = stackalloc byte[LibNoscrypt.NC_HMAC_KEY_SIZE]; + using NostrMessageCipher msgCipher = NostrMessageCipher.CreateNip44Cipher(nc); + + using IMemoryHandle ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); foreach (EncryptionVector vector in GetEncryptionVectors()) { @@ -289,38 +284,30 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests ReadOnlySpan nonce = Convert.FromHexString(vector.nonce); ReadOnlySpan message = Convert.FromBase64String(vector.payload); - Nip44Message nip44Message = new(message); - - Assert.IsTrue(nip44Message.Version == 0x02); - Assert.IsTrue(nonce.SequenceEqual(nip44Message.Nonce)); - - NCPublicKey pub1; + NCPublicKey pub1 = default; //Recover public keys nc.GetPublicKey(in NCUtil.AsSecretKey(secKey1), ref pub1); - - Span 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 - ); - ReadOnlySpan actualPt = Nip44Util.GetPlaintextMessage(plaintextOut); + msgCipher.SetPublicKey(in pub1) + .SetSecretKey(secKey2); + + int outLen = msgCipher.DecryptMessage(message, ptBuffer.Span); + + Assert.IsTrue(outLen > 0); - Assert.AreEqual(expectedPt.Length, actualPt.Length); - Assert.IsTrue(actualPt.SequenceEqual(expectedPt)); + Span plaintext = ptBuffer.AsSpan(0, outLen); - MemoryUtil.InitializeBlock(hmacKeyOut); + 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"); + } + else + { + Assert.IsTrue(nonce.SequenceEqual(msgCipher.Nonce)); + } } } -- cgit