diff options
author | vnugent <public@vaughnnugent.com> | 2024-06-10 22:08:52 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-06-10 22:08:52 -0400 |
commit | 5e32450ccf9186e86a7596a7d774621cf81c62ff (patch) | |
tree | f30f3671357f8c47fba448347d30fdf436c18227 /wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src | |
parent | a74f96251bcc81fb2c94fe75dd6f8043fd35fe0b (diff) |
feat: Begin migrating noscrypt c# library
Diffstat (limited to 'wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src')
22 files changed, 2225 insertions, 0 deletions
diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.cs new file mode 100644 index 0000000..e9aab2b --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IEncryptionVersion.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/>. + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a message encryption version used by the Nostr protocol + /// </summary> + public interface IEncryptionVersion + { + /// <summary> + /// The noscrypt compatible encryption version + /// </summary> + internal uint Version { get; } + + /// <summary> + /// Calculates the required buffer size for the specified data size + /// </summary> + /// <param name="dataSize">The size of the input data</param> + /// <returns>The estimated size of the buffer required to complete the opeation</returns> + internal int CalcBufferSize(int dataSize); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs new file mode 100644 index 0000000..49c0cc0 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs @@ -0,0 +1,164 @@ +// 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 VNLib.Utils.Cryptography.Noscrypt +{ + public interface INostrCrypto + { + + /// <summary> + /// Gets a nostr public key from a secret key. + /// </summary> + /// <param name="secretKey">A reference to the secret key to get the public key from</param> + /// <param name="publicKey">A reference to the public key structure to write the recovered key to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey); + + /// <summary> + /// Validates a secret key is in a valid format. + /// </summary> + /// <param name="secretKey">A readonly reference to key structure to validate</param> + /// <returns>True if the key is consiered valid against the secp256k1 curve</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + bool ValidateSecretKey(ref readonly NCSecretKey secretKey); + + /// <summary> + /// Signs the supplied data with the secret key and random32 nonce, then writes + /// the message signature to the supplied sig64 buffer. + /// </summary> + /// <param name="secretKey">The secret key used to sign the message</param> + /// <param name="random32">A highly secure random nonce used to seed the signature</param> + /// <param name="data">A pointer to the first byte in the message to sign</param> + /// <param name="dataSize">The size of the message in bytes</param> + /// <param name="sig64">A pointer to the first byte of a 64 byte buffer used to write the message signature to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + void SignData( + ref readonly NCSecretKey secretKey, + ref readonly byte random32, + ref readonly byte data, + uint dataSize, + ref byte sig64 + ); + + /// <summary> + /// Performs cryptographic verification of the supplied data + /// against the supplied public key. + /// </summary> + /// <param name="pubKey">The signer's public key</param> + /// <param name="data">A pointer to the first byte in the message to sign</param> + /// <param name="dataSize">The number of bytes in the message</param> + /// <param name="sig64">A pointer to the signature buffer</param> + /// <returns>True if the signature could be verified against the public key. False otherwise</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + bool VerifyData( + ref readonly NCPublicKey pubKey, + ref readonly byte data, + uint dataSize, + ref readonly byte sig64 + ); + + /// <summary> + /// Computes a nip44 message authentication code (MAC) using the supplied key and payload. + /// </summary> + /// <param name="hmacKey32">The key returned during a + /// <see cref="Encrypt(ref readonly NCSecretKey, ref readonly NCPublicKey, ref readonly byte, ref readonly byte, ref byte, uint, ref byte)"/> + /// </param> + /// <param name="payload">A pointer to a buffer </param> + /// <param name="payloadSize">The size of the buffer to compute the mac of, in bytes</param> + /// <param name="hmacOut32">A pointer to the 32byte buffer to write the mac to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + void ComputeMac( + ref readonly byte hmacKey32, + ref readonly byte payload, + uint payloadSize, + ref byte hmacOut32 + ); + + /// <summary> + /// Verifies a nip44 message authentication code (MAC) against the supplied key and payload. + /// </summary> + /// <param name="secretKey">A pointer to the receiver's secret key</param> + /// <param name="publicKey">A pointer to senders the public key</param> + /// <param name="nonce32">A pointer to the 32byte nonce buffer</param> + /// <param name="mac32">A pointer to the 32byte message buffer</param> + /// <param name="payload">A pointer to the message buffer</param> + /// <param name="payloadSize">The size in bytes of the payload buffer</param> + /// <returns>True if the message authentication code (MAC) matches, false otherwise </returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + bool VerifyMac( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte mac32, + ref readonly byte payload, + uint payloadSize + ); + + /// <summary> + /// Encrypts a message using the supplied secret key, public key, and nonce. When this function + /// returns, the cipherText buffer will contain the encrypted message, and the hmacKeyOut32 buffer + /// will contain the key used to compute the message authentication code (MAC). + /// <para> + /// NOTE: The cipherText buffer must be at least as large as the plaintext buffer. The + /// size parameter must be the size of the number of bytes to encrypt. + /// </para> + /// </summary> + /// <param name="secretKey">A pointer to the receiver's secret key</param> + /// <param name="publicKey">A pointer to senders the public key</param> + /// <param name="nonce32">A pointer to the 32byte nonce used for message encryption</param> + /// <param name="plainText">A pointer to the plaintext buffer to encrypt</param> + /// <param name="cipherText">A pointer to the cyphertext buffer to write encrypted data to (must be as large or larger than the plaintext buffer)</param> + /// <param name="size">The size of the data to encrypt</param> + /// <param name="hmacKeyOut32"></param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + void EncryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte plainText, + ref byte cipherText, + uint size, + ref byte hmacKeyOut32 + ); + + /// <summary> + /// Decrypts a message using the supplied secret key, public key, and the original message + /// nonce. + /// </summary> + /// <param name="secretKey">A pointer to the receiver's secret key</param> + /// <param name="publicKey">A pointer to senders the public key</param> + /// <param name="nonce32">A pointer to the 32byte nonce used for message encryption</param> + /// <param name="plainText">A pointer to the plaintext buffer to write plaintext data to (must be as large or larger than the ciphertext buffer)</param> + /// <param name="cipherText">A pointer to the cyphertext buffer to read encrypted data from</param> + /// <param name="size">The size of the buffer to decrypt</param> + void DecryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte cipherText, + ref byte plainText, + uint size + ); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IRandomSource.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IRandomSource.cs new file mode 100644 index 0000000..5c5f2ac --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IRandomSource.cs @@ -0,0 +1,32 @@ +// 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 VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a generator for random data, that fills abinary buffer with random bytes + /// on demand. + /// </summary> + public interface IRandomSource + { + /// <summary> + /// Fills the given buffer with random bytes + /// </summary> + /// <param name="buffer">Binary buffer to fill with random data</param> + void GetRandomBytes(Span<byte> buffer); + } +}
\ No newline at end of file diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs new file mode 100644 index 0000000..e2b3ebe --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/LibNoscrypt.cs @@ -0,0 +1,202 @@ +// 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; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using VNLib.Utils; +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; +using VNLib.Utils.Native; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// Initializes the native library and provides access to the native functions + /// </summary> + /// <param name="Library">An existing noscrypt library handle</param> + /// <param name="OwnsHandle">A value that indicates if the instance owns the library handle</param> + 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 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); + + /// <summary> + /// Gets a reference to the loaded function table for + /// the native library + /// </summary> + internal ref readonly FunctionTable Functions + { + get + { + Check(); + Library.ThrowIfClosed(); + return ref _functions; + } + } + + /// <summary> + /// Gets a value that determines if the library has been released + /// </summary> + internal bool IsClosed => Library.IsClosed || Library.IsInvalid; + + /// <summary> + /// 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. + /// </summary> + /// <param name="heap"></param> + /// <param name="entropy32">Initialization entropy buffer</param> + /// <param name="size">The size of the buffer (must be 32 bytes)</param> + /// <returns>The inialized context</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + 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<FunctionTable.NCInitContextDelegate>(result, true); + + Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}"); + + return new NCContext(ctx, heap, this); + } + catch + { + heap.Free(ref ctx); + throw; + } + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="heap"></param> + /// <param name="enropy32">The 32byte random seed/nonce for the noscrypt context</param> + /// <returns>The inialized context</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public NCContext Initialize(IUnmangedHeap heap, ReadOnlySpan<byte> enropy32) + { + return Initialize( + heap, + ref MemoryMarshal.GetReference(enropy32), + enropy32.Length + ); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="library"></param> + /// <param name="heap">The heap to allocate the context from</param> + /// <param name="entropy32">The random entropy data to initialize the context with</param> + /// <returns>The library wrapper handle</returns> + public NostrCrypto InitializeCrypto(IUnmangedHeap heap, ReadOnlySpan<byte> entropy32) + { + ArgumentNullException.ThrowIfNull(heap); + + //Create the crypto interface from the new context object + return new NostrCrypto( + context: Initialize(heap, entropy32), + ownsContext: true + ); + } + + ///<inheritdoc/> + protected override void Free() + { + if (OwnsHandle) + { + Library.Dispose(); + Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}"); + } + } + + /// <summary> + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// </summary> + /// <param name="path">The native library path or name to load</param> + /// <param name="search">The search path options</param> + /// <returns>The loaded library instance</returns> + 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); + } + + /// <summary> + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// </summary> + /// <param name="path">The native library path or name to load</param> + /// <returns>The loaded library instance</returns> + 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 new file mode 100644 index 0000000..dbd9372 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs @@ -0,0 +1,88 @@ +// 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; +using System.Diagnostics; + +using Microsoft.Win32.SafeHandles; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a context for the native library + /// </summary> + /// <param name="Heap">The heap the handle was allocated from</param> + /// <param name="Library">A reference to the native library</param> + public sealed class NCContext : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly IUnmangedHeap Heap; + + /// <summary> + /// The library this context was created from + /// </summary> + public LibNoscrypt Library { get; } + + internal NCContext(IntPtr handle, IUnmangedHeap heap, LibNoscrypt library) :base(true) + { + ArgumentNullException.ThrowIfNull(heap); + ArgumentNullException.ThrowIfNull(library); + + Heap = heap; + Library = library; + + //Store the handle + SetHandle(handle); + } + + /// <summary> + /// Reinitializes the context with the specified entropy + /// </summary> + /// <param name="entropy">The randomness buffer used to randomize the context</param> + /// <param name="size">The random data buffer size (must be 32 bytes)</param> + public unsafe void Reinitalize(ref byte entropy, int size) + { + //Entropy must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(size, CTX_ENTROPY_SIZE); + + this.ThrowIfClosed(); + fixed (byte* p = &entropy) + { + NCResult result = Library.Functions.NCReInitContext.Invoke(handle, p); + NCUtil.CheckResult<FunctionTable.NCReInitContextDelegate>(result, true); + } + } + + ///<inheritdoc/> + protected override bool ReleaseHandle() + { + if (!Library.IsClosed) + { + //destroy the context + Library.Functions.NCDestroyContext.Invoke(handle); + Trace.WriteLine($"Destroyed noscrypt context 0x{handle:x}"); + } + + //Free the handle + return Heap.Free(ref handle); + } + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs new file mode 100644 index 0000000..69234fe --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs @@ -0,0 +1,37 @@ +// 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 static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The NIP04 encryption version used by the Nostr protocol + /// </summary> + public sealed class NCNip04EncryptionVersion : IEncryptionVersion + { + /// <summary> + /// A static nip04 encryption version instance + /// </summary> + public static readonly NCNip04EncryptionVersion Instance = new(); + + ///<inheritdoc/> + uint IEncryptionVersion.Version => NC_ENC_VERSION_NIP04; + + ///<inheritdoc/> + int IEncryptionVersion.CalcBufferSize(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 new file mode 100644 index 0000000..e572dd7 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs @@ -0,0 +1,37 @@ +// 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 static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The NIP44 encryption version used by the Nostr protocol + /// </summary> + public sealed class NCNip44EncryptionVersion : IEncryptionVersion + { + /// <summary> + /// A static nip44 encryption version instance + /// </summary> + public static readonly NCNip44EncryptionVersion Instance = new(); + + ///<inheritdoc/> + uint IEncryptionVersion.Version => NC_ENC_VERSION_NIP44; + + ///<inheritdoc/> + int IEncryptionVersion.CalcBufferSize(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 new file mode 100644 index 0000000..0699d7d --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.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 <https://www.gnu.org/licenses/>. + +using System; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a user's secp256k1 public key for use with the Nostrcrypt library + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_PUBKEY_SIZE)] + public unsafe struct NCPublicKey + { + 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 new file mode 100644 index 0000000..b586d26 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.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 <https://www.gnu.org/licenses/>. + +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents an nostr variant of a secp265k1 secret key that matches + /// the size of the native library + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_KEY_SIZE)] + public unsafe struct NCSecretKey + { + 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 new file mode 100644 index 0000000..e212125 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs @@ -0,0 +1,194 @@ +// 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; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + public static class NCUtil + { + /// <summary> + /// Gets a span of bytes from the current secret key + /// structure + /// </summary> + /// <param name="key"></param> + /// <returns>The secret key data span</returns> + public unsafe static Span<byte> AsSpan(this ref NCSecretKey key) + { + //Safe to cast secret key to bytes, then we can make a span to its memory + ref byte asBytes = ref Unsafe.As<NCSecretKey, byte>(ref key); + return MemoryMarshal.CreateSpan(ref asBytes, sizeof(NCSecretKey)); + } + + /// <summary> + /// Gets a span of bytes from the current public key + /// structure + /// </summary> + /// <param name="key"></param> + /// <returns>The public key data as a data span</returns> + public unsafe static Span<byte> AsSpan(this ref NCPublicKey key) + { + //Safe to cast secret key to bytes, then we can make a span to its memory + ref byte asBytes = ref Unsafe.As<NCPublicKey, byte>(ref key); + return MemoryMarshal.CreateSpan(ref asBytes, sizeof(NCPublicKey)); + } + + /// <summary> + /// Casts a span of bytes to a secret key reference. Note that + /// the new structure reference will point to the same memory + /// as the span. + /// </summary> + /// <param name="span">The secret key data</param> + /// <returns>A mutable secret key reference</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref NCSecretKey AsSecretKey(Span<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCSecretKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCSecretKey>(ref asBytes); + } + + /// <summary> + /// Casts a span of bytes to a public key reference. Note that + /// the new structure reference will point to the same memory + /// as the span. + /// </summary> + /// <param name="span">The public key data span</param> + /// <returns>A mutable reference to the public key structure</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref NCPublicKey AsPublicKey(Span<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCPublicKey>(ref asBytes); + } + + /// <summary> + /// Casts a read-only span of bytes to a secret key reference. Note that + /// the new structure reference will point to the same memory as the span. + /// </summary> + /// <param name="span">The secret key data span</param> + /// <returns>A readonly refernce to the secret key structure</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref readonly NCSecretKey AsSecretKey(ReadOnlySpan<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCSecretKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCSecretKey>(ref asBytes); + } + + /// <summary> + /// Casts a read-only span of bytes to a public key reference. Note that + /// the new structure reference will point to the same memory as the span. + /// </summary> + /// <param name="span">The public key data span</param> + /// <returns>A readonly reference to the public key structure</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref readonly NCPublicKey AsPublicKey(ReadOnlySpan<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCPublicKey>(ref asBytes); + } + + internal static void CheckResult<T>(NCResult result, bool raiseOnFailure) where T : Delegate + { + //Only negative values are errors + if (result >= NC_SUCCESS) + { + return; + } + + NCResult asPositive = -result; + + // Error code are only 8 bits, if an argument error occured, the + // argument number will be in the next upper 8 bits + byte errorCode = (byte)(asPositive & 0xFF); + byte argNumber = (byte)((asPositive >> 8) & 0xFF); + + switch (errorCode) + { + case E_NULL_PTR: + RaiseNullArgExceptionForArgumentNumber<T>(argNumber); + break; + case E_INVALID_ARG: + RaiseArgExceptionForArgumentNumber<T>(argNumber); + break; + case E_ARGUMENT_OUT_OF_RANGE: + RaiseOORExceptionForArgumentNumber<T>(argNumber); + break; + case E_INVALID_CTX: + throw new InvalidOperationException("The library context object is null or invalid"); + case E_OPERATION_FAILED: + RaiseOperationFailedException(raiseOnFailure); + break; + case E_VERSION_NOT_SUPPORTED: + throw new NotSupportedException("The requested version is not supported"); + + default: + if(raiseOnFailure) + { + throw new InvalidOperationException($"The operation failed for an unknown reason, code: {errorCode:x}"); + } + break; + + } + } + + private static void RaiseOperationFailedException(bool raise) + { + if (raise) + { + throw new InvalidOperationException("The operation failed for an unknown reason"); + } + } + + private static void RaiseNullArgExceptionForArgumentNumber<T>(int argNumber) where T : Delegate + { + //Get delegate parameters + Type type = typeof(T); + ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; + throw new ArgumentNullException(arg.Name, "Argument is null or invalid cannot continue"); + } + + private static void RaiseArgExceptionForArgumentNumber<T>(int argNumber) where T : Delegate + { + //Get delegate parameters + Type type = typeof(T); + ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; + throw new ArgumentException("Argument is null or invalid cannot continue", arg.Name); + } + + private static void RaiseOORExceptionForArgumentNumber<T>(int argNumber) where T : Delegate + { + //Get delegate parameters + Type type = typeof(T); + ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; + throw new ArgumentOutOfRangeException(arg.Name, "Argument is out of range of acceptable values"); + } + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs new file mode 100644 index 0000000..82704db --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs @@ -0,0 +1,41 @@ +// 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; + +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public static class Nip04Util + { + public static bool IsValidPayload(ReadOnlySpan<char> payload) + { + /* Iv is base64 encoded so it should be 33% larger than 16 byte iv */ + ReadOnlySpan<char> iv = payload.SliceAfterParam("?iv="); + return iv.Length > 20 && iv.Length <= 26; + } + + public static ReadOnlySpan<char> GetIV(ReadOnlySpan<char> payload) => payload.SliceAfterParam("?iv="); + + public static ReadOnlySpan<char> GetCipherText(ReadOnlySpan<char> payload) => payload.SliceBeforeParam("?iv="); + + public static int CalcBufferSize(int dataSize) + { + throw new NotImplementedException(); + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Message.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Message.cs new file mode 100644 index 0000000..b78530b --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.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 VNLib.Utils.Cryptography.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/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs new file mode 100644 index 0000000..23c9127 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs @@ -0,0 +1,144 @@ +// 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; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// Provides a set of utility methods for working with the Noscrypt library + /// </summary> + public static class Nip44Util + { + /// <summary> + /// Calculates the required NIP44 encryption buffer size for + /// the specified input data size + /// </summary> + /// <param name="dataSize">The size (in bytes) of the encoded data to encrypt</param> + /// <returns>The exact size of the padded buffer output</returns> + public static int CalcBufferSize(int dataSize) + { + /* + * Taken from https://github.com/nostr-protocol/nips/blob/master/44.md + * + * Not gonna lie, kinda dumb branches. I guess they want to save space + * with really tiny messages... Dunno, but whatever RTFM + */ + + //Min message size is 32 bytes + int minSize = Math.Max(dataSize, 32); + + //find the next power of 2 that will fit the min size + int nexPower = 1 << ((int)Math.Log2(minSize - 1)) + 1; + + int chunk = nexPower <= 256 ? 32 : nexPower / 8; + + return (chunk * ((int)Math.Floor((double)((minSize - 1) / chunk)) + 1)) + sizeof(ushort); + } + + /// <summary> + /// Calculates the final buffer size required to hold the encrypted data + /// </summary> + /// <param name="dataSize">The size (in bytes) of plaintext data to encrypt</param> + /// <returns>The number of bytes required to store the final nip44 message</returns> + public static int CalcFinalBufferSize(int dataSize) + { + /* version + nonce + payload + mac */ + return CalcBufferSize(dataSize) + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE + 1; + } + + /// <summary> + /// Formats the plaintext data into a buffer that can be properly encrypted. + /// The output buffer must be zeroed, or can be zeroed using the + /// <paramref name="zeroOutput"/> parameter. Use <see cref="CalcBufferSize(uint)"/> + /// to determine the required output buffer size. + /// </summary> + /// <param name="plaintextData">A buffer containing plaintext data to copy to the output</param> + /// <param name="output">The output data buffer to format</param> + /// <param name="zeroOutput">A value that indicates if the buffer should be zeroed before use</param> + public static void FormatBuffer(ReadOnlySpan<byte> plaintextData, Span<byte> output, bool zeroOutput) + { + //First zero out the buffer + if (zeroOutput) + { + MemoryUtil.InitializeBlock(output); + } + + //Make sure the output buffer is large enough so we dont overrun it + ArgumentOutOfRangeException.ThrowIfLessThan(output.Length, plaintextData.Length + sizeof(ushort), nameof(output)); + + //Write the data size to the first 2 bytes + ushort dataSize = (ushort)plaintextData.Length; + BinaryPrimitives.WriteUInt16BigEndian(output, dataSize); + + //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 + ); + + //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); + } + + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs new file mode 100644 index 0000000..aaabc08 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs @@ -0,0 +1,237 @@ +// 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; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + public static class NoscryptExtensions + { + public static void EncryptNip44( + 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)); + + //Nonce must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(nonce32.Length, NC_ENCRYPTION_NONCE_SIZE, nameof(nonce32)); + + ArgumentOutOfRangeException.ThrowIfNotEqual(hmackKeyOut32.Length, NC_HMAC_KEY_SIZE, nameof(hmackKeyOut32)); + + //Encrypt data, use the plaintext buffer size as the data size + lib.EncryptNip44( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in MemoryMarshal.GetReference(nonce32), + plainText: in MemoryMarshal.GetReference(plainText), + cipherText: ref MemoryMarshal.GetReference(cipherText), + size: (uint)plainText.Length, + hmacKeyOut32: ref MemoryMarshal.GetReference(hmackKeyOut32) + ); + } + + public static unsafe void EncryptNip44( + this INostrCrypto lib, + ref NCSecretKey secretKey, + ref NCPublicKey publicKey, + void* nonce32, + void* hmacKeyOut32, + void* plainText, + void* cipherText, + uint size + ) + { + ArgumentNullException.ThrowIfNull(plainText); + ArgumentNullException.ThrowIfNull(cipherText); + ArgumentNullException.ThrowIfNull(nonce32); + + //Spans are easer to forward references from pointers without screwing up arguments + EncryptNip44( + lib: lib, + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: new ReadOnlySpan<byte>(nonce32, NC_ENCRYPTION_NONCE_SIZE), + plainText: new ReadOnlySpan<byte>(plainText, (int)size), + hmackKeyOut32: new Span<byte>(hmacKeyOut32, NC_HMAC_KEY_SIZE), + cipherText: new Span<byte>(cipherText, (int)size) + ); + } + + + public static void DecryptNip44( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> cipherText, + Span<byte> plainText + ) + { + ArgumentNullException.ThrowIfNull(lib); + + //Chacha requires the output buffer to be at-least the size of the input buffer + ArgumentOutOfRangeException.ThrowIfGreaterThan(cipherText.Length, plainText.Length, nameof(cipherText)); + + //Nonce must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(nonce32.Length, 32, nameof(nonce32)); + + //Decrypt data, use the ciphertext buffer size as the data size + lib.DecryptNip44( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in MemoryMarshal.GetReference(nonce32), + cipherText: in MemoryMarshal.GetReference(cipherText), + plainText: ref MemoryMarshal.GetReference(plainText), + size: (uint)cipherText.Length + ); + } + + public static unsafe void DecryptNip44( + 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); + + //Spans are easer to forward references from pointers without screwing up arguments + DecryptNip44( + lib: lib, + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: new Span<byte>(nonce32, NC_ENCRYPTION_NONCE_SIZE), + cipherText: new Span<byte>(cipherText, (int)size), + plainText: new Span<byte>(plainText, (int)size) + ); + } + + public static bool VerifyMac( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> mac32, + ReadOnlySpan<byte> payload + ) + { + ArgumentNullException.ThrowIfNull(lib); + ArgumentOutOfRangeException.ThrowIfZero(payload.Length, nameof(payload)); + ArgumentOutOfRangeException.ThrowIfNotEqual(nonce32.Length, NC_ENCRYPTION_NONCE_SIZE, nameof(nonce32)); + ArgumentOutOfRangeException.ThrowIfNotEqual(mac32.Length, NC_ENCRYPTION_MAC_SIZE, nameof(mac32)); + + //Verify the HMAC + return lib.VerifyMac( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in MemoryMarshal.GetReference(nonce32), + mac32: in MemoryMarshal.GetReference(mac32), + payload: in MemoryMarshal.GetReference(payload), + payloadSize: (uint)payload.Length + ); + } + + public static unsafe bool VerifyMac( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + void* nonce32, + void* mac32, + void* payload, + uint payloadSize + ) + { + ArgumentNullException.ThrowIfNull(nonce32); + ArgumentNullException.ThrowIfNull(mac32); + ArgumentNullException.ThrowIfNull(payload); + + //Spans are easer to forward references from pointers without screwing up arguments + return VerifyMac( + lib: lib, + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: new Span<byte>(nonce32, NC_ENCRYPTION_NONCE_SIZE), + mac32: new Span<byte>(mac32, NC_ENCRYPTION_MAC_SIZE), + payload: new Span<byte>(payload, (int)payloadSize) + ); + } + + public static void SignData( + this INostrCrypto lib, + ref readonly NCSecretKey secKey, + ReadOnlySpan<byte> random32, + ReadOnlySpan<byte> data, + Span<byte> signatureBuffer + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signatureBuffer.Length, NC_SIGNATURE_SIZE, nameof(signatureBuffer)); + ArgumentOutOfRangeException.ThrowIfLessThan(random32.Length, 32, nameof(random32)); + ArgumentOutOfRangeException.ThrowIfZero(data.Length, nameof(data)); + + lib.SignData( + secretKey: in secKey, + random32: in MemoryMarshal.GetReference(random32), + data: in MemoryMarshal.GetReference(data), + dataSize: (uint)data.Length, + sig64: ref MemoryMarshal.GetReference(signatureBuffer) + ); + } + +#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( + secretKey: in secretKey, + publicKey: in publicKey, + key32: ref MemoryMarshal.GetReference(conversationKeyOut32) + ); + + } +#endif + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs new file mode 100644 index 0000000..c4bef05 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs @@ -0,0 +1,287 @@ +// 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; +using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; + +using VNLib.Utils.Cryptography.Noscrypt.@internal; +using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// A default implementation of the <see cref="INostrCrypto"/> interface + /// </summary> + /// <param name="context">The initialized library context</param> + public unsafe class NostrCrypto(NCContext context, bool ownsContext) : VnDisposeable, INostrCrypto + { + /// <summary> + /// Gets the underlying library context. + /// </summary> + public NCContext Context => context; + + private ref readonly FunctionTable Functions => ref context.Library.Functions; + + ///<inheritdoc/> + public void DecryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte cipherText, + ref byte plainText, + uint size + ) + { + Check(); + + ThrowIfNullRef(in nonce32, nameof(nonce32)); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pNonce = &nonce32) + { + NCEncryptionArgs data = new() + { + //Set input data to the cipher text to decrypt and the output data to the plaintext buffer + dataSize = size, + hmacKeyOut32 = null, + inputData = pCipherText, + outputData = pTextPtr, + nonce32 = pNonce, + version = NC_ENC_VERSION_NIP44 + }; + + NCResult result = Functions.NCDecrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); + NCUtil.CheckResult<FunctionTable.NCDecryptDelegate>(result, true); + } + } + + ///<inheritdoc/> + public void EncryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte plainText, + ref byte cipherText, + uint size, + ref byte hmackKeyOut32 + ) + { + Check(); + + ThrowIfNullRef(in nonce32, nameof(nonce32)); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pHmacKeyOut = &hmackKeyOut32, pNonce = &nonce32) + { + NCEncryptionArgs data = new() + { + nonce32 = pNonce, + hmacKeyOut32 = pHmacKeyOut, + //Set input data to the plaintext to encrypt and the output data to the cipher text buffer + inputData = pTextPtr, + outputData = pCipherText, + dataSize = size, + version = NC_ENC_VERSION_NIP44 //Force nip44 encryption + }; + + NCResult result = Functions.NCEncrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); + NCUtil.CheckResult<FunctionTable.NCEncryptDelegate>(result, true); + } + } + + ///<inheritdoc/> + public void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey) + { + Check(); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + { + NCResult result = Functions.NCGetPublicKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey); + NCUtil.CheckResult<FunctionTable.NCGetPublicKeyDelegate>(result, true); + } + } + + ///<inheritdoc/> + public void SignData( + ref readonly NCSecretKey secretKey, + ref readonly byte random32, + ref readonly byte data, + uint dataSize, + ref byte sig64 + ) + { + Check(); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (byte* pData = &data, pSig = &sig64, pRandom = &random32) + { + NCResult result = Functions.NCSignData.Invoke( + ctx: context.DangerousGetHandle(), + sk: pSecKey, + random32: pRandom, + data: pData, + dataSize, + sig64: pSig + ); + + NCUtil.CheckResult<FunctionTable.NCSignDataDelegate>(result, true); + } + } + + ///<inheritdoc/> + public bool ValidateSecretKey(ref readonly NCSecretKey secretKey) + { + Check(); + + IntPtr libCtx = context.DangerousGetHandle(); + + fixed (NCSecretKey* pSecKey = &secretKey) + { + /* + * Validate should return a result of 1 if the secret key is valid + * or a 0 if it is not. + */ + NCResult result = Functions.NCValidateSecretKey.Invoke(libCtx, pSecKey); + NCUtil.CheckResult<FunctionTable.NCValidateSecretKeyDelegate>(result, false); + + return result == NC_SUCCESS; + } + } + + ///<inheritdoc/> + public bool VerifyData( + ref readonly NCPublicKey pubKey, + ref readonly byte data, + uint dataSize, + ref readonly byte sig64 + ) + { + Check(); + + fixed(NCPublicKey* pPubKey = &pubKey) + fixed (byte* pData = &data, pSig = &sig64) + { + NCResult result = Functions.NCVerifyData.Invoke(context.DangerousGetHandle(), pPubKey, pData, dataSize, pSig); + NCUtil.CheckResult<FunctionTable.NCVerifyDataDelegate>(result, false); + + return result == NC_SUCCESS; + } + } + + ///<inheritdoc/> + public bool VerifyMac( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte mac32, + ref readonly byte payload, + uint payloadSize + ) + { + Check(); + + //Check pointers we need to use + ThrowIfNullRef(in nonce32, nameof(nonce32)); + ThrowIfNullRef(in mac32, nameof(mac32)); + ThrowIfNullRef(in payload, nameof(payload)); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pPayload = &payload, pMac = &mac32, pNonce = &nonce32) + { + + NCMacVerifyArgs args = new() + { + payloadSize = payloadSize, + payload = pPayload, + mac32 = pMac, + nonce32 = pNonce + }; + + //Exec and bypass failure + 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 + return result == NC_SUCCESS; + } + } + + public void ComputeMac(ref readonly byte hmacKey32, ref readonly byte payload, uint payloadSize, ref byte hmacOut32) + { + Check(); + + //Library will check for null pointers, since they are all arguments + fixed (byte* pKey = &hmacKey32, pPayload = &payload, pOut = &hmacOut32) + { + NCResult result = Functions.NCComputeMac.Invoke(context.DangerousGetHandle(), pKey, pPayload, payloadSize, pOut); + NCUtil.CheckResult<FunctionTable.NCComputeMacDelegate>(result, true); + } + } + +#if DEBUG + + /// <summary> + /// DEBUG ONLY: Gets the conversation key for the supplied secret key and public key + /// </summary> + /// <param name="secretKey">The sender's private key</param> + /// <param name="publicKey">The receiver's public key</param> + /// <param name="key32">A pointer to the 32byte buffer to write the conversation key to</param> + 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); + } + } + +#endif + + ///<inheritdoc/> + protected override void Free() + { + if(ownsContext) + { + 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/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs new file mode 100644 index 0000000..c70839c --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrEncryptedMessage.cs @@ -0,0 +1,234 @@ +// 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; +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; + + /// <summary> + /// The message encryption version used by this instance + /// </summary> + public uint Version { get; } = version.Version; + + /// <summary> + /// The message nonce created during encryption event + /// </summary> + public unsafe Span<byte> Nonce + { + get + { + Debug.Assert(NC_ENCRYPTION_NONCE_SIZE == sizeof(Buffer32)); + return MemoryMarshal.CreateSpan(ref GetNonceRef(), sizeof(Buffer32)); + } + } + + /// <summary> + /// Gets the size of the buffer required to encrypt the specified data size + /// </summary> + /// <param name="dataSize">The size of the message raw plaintext message to send</param> + /// <returns>The minimum number of bytes required for message encryption output</returns> + /// <exception cref="NotSupportedException"></exception> + public int GetOutputBufferSize(int dataSize) + => version.CalcBufferSize(dataSize); + + /// <summary> + /// Sets the encryption secret key for the message + /// </summary> + /// <param name="secKey">The secret key buffer</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrEncryptedMessage SetSecretKey(ReadOnlySpan<byte> secKey) + => SetSecretKey(in NCUtil.AsSecretKey(secKey)); + + /// <summary> + /// Sets the encryption secret key for the message + /// </summary> + /// <param name="secKey">The secret key structure to copy</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrEncryptedMessage SetSecretKey(ref readonly NCSecretKey secKey) + { + MemoryUtil.CloneStruct(in secKey, ref _fromKey); + return this; + } + + /// <summary> + /// Assigns the public key used to encrypt the message as the + /// receiver of the message + /// </summary> + /// <param name="pubKey">The user's public key receiving the message</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrEncryptedMessage SetPublicKey(ReadOnlySpan<byte> pubKey) + => SetPublicKey(in NCUtil.AsPublicKey(pubKey)); + + /// <summary> + /// Assigns the public key used to encrypt the message as the + /// receiver of the message + /// </summary> + /// <param name="pubKey">The user's public key receiving the message</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrEncryptedMessage SetPublicKey(ref readonly NCPublicKey pubKey) + { + MemoryUtil.CloneStruct(in pubKey, ref _toKey); + return this; + } + + /// <summary> + /// Assigns the nonce to the message. Must be <see cref="NC_ENCRYPTION_NONCE_SIZE"/> + /// in length + /// </summary> + /// <param name="nonce">The nonce value to copy</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrEncryptedMessage SetNonce(ReadOnlySpan<byte> nonce) + { + MemoryUtil.CopyStruct(nonce, ref _nonce32); + return this; + } + + /// <summary> + /// Assigns a random nonce using the specified random source + /// </summary> + /// <param name="rng">The random source to genrate a random nonce from</param> + /// <returns>The current instance for chaining</returns> + public NostrEncryptedMessage SetRandomNonce(IRandomSource rng) + { + rng.GetRandomBytes(Nonce); + return this; + } + + /// <summary> + /// Encrypts the plaintext message and writes the encrypted message to the + /// specified buffer, along with a 32 byte mac of the message + /// </summary> + /// <param name="plaintext">The plaintext data to encrypt</param> + /// <param name="message">The message output buffer to write encrypted data to</param> + /// <param name="macOut32">A buffer to write the computed message mac to</param> + /// <returns>The number of bytes writtn to the message output buffer</returns> + /// <remarks> + /// The message buffer must be at-least the size of the output buffer, and it is not + /// initialized before the encryption operation. + /// </remarks> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public int EncryptMessage(ReadOnlySpan<byte> plaintext, Span<byte> message, Span<byte> macOut32) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44(plaintext, message, macOut32), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44(ReadOnlySpan<byte> plaintext, Span<byte> message, Span<byte> 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<byte> ptPayloadBuf = MemoryUtil.UnsafeAllocNearestPage<byte>(payloadSize, true); + using UnsafeMemoryHandle<byte> hmacKeyBuf = MemoryUtil.UnsafeAlloc<byte>(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<Buffer32, byte>(ref _nonce32); + + protected override void Free() + { + //Zero all internal memory + MemoryUtil.ZeroStruct(ref _fromKey); + MemoryUtil.ZeroStruct(ref _toKey); + MemoryUtil.ZeroStruct(ref _nonce32); + } + + /// <summary> + /// Initializes a new <see cref="NostrEncryptedMessage"/> with the nip44 encryption + /// method. + /// </summary> + /// <param name="lib">The nostr crypto implementation instance to use</param> + /// <returns>The intialzied message instance</returns> + 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/Taskfile.yaml b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Taskfile.yaml new file mode 100644 index 0000000..0b441a3 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Taskfile.yaml @@ -0,0 +1,70 @@ +# https://taskfile.dev + +#Called by the vnbuild system to produce builds for my website +#https://www.vaughnnugent.com/resources/software + +#This taskfile is called from the root of a project that is being built +#and the purpose of this taskfile is to package up the output of a build +#from the solution file, and package it up into a tgz files for distribution + +version: '3' + +vars: + TARGET: '{{.USER_WORKING_DIR}}/bin' + RELEASE_DIR: "./bin/release/{{.TARGET_FRAMEWORK}}/publish" + +tasks: + + #when build succeeds, archive the output into a tgz + postbuild_success: + dir: '{{.USER_WORKING_DIR}}' + cmds: + #pack up source code + - task: packsource + + #run post in debug mode + - task: postbuild + vars: { BUILD_MODE: debug } + + #remove uncessary files from the release dir + - powershell -Command "Get-ChildItem -Recurse '{{.RELEASE_DIR}}/' -Include *.pdb,*.xml | Remove-Item" + + #run post in release mode + - task: postbuild + vars: { BUILD_MODE: release } + + + postbuild_failed: + dir: '{{.USER_WORKING_DIR}}' + cmds: [] + + postbuild: + dir: '{{.USER_WORKING_DIR}}' + internal: true + vars: + #the build output directory + BUILD_OUT: "{{.USER_WORKING_DIR}}/bin/{{.BUILD_MODE}}/{{.TARGET_FRAMEWORK}}/publish" + + cmds: + #copy license and readme to target + - cd .. && powershell -Command "Copy-Item -Path ./build.readme.md -Destination '{{.BUILD_OUT}}/readme.md'" + + #tar outputs + - cd "{{.BUILD_OUT}}" && tar -czf "{{.TARGET}}/{{.BUILD_MODE}}.tgz" . + + packsource: + dir: '{{.USER_WORKING_DIR}}' + internal: true + cmds: + #copy source code to target + - powershell -Command "Get-ChildItem -Include *.cs,*.csproj -Recurse | Where { \$_.FullName -notlike '*\obj\*' -and \$_.FullName -notlike '*\bin\*' } | Resolve-Path -Relative | tar --files-from - -czf '{{.TARGET}}/src.tgz'" + + +#Remove the output dirs on clean + clean: + dir: '{{.USER_WORKING_DIR}}' + ignore_error: true + cmds: + - for: ['bin/', 'obj/'] + cmd: powershell Remove-Item -Recurse '{{.ITEM}}' + diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/UnmanagedRandomSource.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/UnmanagedRandomSource.cs new file mode 100644 index 0000000..91ff64b --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/UnmanagedRandomSource.cs @@ -0,0 +1,104 @@ +// 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; +using System.Runtime.InteropServices; + +using VNLib.Utils; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// A wrapper class for an unmanaged random source that conforms to the <see cref="IRandomSource"/> interface + /// </summary> + public class UnmanagedRandomSource : VnDisposeable, IRandomSource + { + /// <summary> + /// The explicit name of the unmanaged random function to get random bytes + /// </summary> + public const string RngFunctionName = "GetRandomBytes"; + + unsafe delegate void UnmanagedRandomSourceDelegate(byte* buffer, int size); + + private readonly bool OwnsHandle; + private readonly SafeLibraryHandle _library; + private readonly UnmanagedRandomSourceDelegate _getRandomBytes; + + /// <summary> + /// Loads the unmanaged random source from the given library + /// and attempts to get the random bytes method <see cref="RngFunctionName"/> + /// </summary> + /// <param name="path"></param> + /// <param name="search"></param> + /// <returns>The wrapped library that conforms to the <see cref="IRandomSource"/></returns> + public static UnmanagedRandomSource LoadLibrary(string path, DllImportSearchPath search) + { + //Try to load the library + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(path, search); + try + { + return new UnmanagedRandomSource(lib, true); + } + catch + { + //release lib + lib.Dispose(); + throw; + } + } + + /// <summary> + /// Creates the unmanaged random source from the given library + /// </summary> + /// <param name="lib">The library handle to wrap</param> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="EntryPointNotFoundException"></exception> + public UnmanagedRandomSource(SafeLibraryHandle lib, bool ownsHandle) + { + lib.ThrowIfClosed(); + + _library = lib; + + //get the method delegate + _getRandomBytes = lib.DangerousGetFunction<UnmanagedRandomSourceDelegate>(RngFunctionName); + + OwnsHandle = ownsHandle; + } + + ///<inheritdoc/> + public unsafe void GetRandomBytes(Span<byte> buffer) + { + _library.ThrowIfClosed(); + + //Fix buffer and call unmanaged method + fixed (byte* ptr = &MemoryMarshal.GetReference(buffer)) + { + _getRandomBytes(ptr, buffer.Length); + } + } + + ///<inheritdoc/> + protected override void Free() + { + if (OwnsHandle) + { + _library.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/VNLib.Utils.Cryptography.Noscrypt.csproj b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/VNLib.Utils.Cryptography.Noscrypt.csproj new file mode 100644 index 0000000..7e7f5de --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/VNLib.Utils.Cryptography.Noscrypt.csproj @@ -0,0 +1,28 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>false</ImplicitUsings> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <PackageReadmeFile>README.md</PackageReadmeFile> + <RootNamespace>VNLib.Utils.Cryptography.Noscrypt</RootNamespace> + <AssemblyName>VNLib.Utils.Cryptography.Noscrypt</AssemblyName> + </PropertyGroup> + + <PropertyGroup> + <Authors>Vaughn Nugent</Authors> + <Company>Vaughn Nugent</Company> + <Product>VNLib.Utils.Cryptography.Noscrypt</Product> + <Description>Provides a managed library wrapper for the noscrypt native library</Description> + <Copyright>Copyright © 2024 Vaughn Nugent</Copyright> + <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Noscrypt</PackageProjectUrl> + <RepositoryUrl>https://github.com/VnUgE/noscryot/tree/master/dotnet/VNLib.Utils.Cryptography.Noscrypt</RepositoryUrl> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0122" /> + <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0122" /> + </ItemGroup> + +</Project> diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs new file mode 100644 index 0000000..aa916eb --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs @@ -0,0 +1,127 @@ +// 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; + +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt.@internal +{ + + internal unsafe readonly struct FunctionTable + { + + public readonly NCGetContextStructSizeDelegate NCGetContextStructSize; + public readonly NCInitContextDelegate NCInitContext; + public readonly NCReInitContextDelegate NCReInitContext; + public readonly NCDestroyContextDelegate NCDestroyContext; + public readonly NCGetPublicKeyDelegate NCGetPublicKey; + public readonly NCValidateSecretKeyDelegate NCValidateSecretKey; + public readonly NCSignDataDelegate NCSignData; + public readonly NCVerifyDataDelegate NCVerifyData; + public readonly NCEncryptDelegate NCEncrypt; + public readonly NCDecryptDelegate NCDecrypt; + public readonly NCVerifyMacDelegate NCVerifyMac; + public readonly NCComputeMacDelegate NCComputeMac; + +#if DEBUG + public readonly NCGetConversationKeyDelegate NCGetConversationKey; +#endif + + private FunctionTable(SafeLibraryHandle library) + { + //Load the required high-level api functions + NCGetContextStructSize = library.DangerousGetFunction<NCGetContextStructSizeDelegate>(); + NCInitContext = library.DangerousGetFunction<NCInitContextDelegate>(); + NCReInitContext = library.DangerousGetFunction<NCReInitContextDelegate>(); + NCDestroyContext = library.DangerousGetFunction<NCDestroyContextDelegate>(); + NCGetPublicKey = library.DangerousGetFunction<NCGetPublicKeyDelegate>(); + NCValidateSecretKey = library.DangerousGetFunction<NCValidateSecretKeyDelegate>(); + NCSignData = library.DangerousGetFunction<NCSignDataDelegate>(); + NCVerifyData = library.DangerousGetFunction<NCVerifyDataDelegate>(); + NCSignData = library.DangerousGetFunction<NCSignDataDelegate>(); + NCVerifyData = library.DangerousGetFunction<NCVerifyDataDelegate>(); + NCEncrypt = library.DangerousGetFunction<NCEncryptDelegate>(); + NCDecrypt = library.DangerousGetFunction<NCDecryptDelegate>(); + NCVerifyMac = library.DangerousGetFunction<NCVerifyMacDelegate>(); + NCComputeMac = library.DangerousGetFunction<NCComputeMacDelegate>(); + +#if DEBUG + NCGetConversationKey = library.DangerousGetFunction<NCGetConversationKeyDelegate>(); +#endif + } + + /// <summary> + /// Initialize a new function table from the specified library + /// </summary> + /// <param name="library"></param> + /// <returns>The function table structure</returns> + /// <exception cref="MissingMemberException"></exception> + /// <exception cref="EntryPointNotFoundException"></exception> + public static FunctionTable BuildFunctionTable(SafeLibraryHandle library) => new (library); + + /* + * ################################################ + * + * Functions match the noscrypt.h header file + * + * ################################################ + */ + + //FUCNTIONS + [SafeMethodName("NCGetContextStructSize")] + internal delegate uint NCGetContextStructSizeDelegate(); + + [SafeMethodName("NCInitContext")] + internal delegate NCResult NCInitContextDelegate(IntPtr ctx, byte* entropy32); + + [SafeMethodName("NCReInitContext")] + internal delegate NCResult NCReInitContextDelegate(IntPtr ctx, byte* entropy32); + + [SafeMethodName("NCDestroyContext")] + internal delegate NCResult NCDestroyContextDelegate(IntPtr ctx); + + [SafeMethodName("NCGetPublicKey")] + internal delegate NCResult NCGetPublicKeyDelegate(IntPtr ctx, NCSecretKey* secKey, NCPublicKey* publicKey); + + [SafeMethodName("NCValidateSecretKey")] + internal delegate NCResult NCValidateSecretKeyDelegate(IntPtr ctx, NCSecretKey* secKey); + + [SafeMethodName("NCSignData")] + internal delegate NCResult NCSignDataDelegate(IntPtr ctx, NCSecretKey* sk, byte* random32, byte* data, uint dataSize, byte* sig64); + + [SafeMethodName("NCVerifyData")] + internal delegate NCResult NCVerifyDataDelegate(IntPtr ctx, NCPublicKey* sk, byte* data, uint dataSize, byte* sig64); + + [SafeMethodName("NCEncrypt")] + internal delegate NCResult NCEncryptDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCEncryptionArgs* data); + + [SafeMethodName("NCDecrypt")] + internal delegate NCResult NCDecryptDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCEncryptionArgs* data); + + [SafeMethodName("NCVerifyMac")] + internal delegate NCResult NCVerifyMacDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCMacVerifyArgs* args); + + [SafeMethodName("NCComputeMac")] + internal delegate NCResult NCComputeMacDelegate(IntPtr ctx, byte* hmacKey32, byte* payload, uint payloadSize, byte* hmacOut32); + + [SafeMethodName("NCGetConversationKey")] + internal delegate NCResult NCGetConversationKeyDelegate(nint ctx, NCSecretKey* sk, NCPublicKey* pk, byte* keyOut32); + + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCEncryptionArgs.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCEncryptionArgs.cs new file mode 100644 index 0000000..a63d3b3 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCEncryptionArgs.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 <https://www.gnu.org/licenses/>. + +using System; +using System.Runtime.InteropServices; + +namespace VNLib.Utils.Cryptography.Noscrypt.@internal +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct NCEncryptionArgs + { + public byte* nonce32; + public byte* hmacKeyOut32; + public byte* inputData; + public byte* outputData; + public uint dataSize; + public uint version; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCMacVerifyArgs.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCMacVerifyArgs.cs new file mode 100644 index 0000000..8a9ba1f --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCMacVerifyArgs.cs @@ -0,0 +1,34 @@ +// 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 VNLib.Utils.Cryptography.Noscrypt.@internal +{ + internal unsafe struct NCMacVerifyArgs + { + /* The message authentication code certifying the Nip44 payload */ + public byte* mac32; + + /* The nonce used for the original message encryption */ + public byte* nonce32; + + /* The message payload data */ + public byte* payload; + + /* The size of the payload data */ + public uint payloadSize; + } +} |