diff options
Diffstat (limited to 'wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src')
26 files changed, 2775 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public sealed class Base64SignatureEncoder : INostrSignatureEncoder + { + /// <summary> + /// Shared formatter instance for base64 signatures + /// </summary> + public static Base64SignatureEncoder Instance { get; } = new Base64SignatureEncoder(); + + ///<inheritdoc/> + public string GetString(ReadOnlySpan<byte> 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 <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public sealed class HexSignatureEncoder : INostrSignatureEncoder + { + /// <summary> + /// Shared formatter instance for hex signatures + /// </summary> + public static HexSignatureEncoder Instance { get; } = new HexSignatureEncoder(); + + ///<inheritdoc/> + public string GetString(ReadOnlySpan<byte> signature) => Convert.ToHexString(signature); + } + +} 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/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 <https://www.gnu.org/licenses/>. + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a message encryption version used by the Nostr protocol + /// </summary> + public interface INostrEncryptionVersion + { + /// <summary> + /// The noscrypt compatible encryption version + /// </summary> + internal uint Version { get; } + + /// <summary> + /// Calculates the required payload 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 GetPayloadBufferSize(int dataSize); + + /// <summary> + /// Calculates the required message buffer size for the specified data size + /// </summary> + /// <param name="dataSize">Plain text data size</param> + /// <returns>The estimated size of the buffer required to complete the opeation</returns> + 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 <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Encodes a message signature into it's string representation + /// </summary> + public interface INostrSignatureEncoder + { + /// <summary> + /// Creates a string of the encoded signature data + /// </summary> + /// <param name="signature">The signature data to encode into the string</param> + /// <returns>The encoded signature string</returns> + string GetString(ReadOnlySpan<byte> signature); + } + +} 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/NCContext.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs new file mode 100644 index 0000000..8f8c6b4 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs @@ -0,0 +1,87 @@ +// 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 VNLib.Utils.Cryptography.Noscrypt.@internal; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The noscrypt library context + /// </summary> + public sealed class NCContext : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly IUnmangedHeap Heap; + + /// <summary> + /// The library this context was created from + /// </summary> + public NoscryptLibrary Library { get; } + + internal NCContext(IntPtr handle, IUnmangedHeap heap, NoscryptLibrary 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, NC_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..beb21c2 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs @@ -0,0 +1,40 @@ +// 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.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The NIP04 encryption version used by the Nostr protocol + /// </summary> + public sealed class NCNip04EncryptionVersion : INostrEncryptionVersion + { + /// <summary> + /// A static nip04 encryption version instance + /// </summary> + public static readonly NCNip04EncryptionVersion Instance = new(); + + ///<inheritdoc/> + uint INostrEncryptionVersion.Version => NC_ENC_VERSION_NIP04; + + ///<inheritdoc/> + int INostrEncryptionVersion.GetMessageBufferSize(int dataSize) => Nip04Util.CalcBufferSize(dataSize); + + ///<inheritdoc/> + 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 new file mode 100644 index 0000000..0d5907a --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs @@ -0,0 +1,39 @@ +// 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.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The NIP44 encryption version used by the Nostr protocol + /// </summary> + public sealed class NCNip44EncryptionVersion : INostrEncryptionVersion + { + /// <summary> + /// A static nip44 encryption version instance + /// </summary> + public static readonly NCNip44EncryptionVersion Instance = new(); + + ///<inheritdoc/> + uint INostrEncryptionVersion.Version => NC_ENC_VERSION_NIP44; + + int INostrEncryptionVersion.GetMessageBufferSize(int dataSize) => Nip44Util.CalcFinalBufferSize(dataSize); + + ///<inheritdoc/> + 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 new file mode 100644 index 0000000..57d7c3f --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.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 System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +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 + { + /// <summary> + /// Gets a null <see cref="NCPublicKey"/> reference. + /// </summary> + public static ref NCPublicKey NullRef => ref Unsafe.NullRef<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..18f025b --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.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 System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +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 + { + /// <summary> + /// Gets a null reference to a secret key + /// </summary> + public static ref NCSecretKey NullRef => ref Unsafe.NullRef<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..49c66c1 --- /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.NoscryptLibrary; + +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/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 <https://www.gnu.org/licenses/>. + +using System; + +using VNLib.Hashing; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// A fallback crypographic random source used for default + /// rng if you wish + /// </summary> + public sealed class NcFallbackRandom : IRandomSource + { + /// <summary> + /// Gets the shared instance of the fallback random source + /// </summary> + public static NcFallbackRandom Shared { get; } = new NcFallbackRandom(); + + /// <inheritdoc/> + public void GetRandomBytes(Span<byte> 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 new file mode 100644 index 0000000..c1906f0 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs @@ -0,0 +1,50 @@ +// 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.Text; + +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(); + } + + 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/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 <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public readonly ref struct Nip44MessageSegments(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..2aebee1 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs @@ -0,0 +1,190 @@ +// 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.NoscryptLibrary; + +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( + 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<byte> payloadBuffer, + byte version, + ReadOnlySpan<byte> mac, + Span<byte> 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<byte> GetNonceFromPayload(ReadOnlySpan<byte> message) + { + //The nonce is 32 bytes following the 1st byte version number of the message + return message.Slice(1, NC_ENCRYPTION_NONCE_SIZE); + } + + public static ReadOnlySpan<byte> GetCiphertextFromPayload(ReadOnlySpan<byte> message) + { + //Message is between the nonce and the trailing mac + int payloadSize = message.Length - (1 + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE); + return message.Slice(1 + NC_ENCRYPTION_NONCE_SIZE, payloadSize); + } + + public static ReadOnlySpan<byte> GetMacFromPayload(ReadOnlySpan<byte> message) + { + //The mac is the last 32 bytes of the message + return message[^NC_ENCRYPTION_MAC_SIZE..]; + } + + public static ReadOnlySpan<byte> GetNonceAndCiphertext(ReadOnlySpan<byte> message) + { + //The nonce is 32 bytes following the 1st byte version number of the message + return message.Slice(1, NC_ENCRYPTION_NONCE_SIZE + GetCiphertextFromPayload(message).Length); + } + + public static byte GetMessageVersion(ReadOnlySpan<byte> message) + { + //The first byte is the message version + return message[0]; + } + + public static ReadOnlySpan<byte> GetPlaintextMessage(ReadOnlySpan<byte> plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return plaintextPayload.Slice(sizeof(ushort), ptLength); + } + + public static bool IsValidPlaintextMessage(ReadOnlySpan<byte> plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return ptLength == plaintextPayload.Length - sizeof(ushort); + } + + public static Range GetPlaintextRange(ReadOnlySpan<byte> 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 new file mode 100644 index 0000000..ccae190 --- /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.CompilerServices; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +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 + lib.EncryptNip44( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in Unsafe.AsRef<byte>(nonce32), + plainText: in Unsafe.AsRef<byte>(plainText), + cipherText: ref Unsafe.AsRef<byte>(cipherText), + size: size, + hmacKeyOut32: ref Unsafe.AsRef<byte>(hmacKeyOut32) + ); + } + + + 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); + + return lib.VerifyMac( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in Unsafe.AsRef<byte>(nonce32), + mac32: in Unsafe.AsRef<byte>(mac32), + payload: in Unsafe.AsRef<byte>(payload), + payloadSize: 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/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 <https://www.gnu.org/licenses/>. + +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 +{ + + /// <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 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); + + /// <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, 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<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="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 + ); + } + + /// <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="heap">The heap to allocate the context from</param> + /// <param name="random">Random source used to generate context entropy</param> + /// <returns>The library wrapper handle</returns> + public NostrCrypto InitializeCrypto(IUnmangedHeap heap, IRandomSource random) + { + ArgumentNullException.ThrowIfNull(random); + + //Get random bytes for context entropy + Span<byte> entropy = stackalloc byte[NC_CTX_ENTROPY_SIZE]; + random.GetRandomBytes(entropy); + + NostrCrypto nc = InitializeCrypto(heap, entropy); + + MemoryUtil.InitializeBlock(entropy); + + return nc; + } + + ///<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> + /// <exception cref="DllNotFoundException"></exception> + 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); + } + + /// <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> + /// <exception cref="DllNotFoundException"></exception> + public static NoscryptLibrary Load(string path) => Load(path, DllImportSearchPath.SafeDirectories); + + /// <summary> + /// Attempts to load the default noscrypt library from the system search path + /// </summary> + /// <returns>The loaded library instance</returns> + /// <exception cref="DllNotFoundException"></exception> + 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 <https://www.gnu.org/licenses/>. + +using System; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// A simple wrapper class to sign nostr message data using + /// the noscrypt library + /// </summary> + /// <param name="noscrypt">The noscrypt library instance</param> + /// <param name="random">A random entropy pool used to source random data for signature entropy</param> + public class NoscryptSigner(INostrCrypto noscrypt, IRandomSource random) + { + /// <summary> + /// Gets the size of the buffer required to hold the signature + /// </summary> + public static int SignatureBufferSize => NC_SIGNATURE_SIZE; + + /// <summary> + /// Signs a message using the specified private key and message data + /// </summary> + /// <param name="hexPrivateKey">The hexadecimal private key used to sign the message</param> + /// <param name="message">The message data to sign</param> + /// <param name="format">A encoder used to convert the signature data to an encoded string</param> + /// <returns>The string encoded nostr signature</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public string SignData(string hexPrivateKey, ReadOnlySpan<byte> 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); + } + } + + /// <summary> + /// Signs a message using the specified secret key and message data + /// </summary> + /// <param name="secretKey">The secret key data buffer</param> + /// <param name="message">The message data to sign</param> + /// <param name="format">A encoder used to convert the signature data to an encoded string</param> + /// <returns>The string encoded nostr signature</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public string SignData( + ReadOnlySpan<byte> secretKey, + ReadOnlySpan<byte> message, + INostrSignatureEncoder? format = null + ) + { + return SignData(in NCUtil.AsSecretKey(secretKey), message, format); + } + + /// <summary> + /// Signs a message using the specified secret key and message data + /// </summary> + /// <param name="secretkey">A reference to the secret key structurer</param> + /// <param name="message">The message data to sign</param> + /// <param name="format">A encoder used to convert the signature data to an encoded string</param> + /// <returns>The string encoded nostr signature</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public string SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan<byte> message, + INostrSignatureEncoder? format = null + ) + { + //Default to hex encoding because that is the default NIP-01 format + format ??= HexSignatureEncoder.Instance; + + Span<byte> sigBuffer = stackalloc byte[SignatureBufferSize]; + + SignData(message, sigBuffer); + + return format.GetString(sigBuffer); + } + + + /// <summary> + /// Signs a message using the specified secret key and message data + /// </summary> + /// <param name="secretkey">A reference to the secret key structurer</param> + /// <param name="data">The message data to sign</param> + /// <param name="signature">A buffer to write signature data to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public void SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan<byte> data, + Span<byte> signature + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signature.Length, NC_SIGNATURE_SIZE, nameof(signature)); + + //Signature generation required random entropy to be secure + Span<byte> 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 new file mode 100644 index 0000000..36e2381 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs @@ -0,0 +1,322 @@ +// 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.NoscryptLibrary; + +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(); + + //Version set first otherwise errors will occur + SetEncProperty(&data, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44); + //Only the nonce must be set, the hmac key is not needed for decryption + SetEncPropertyEx(&data, NC_ENC_SET_NIP44_NONCE, pNonce, NC_ENCRYPTION_NONCE_SIZE); + SetEncData(&data, pTextPtr, pCipherText, size); + + 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(); + + /* + * Use the extended api to set properties correctly and validate them. + * + * The version MUST be set before continuing to set properties + * + * Since pointers are used, they must be only be set/accessed inside + * this fixed statement. + */ + SetEncProperty(&data, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44); + SetEncPropertyEx(&data, NC_ENC_SET_NIP44_MAC_KEY, pHmacKeyOut, NC_HMAC_KEY_SIZE); + SetEncPropertyEx(&data, NC_ENC_SET_NIP44_NONCE, pNonce, NC_ENCRYPTION_NONCE_SIZE); + SetEncData(&data, pTextPtr, pCipherText, size); + + 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; + } + } + + ///<inheritdoc/> + 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 + + + private void SetEncPropertyEx(NCEncryptionArgs* args, uint prop, byte* value, uint valueLen) + { + NCResult result = Functions.NCSetEncryptionPropertyEx(args, prop, value, valueLen); + NCUtil.CheckResult<FunctionTable.NCSetEncryptionPropertyExDelegate>(result, true); + } + + private void SetEncProperty(NCEncryptionArgs* args, uint prop, uint value) + { + NCResult result = Functions.NCSetEncryptionProperty(args, prop, value); + NCUtil.CheckResult<FunctionTable.NCSetEncryptionPropertyExDelegate>(result, true); + } + + private void SetEncData(NCEncryptionArgs* args, byte* input, byte* output, uint dataLen) + { + /* + * WARNING: + * For now this a short-cut for setting the input and output data pointers + * technically this still works and avoids the PInvoke call, but this may + * change in the future. + */ + args->dataSize = dataLen; + args->inputData = input; + args->outputData = output; + } + + ///<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/NostrMessageCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs new file mode 100644 index 0000000..918d196 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs @@ -0,0 +1,423 @@ +// 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 System.Security.Authentication; + +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; + + /// <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 => MemoryMarshal.CreateSpan(ref GetNonceRef(), sizeof(Buffer32)); + + /// <summary> + /// The message MAC set during encryption, and required for decryption + /// </summary> + public unsafe Span<byte> Mac => MemoryMarshal.CreateSpan(ref GetMacRef(), 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 GetPayloadBufferSize(int dataSize) + => version.GetPayloadBufferSize(dataSize); + + /// <summary> + /// Gets the size of the buffer required to hold the full encrypted message data + /// for the encryption version used + /// </summary> + /// <param name="dataSize">The plaintext data size</param> + /// <returns>The estimated size of the output buffer</returns> + public int GetMessageBufferSize(int dataSize) + => version.GetMessageBufferSize(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 NostrMessageCipher 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 NostrMessageCipher 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 NostrMessageCipher 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 NostrMessageCipher 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 NostrMessageCipher 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 NostrMessageCipher SetRandomNonce(IRandomSource rng) + { + rng.GetRandomBytes(Nonce); + return this; + } + + /// <summary> + /// Configures a 32 byte mac for the message for nip44 decryption + /// </summary> + /// <param name="mac">The message mac</param> + /// <returns>The current instance for chaining</returns> + public NostrMessageCipher SetMac(ReadOnlySpan<byte> mac) + { + MemoryUtil.CopyStruct(mac, ref _mac32); + return this; + } + + /// <summary> + /// Decrypts a full nostr encrypted message and writes the plaintext + /// data to the output buffer + /// </summary> + /// <param name="message">The nostr message buffer to decrypt</param> + /// <param name="plaintext">The output plaintext buffer</param> + /// <returns>The number of bytes written the the plaintext buffer</returns> + /// <exception cref="FormatException"></exception> + /// <exception cref="NotSupportedException"></exception> + public int DecryptMessage(ReadOnlySpan<byte> message, Span<byte> plaintext) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => DecryptNip44Message(message, plaintext), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + /// <summary> + /// 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 + /// </summary> + /// <param name="plaintext">The plaintext data to be encrypted</param> + /// <param name="message">The buffer to write the encrypted message data to</param> + /// <returns>The number of bytes written to the message buffer</returns> + /// <exception cref="NotSupportedException"></exception> + public int EncryptMessage(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44Message(plaintext, message), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44Message(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + int minRequiredOutSize = Nip44Util.CalcFinalBufferSize(plaintext.Length); + + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, minRequiredOutSize, nameof(message)); + + ForwardOnlyWriter<byte> messageWriter = new(message); + + // From spec -> concat(version, nonce, ciphertext, mac) + messageWriter.Append(0x02); // Version + messageWriter.Append<byte>(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<byte>(Mac); + + return messageWriter.Written; + } + + /// <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 EncryptPayload(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44(plaintext, message), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44(ReadOnlySpan<byte> plaintext, Span<byte> 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<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( + 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<byte> message, Span<byte> 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<byte> plaintextBuffer = MemoryUtil.UnsafeAllocNearestPage<byte>(msg.Ciphertext.Length, true); + + int written = DecryptPayload(msg.Ciphertext, plaintextBuffer.Span); + + Span<byte> 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; + } + + /// <summary> + /// Decrypts a nostr encrypted message in it's full binary from. + /// </summary> + /// <param name="payload"></param> + /// <param name="plaintext"></param> + /// <returns>The number of bytes written to the output buffer, or an error code if an error occured during the encryption</returns> + /// <exception cref="NotSupportedException"></exception> + public int DecryptPayload(ReadOnlySpan<byte> payload, Span<byte> plaintext) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => DecryptNip44Payload(payload, plaintext), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int DecryptNip44Payload(ReadOnlySpan<byte> message, Span<byte> 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<Buffer32, byte>(ref _nonce32); + } + + private unsafe ref byte GetMacRef() + { + Debug.Assert(NC_ENCRYPTION_MAC_SIZE == sizeof(Buffer32)); + return ref Unsafe.As<Buffer32, byte>(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); + } + + /// <summary> + /// Initializes a new <see cref="NostrMessageCipher"/> 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 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/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..17b66b2 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs @@ -0,0 +1,141 @@ +// 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; + public readonly NCSetEncryptionDataDelegate NCSetEncryptionData; + public readonly NCSetEncryptionPropertyDelegate NCSetEncryptionProperty; + public readonly NCSetEncryptionPropertyExDelegate NCSetEncryptionPropertyEx; + +#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>(); + NCSetEncryptionData = library.DangerousGetFunction<NCSetEncryptionDataDelegate>(); + NCSetEncryptionProperty = library.DangerousGetFunction<NCSetEncryptionPropertyDelegate>(); + NCSetEncryptionPropertyEx = library.DangerousGetFunction<NCSetEncryptionPropertyExDelegate>(); + +#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); + + [SafeMethodName("NCSetEncryptionProperty")] + internal delegate NCResult NCSetEncryptionPropertyDelegate(NCEncryptionArgs* args, uint property, uint value); + + [SafeMethodName("NCSetEncryptionPropertyEx")] + internal delegate NCResult NCSetEncryptionPropertyExDelegate(NCEncryptionArgs* args, uint property, byte* value, uint valueLen); + + [SafeMethodName("NCSetEncryptionData")] + internal delegate NCResult NCSetEncryptionDataDelegate(NCEncryptionArgs* args, byte* input, byte* output, uint dataSize); + } +} 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..91f0ff5 --- /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* nonceData; + public byte* keyData; + 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; + } +} |