From d9f4778896407ebe6e1b8bb439d2c175b4a22f45 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 4 Feb 2024 21:22:48 -0500 Subject: noscrypt impl prototype and some simple tests --- .../LibNoscryptTests.cs | 86 +++++++++ .../NVault.Crypto.NoscryptTests.csproj | 23 +++ .../Properties/launchSettings.json | 8 + lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs | 96 ++++++++++ lib/NVault.Crypto.Noscrypt/src/INostrCrypto.cs | 32 ++++ lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs | 170 ++++++++++++------ lib/NVault.Crypto.Noscrypt/src/NCContext.cs | 88 +++++++++ lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs | 29 +++ lib/NVault.Crypto.Noscrypt/src/NCPublicKey.cs | 31 ++++ lib/NVault.Crypto.Noscrypt/src/NCSecretKey.cs | 31 ++++ lib/NVault.Crypto.Noscrypt/src/NCUtil.cs | 175 ++++++++++++++++++ .../src/NVault.Crypto.Noscrypt.csproj | 2 +- lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs | 196 ++++++++++++++++++++ lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs | 200 +++++++++++++++++++++ 14 files changed, 1115 insertions(+), 52 deletions(-) create mode 100644 lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/LibNoscryptTests.cs create mode 100644 lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/NVault.Crypto.NoscryptTests.csproj create mode 100644 lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/Properties/launchSettings.json create mode 100644 lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/INostrCrypto.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NCContext.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NCPublicKey.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NCSecretKey.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NCUtil.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs (limited to 'lib') diff --git a/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/LibNoscryptTests.cs b/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/LibNoscryptTests.cs new file mode 100644 index 0000000..d254253 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/LibNoscryptTests.cs @@ -0,0 +1,86 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Runtime.CompilerServices; + +using VNLib.Hashing; +using VNLib.Utils.Memory; + +namespace NVault.Crypto.Noscrypt.Tests +{ + [TestClass()] + public class LibNoscryptTests + { + const string NoscryptLib = @"F:\Programming\noscrypt\out\build\x64-debug\Debug\noscrypt.dll"; + + [TestMethod()] + public void InitializeTest() + { + //Random context seed + ReadOnlySpan seed = RandomHash.GetRandomBytes(32); + + using LibNoscrypt library = LibNoscrypt.Load(NoscryptLib); + + //Init new context and interface + NCContext context = library.Initialize(MemoryUtil.Shared, seed); + + using NostrCrypto crypto = new(context, true); + } + + [TestMethod()] + public void ValidateSecretKeyTest() + { + //Random context seed + ReadOnlySpan seed = RandomHash.GetRandomBytes(32); + ReadOnlySpan secretKey = RandomHash.GetRandomBytes(32); + + Span publicKey = stackalloc byte[32]; + + using LibNoscrypt library = LibNoscrypt.Load(NoscryptLib); + + //Init new context and interface + NCContext context = library.Initialize(MemoryUtil.Shared, seed); + + using NostrCrypto crypto = new(context, true); + + //validate the secret key + Assert.IsTrue(crypto.ValidateSecretKey(in NCUtil.AsSecretKey(secretKey))); + + //Generate the public key + crypto.GetPublicKey( + in NCUtil.AsSecretKey(secretKey), + ref NCUtil.AsPublicKey(publicKey) + ); + + //Make sure the does not contain all zeros + Assert.IsTrue(publicKey.ToArray().Any(b => b != 0)); + } + + //Test argument validations + [TestMethod()] + public void TestPublicApiArgValidations() + { + //Random context seed + ReadOnlySpan seed = RandomHash.GetRandomBytes(32); + + using LibNoscrypt library = LibNoscrypt.Load(NoscryptLib); + + //Init new context and interface + NCContext context = library.Initialize(MemoryUtil.Shared, seed); + + using NostrCrypto crypto = new(context, true); + + NCSecretKey secKey = default; + NCPublicKey pubKey = default; + + //noThrow (its a bad sec key but it should not throw) + crypto.ValidateSecretKey(ref secKey); + Assert.ThrowsException(() => crypto.ValidateSecretKey(ref Unsafe.NullRef())); + + //public key + //NoThrow + Assert.ThrowsException(() => crypto.GetPublicKey(ref Unsafe.NullRef(), ref pubKey)); + Assert.ThrowsException(() => crypto.GetPublicKey(in secKey, ref Unsafe.NullRef())); + + } + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/NVault.Crypto.NoscryptTests.csproj b/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/NVault.Crypto.NoscryptTests.csproj new file mode 100644 index 0000000..d3bbd9c --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/NVault.Crypto.NoscryptTests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/Properties/launchSettings.json b/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/Properties/launchSettings.json new file mode 100644 index 0000000..b3894a7 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/NVault.Crypto.NoscryptTests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "NVault.Crypto.NoscryptTests": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs b/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs new file mode 100644 index 0000000..fe1613a --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/FunctionTable.cs @@ -0,0 +1,96 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Native; + +using NCResult = System.Int64; + +namespace NVault.Crypto.Noscrypt +{ + 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; + + private FunctionTable(SafeLibraryHandle library) + { + //Load the required high-level api functions + NCGetContextStructSize = library.DangerousGetMethod(); + NCInitContext = library.DangerousGetMethod(); + NCReInitContext = library.DangerousGetMethod(); + NCDestroyContext = library.DangerousGetMethod(); + NCGetPublicKey = library.DangerousGetMethod(); + NCValidateSecretKey = library.DangerousGetMethod(); + NCSignData = library.DangerousGetMethod(); + NCVerifyData = library.DangerousGetMethod(); + NCSignData = library.DangerousGetMethod(); + NCVerifyData = library.DangerousGetMethod(); + NCEncrypt = library.DangerousGetMethod(); + NCDecrypt = library.DangerousGetMethod(); + } + + /// + /// Initialize a new function table from the specified library + /// + /// + /// The function table structure + /// + /// + public static FunctionTable BuildFunctionTable(SafeLibraryHandle library) => new (library); + + //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, nint dataSize, byte* sig64); + + [SafeMethodName("NCVerifyData")] + internal delegate NCResult NCVerifyDataDelegate(IntPtr ctx, NCPublicKey* sk, byte* data, nint dataSize, byte* sig64); + + [SafeMethodName("NCEncrypt")] + internal delegate NCResult NCEncryptDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCCryptoData* data); + + [SafeMethodName("NCDecrypt")] + internal delegate NCResult NCDecryptDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCCryptoData* data); + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/INostrCrypto.cs b/lib/NVault.Crypto.Noscrypt/src/INostrCrypto.cs new file mode 100644 index 0000000..11be0d0 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/INostrCrypto.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 . + +namespace NVault.Crypto.Noscrypt +{ + public interface INostrCrypto + { + void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey); + + bool ValidateSecretKey(ref readonly NCSecretKey secretKey); + + void SignData(ref readonly NCSecretKey secretKey, ref readonly byte random32, ref readonly byte data, nint dataSize, ref byte sig64); + + void VerifyData(ref readonly NCPublicKey pubKey, ref readonly byte data, nint dataSize, ref byte sig64); + + void Encrypt(ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, ref readonly byte nonce, ref readonly byte plainText, ref byte cipherText, uint size); + + void Decrypt(ref readonly NCSecretKey secretKey, ref readonly NCPublicKey publicKey, ref readonly byte nonce, ref readonly byte cipherText, ref byte plainText, uint size); + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs b/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs index ec0ce2d..4f892b3 100644 --- a/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs +++ b/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs @@ -1,18 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; using VNLib.Utils; using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; using VNLib.Utils.Native; using NCResult = System.Int64; namespace NVault.Crypto.Noscrypt { + + /// + /// Initializes the native library and provides access to the native functions + /// + /// An existing noscrypt library handle + /// A value that indicates if the instance owns the library handle public unsafe sealed class LibNoscrypt(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable { //Values that match the noscrypt.h header @@ -25,62 +44,96 @@ namespace NVault.Crypto.Noscrypt public const int NC_MESSAGE_KEY_SIZE = 32; public const int CTX_ENTROPY_SIZE = 32; - //STRUCTS MUST MATCH THE NOSCRYPT.H HEADER + public const NCResult NC_SUCCESS = 0; + public const byte E_NULL_PTR = 0x01; + public const byte E_INVALID_ARG = 0x02; + public const byte E_INVALID_CTX = 0x03; + public const byte E_ARGUMENT_OUT_OF_RANGE = 0x04; + public const byte E_OPERATION_FAILED = 0x05; - [StructLayout(LayoutKind.Sequential, Size = NC_SEC_KEY_SIZE)] - internal struct NCSecretKey - { - public fixed byte key[NC_SEC_KEY_SIZE]; - } + private readonly FunctionTable _functions = FunctionTable.BuildFunctionTable(Library); - [StructLayout(LayoutKind.Sequential, Size = NC_SEC_PUBKEY_SIZE)] - internal struct NCPublicKey + /// + /// Gets a reference to the loaded function table for + /// the native library + /// + internal ref readonly FunctionTable Functions { - public fixed byte key[NC_SEC_PUBKEY_SIZE]; + get + { + Check(); + Library.ThrowIfClosed(); + return ref _functions; + } } - [StructLayout(LayoutKind.Sequential)] - internal struct NCCryptoData + /// + /// Gets a value that determines if the library has been released + /// + internal bool IsClosed => Library.IsClosed || Library.IsInvalid; + + /// + /// Initialize a new NCContext for use. This may be done once at app startup + /// and is thread-safe for the rest of the application lifetime. + /// + /// + /// Initialization entropy buffer + /// The size of the buffer (must be 32 bytes) + /// The inialized context + /// + /// + /// + public NCContext Initialize(IUnmangedHeap heap, ref readonly byte entropy32, int size) { - public fixed byte nonce[NC_ENCRYPTION_NONCE_SIZE]; - public void* inputData; - public void* outputData; - public uint dataSize; - } - - //FUCNTIONS - [SafeMethodName("NCGetContextStructSize")] - internal delegate uint NCGetContextStructSizeDelegate(); - - [SafeMethodName("NCInitContext")] - internal delegate NCResult NCInitContextDelegate(void* ctx , byte* entropy32); - - [SafeMethodName("NCReInitContext")] - internal delegate NCResult NCReInitContextDelegate(void* ctx, byte* entropy32); - - [SafeMethodName("NCDestroyContext")] - internal delegate NCResult NCDestroyContextDelegate(void* ctx); - - [SafeMethodName("NCGetPublicKey")] - internal delegate NCResult NCGetPublicKeyDelegate(void* ctx, NCSecretKey* secKey, NCPublicKey* publicKey); + ArgumentNullException.ThrowIfNull(heap); - [SafeMethodName("NCValidateSecretKey")] - internal delegate NCResult NCValidateSecretKeyDelegate(void* ctx, NCSecretKey* secKey); + //Entropy must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(size, CTX_ENTROPY_SIZE); - [SafeMethodName("NCSignData")] - internal delegate NCResult NCSignDataDelegate(void* ctx, NCSecretKey* secKey, byte* random32, byte* data, long dataSize, byte* sig64); + //Get struct size + nuint ctxSize = Functions.NCGetContextStructSize.Invoke(); - [SafeMethodName("NCVerifyData")] - internal delegate NCResult NCVerifyDataDelegate(void* ctx, NCPublicKey* pubKey, byte* data, long dataSize, byte* sig64); - - [SafeMethodName("NCSignDigest")] - internal delegate NCResult NCSignDigestDelegate(void* ctx, NCSecretKey* secKey, byte* random32, byte* digest32, byte* sig64); + //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); + } - [SafeMethodName("NCVerifyDigest")] - internal delegate NCResult NCVerifyDigestDelegate(void* ctx, NCPublicKey* pubKey, byte* digest32, byte* sig64); + NCUtil.CheckResult(result); + Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}"); + return new NCContext(ctx, heap, this); + } + catch + { + heap.Free(ref ctx); + throw; + } + } + /// + /// Initialize a new NCContext for use. This may be done once at app startup + /// and is thread-safe for the rest of the application lifetime. + /// + /// + /// The 32byte random seed/nonce for the noscrypt context + /// The inialized context + /// + /// + /// + public NCContext Initialize(IUnmangedHeap heap, ReadOnlySpan enropy32) + { + return Initialize( + heap, + ref MemoryMarshal.GetReference(enropy32), + enropy32.Length + ); + } /// protected override void Free() @@ -88,20 +141,35 @@ namespace NVault.Crypto.Noscrypt if (OwnsHandle) { Library.Dispose(); + Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}"); } } + /// + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// + /// The native library path or name to load + /// The search path options + /// The loaded library instance public static LibNoscrypt Load(string path, DllImportSearchPath search) { //Load the native library SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search); + Trace.WriteLine($"Loaded noscrypt library 0x{handle.DangerousGetHandle():x} from {path}"); + //Create the wrapper return new LibNoscrypt(handle, true); } + /// + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// + /// The native library path or name to load + /// The loaded library instance public static LibNoscrypt Load(string path) => Load(path, DllImportSearchPath.SafeDirectories); - - - } + + } } diff --git a/lib/NVault.Crypto.Noscrypt/src/NCContext.cs b/lib/NVault.Crypto.Noscrypt/src/NCContext.cs new file mode 100644 index 0000000..a9bd47d --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NCContext.cs @@ -0,0 +1,88 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Diagnostics; + +using Microsoft.Win32.SafeHandles; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static NVault.Crypto.Noscrypt.LibNoscrypt; + +using NCResult = System.Int64; + +namespace NVault.Crypto.Noscrypt +{ + /// + /// Represents a context for the native library + /// + /// The heap the handle was allocated from + /// A reference to the native library + public sealed class NCContext : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly IUnmangedHeap Heap; + + /// + /// The library this context was created from + /// + public LibNoscrypt Library { get; } + + internal NCContext(IntPtr handle, IUnmangedHeap heap, LibNoscrypt library) :base(true) + { + ArgumentNullException.ThrowIfNull(heap); + ArgumentNullException.ThrowIfNull(library); + + Heap = heap; + Library = library; + + //Store the handle + SetHandle(handle); + } + + /// + /// Reinitializes the context with the specified entropy + /// + /// The randomness buffer used to randomize the context + /// The random data buffer size (must be 32 bytes) + public unsafe void Reinitalize(ref byte entropy, int size) + { + //Entropy must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(size, CTX_ENTROPY_SIZE); + + this.ThrowIfClosed(); + fixed (byte* p = &entropy) + { + NCResult result = Library.Functions.NCReInitContext.Invoke(handle, p); + NCUtil.CheckResult(result); + } + } + + /// + 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/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs b/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs new file mode 100644 index 0000000..1224fa6 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NCCryptoData.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Runtime.InteropServices; + +namespace NVault.Crypto.Noscrypt +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct NCCryptoData + { + public fixed byte nonce[LibNoscrypt.NC_ENCRYPTION_NONCE_SIZE]; + + public void* inputData; + public void* outputData; + public uint dataSize; + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/NCPublicKey.cs b/lib/NVault.Crypto.Noscrypt/src/NCPublicKey.cs new file mode 100644 index 0000000..9a7ba46 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NCPublicKey.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Runtime.InteropServices; + +using static NVault.Crypto.Noscrypt.LibNoscrypt; + +namespace NVault.Crypto.Noscrypt +{ + /// + /// Represents a user's secp256k1 public key for use with the Nostrcrypt library + /// + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_PUBKEY_SIZE)] + public unsafe struct NCPublicKey + { + private fixed byte key[NC_SEC_PUBKEY_SIZE]; + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/NCSecretKey.cs b/lib/NVault.Crypto.Noscrypt/src/NCSecretKey.cs new file mode 100644 index 0000000..ebd4e9d --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NCSecretKey.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Runtime.InteropServices; + +using static NVault.Crypto.Noscrypt.LibNoscrypt; + +namespace NVault.Crypto.Noscrypt +{ + /// + /// Represents an nostr variant of a secp265k1 secret key that matches + /// the size of the native library + /// + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_KEY_SIZE)] + public unsafe struct NCSecretKey + { + private fixed byte key[NC_SEC_KEY_SIZE]; + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/NCUtil.cs b/lib/NVault.Crypto.Noscrypt/src/NCUtil.cs new file mode 100644 index 0000000..61c2068 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NCUtil.cs @@ -0,0 +1,175 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static NVault.Crypto.Noscrypt.LibNoscrypt; + +using NCResult = System.Int64; + +namespace NVault.Crypto.Noscrypt +{ + public static class NCUtil + { + /// + /// Gets a span of bytes from the current secret key + /// structure + /// + /// + /// The secret key data span + public unsafe static Span 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(ref key); + return MemoryMarshal.CreateSpan(ref asBytes, sizeof(NCSecretKey)); + } + + /// + /// Gets a span of bytes from the current public key + /// structure + /// + /// + /// The public key data as a data span + public unsafe static Span 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(ref key); + return MemoryMarshal.CreateSpan(ref asBytes, sizeof(NCPublicKey)); + } + + /// + /// 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. + /// + /// The secret key data + /// A mutable secret key reference + /// + public unsafe static ref NCSecretKey AsSecretKey(Span span) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(span.Length, sizeof(NCSecretKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As(ref asBytes); + } + + /// + /// 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. + /// + /// The public key data span + /// A mutable reference to the public key structure + /// + public unsafe static ref NCPublicKey AsPublicKey(Span span) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As(ref asBytes); + } + + /// + /// 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. + /// + /// The secret key data span + /// A readonly refernce to the secret key structure + /// + public unsafe static ref readonly NCSecretKey AsSecretKey(ReadOnlySpan span) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(span.Length, sizeof(NCSecretKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As(ref asBytes); + } + + /// + /// 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. + /// + /// The public key data span + /// A readonly reference to the public key structure + /// + public unsafe static ref readonly NCPublicKey AsPublicKey(ReadOnlySpan span) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As(ref asBytes); + } + + internal static void CheckResult(NCResult result) 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(argNumber); + break; + case E_INVALID_ARG: + RaiseArgExceptionForArgumentNumber(argNumber); + break; + case E_ARGUMENT_OUT_OF_RANGE: + RaiseOORExceptionForArgumentNumber(argNumber); + break; + case E_INVALID_CTX: + throw new InvalidOperationException("The library context object is null or invalid"); + case E_OPERATION_FAILED: + throw new InvalidOperationException("The operation failed for an unknown reason"); + + } + } + + private static void RaiseNullArgExceptionForArgumentNumber(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(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(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/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj b/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj index 2416535..1424aee 100644 --- a/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj +++ b/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj @@ -16,7 +16,7 @@ Provides a managed library for the noscrypt native library, along with other helper types for NVault Copyright © 2024 Vaughn Nugent https://www.vaughnnugent.com/resources/software/modules/NVault - https://github.com/VnUgE/NVault/tree/master/ + https://github.com/VnUgE/NVault/tree/master/lib/NVault.Crypto.Noscrypt diff --git a/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs b/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs new file mode 100644 index 0000000..18762c0 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/Nip44Util.cs @@ -0,0 +1,196 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +using VNLib.Utils.Memory; + +namespace NVault.Crypto.Noscrypt +{ + /// + /// Provides a set of utility methods for working with the Noscrypt library + /// + public static class Nip44Util + { + /// + /// Calculates the required NIP44 encryption buffer size for + /// the specified input data size + /// + /// The size (in bytes) of the encoded data to encrypt + /// The exact size of the padded buffer output + public static uint CalcBufferSize(uint dataSize) + { + //always add leading 2 bytes for the encoded data size + dataSize += sizeof(ushort); + + //Min message size is 32 bytes + uint minSize = Math.Max(dataSize, 32); + + //calculate the next power of 2 + uint nextPow2 = 1; + while (nextPow2 < minSize) + { + nextPow2 <<= 1; + } + + return nextPow2; + } + + /// + /// Formats the plaintext data into a buffer that can be properly encrypted. + /// The output buffer must be zeroed, or can be zeroed using the + /// parameter. Use + /// to determine the required output buffer size. + /// + /// A buffer containing plaintext data to copy to the output + /// The output data buffer to format + /// A value that indicates if the buffer should be zeroed before use + public static void FormatBuffer(ReadOnlySpan plaintextData, Span 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( + ref MemoryMarshal.GetReference(plaintextData), + sizeof(ushort), + ref MemoryMarshal.GetReference(output), + 0, + (uint)plaintextData.Length + ); + + //We assume the remaining buffer is zeroed out + } + + public static void Encrypt( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan nonce32, + ReadOnlySpan plainText, + Span 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, 32, nameof(nonce32)); + + //Encrypt data, use the plaintext buffer size as the data size + lib.Encrypt( + in secretKey, + in publicKey, + in MemoryMarshal.GetReference(nonce32), + in MemoryMarshal.GetReference(plainText), + ref MemoryMarshal.GetReference(cipherText), + (uint)plainText.Length + ); + } + + public static unsafe void Encrypt( + this INostrCrypto lib, + ref NCSecretKey secretKey, + ref NCPublicKey publicKey, + void* nonce32, + 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 + Encrypt( + lib, + in secretKey, + in publicKey, + new Span(nonce32, 32), + new Span(plainText, (int)size), + new Span(cipherText, (int)size) + ); + } + + + public static void Decrypt( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan nonce32, + ReadOnlySpan cipherText, + Span 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.Decrypt( + in secretKey, + in publicKey, + in MemoryMarshal.GetReference(nonce32), + in MemoryMarshal.GetReference(cipherText), + ref MemoryMarshal.GetReference(plainText), + (uint)cipherText.Length + ); + } + + public static unsafe void Decrypt( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + void* nonce32, + void* cipherText, + void* plainText, + uint size + ) + { + ArgumentNullException.ThrowIfNull(nonce32); + ArgumentNullException.ThrowIfNull(cipherText); + ArgumentNullException.ThrowIfNull(plainText); + + //Spans are easer to forward references from pointers without screwing up arguments + Decrypt( + lib, + in secretKey, + in publicKey, + new Span(nonce32, 32), + new Span(cipherText, (int)size), + new Span(plainText, (int)size) + ); + } + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs b/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs new file mode 100644 index 0000000..6b547de --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NostrCrypto.cs @@ -0,0 +1,200 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Runtime.CompilerServices; + +using VNLib.Utils; + +using NCResult = System.Int64; + +namespace NVault.Crypto.Noscrypt +{ + + /// + /// A default implementation of the interface + /// + /// The initialized library context + public unsafe class NostrCrypto(NCContext context, bool ownsContext) : VnDisposeable, INostrCrypto + { + /// + /// Gets the underlying library context. + /// + public NCContext Context => context; + + private ref readonly FunctionTable Functions => ref context.Library.Functions; + + /// + public void Decrypt( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce, + ref readonly byte cipherText, + ref byte plainText, + uint size + ) + { + Check(); + + IntPtr libCtx = context.DangerousGetHandle(); + + NCCryptoData data = default; + data.dataSize = size; + + //Copy nonce to struct memory buffer + Unsafe.CopyBlock( + ref Unsafe.AsRef(data.nonce), + in nonce, + LibNoscrypt.NC_ENCRYPTION_NONCE_SIZE + ); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText) + { + //Set input data to the cipher text to decrypt and the output data to the plaintext buffer + data.inputData = pCipherText; + data.outputData = pTextPtr; + + NCResult result = Functions.NCDecrypt.Invoke(libCtx, pSecKey, pPubKey, &data); + NCUtil.CheckResult(result); + } + } + + /// + public void Encrypt( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce, + ref readonly byte plainText, + ref byte cipherText, + uint size + ) + { + Check(); + + IntPtr libCtx = context.DangerousGetHandle(); + + NCCryptoData data = default; + data.dataSize = size; + + //Copy nonce to struct memory buffer + Unsafe.CopyBlock( + ref Unsafe.AsRef(data.nonce), + in nonce, + 0 + ); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText) + { + //Set input data to the plaintext to encrypt and the output data to the cipher text buffer + data.inputData = pTextPtr; + data.outputData = pCipherText; + + NCResult result = Functions.NCEncrypt.Invoke(libCtx, pSecKey, pPubKey, &data); + NCUtil.CheckResult(result); + } + } + + /// + public void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey) + { + Check(); + + IntPtr libCtx = context.DangerousGetHandle(); + + fixed(NCSecretKey* pSecKey = &secretKey) + fixed(NCPublicKey* pPubKey = &publicKey) + { + NCResult result = Functions.NCGetPublicKey.Invoke(libCtx, pSecKey, pPubKey); + NCUtil.CheckResult(result); + } + } + + /// + public void SignData( + ref readonly NCSecretKey secretKey, + ref readonly byte random32, + ref readonly byte data, + nint dataSize, + ref byte sig64 + ) + { + Check(); + + IntPtr libCtx = context.DangerousGetHandle(); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed(byte* pData = &data, pSig = &sig64, pRandom = &random32) + { + NCResult result = Functions.NCSignData.Invoke(libCtx, pSecKey, pRandom, pData, dataSize, pSig); + NCUtil.CheckResult(result); + } + } + + /// + 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(result); + + //Result should be 1 if the secret key is valid + return result == 1; + } + } + + /// + public void VerifyData( + ref readonly NCPublicKey pubKey, + ref readonly byte data, + nint dataSize, + ref byte sig64 + ) + { + Check(); + + IntPtr libCtx = context.DangerousGetHandle(); + + fixed(NCPublicKey* pPubKey = &pubKey) + fixed (byte* pData = &data, pSig = &sig64) + { + NCResult result = Functions.NCVerifyData.Invoke(libCtx, pPubKey, pData, dataSize, pSig); + NCUtil.CheckResult(result); + } + } + + + /// + protected override void Free() + { + if(ownsContext) + { + context.Dispose(); + } + } + } +} -- cgit