aboutsummaryrefslogtreecommitdiff
path: root/lib/NVault.Crypto.Secp256k1/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/NVault.Crypto.Secp256k1/src')
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs274
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs32
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs284
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj27
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs56
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs76
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs58
-rw-r--r--lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs101
8 files changed, 908 insertions, 0 deletions
diff --git a/lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs b/lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs
new file mode 100644
index 0000000..bb014df
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs
@@ -0,0 +1,274 @@
+// 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.Security.Cryptography;
+using System.Runtime.InteropServices;
+
+using VNLib.Hashing;
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+using static NVault.Crypto.Secp256k1.LibSecp256k1;
+
+namespace NVault.Crypto.Secp256k1
+{
+ /// <summary>
+ /// The callback function signature required for ECDH hash functions
+ /// </summary>
+ /// <param name="state">The callback state</param>
+ /// <returns>The return value to be passed as a result of the operation</returns>
+ public delegate int Secp256k1EcdhHashFunc(in Secp256HashFuncState state);
+
+ public static unsafe class ContextExtensions
+ {
+ /// <summary>
+ /// Signs a 32byte message digest with the specified secret key on the current context and writes the signature to the specified buffer
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="secretKey">The 32byte secret key used to sign messages from the user</param>
+ /// <param name="digest">The 32byte message digest to compute the signature of</param>
+ /// <param name="signature">The buffer to write the signature output to, must be at-least 64 bytes</param>
+ /// <returns>The number of bytes written to the signature buffer, or less than 1 if the operation failed</returns>
+ public static ERRNO SchnorSignDigest(this in Secp256k1Context context, ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> digest, Span<byte> signature)
+ {
+ //Check the signature buffer size
+ if (signature.Length < SignatureSize)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Message digest must be exactly 32 bytes long
+ if (digest.Length != (int)HashAlg.SHA256)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Secret key size must be exactly the size of the secret key struct
+ if(secretKey.Length != sizeof(Secp256k1SecretKey))
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Stack allocated keypair
+ KeyPair keyPair = new();
+
+ //Init the secret key struct from key data
+ Secp256k1SecretKey secKeyStruct = MemoryMarshal.Read<Secp256k1SecretKey>(secretKey);
+
+ //Randomize the context and create the keypair
+ if (!context.CreateKeyPair(&keyPair, &secKeyStruct))
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Create the random nonce
+ byte* random = stackalloc byte[RandomBufferSize];
+
+ //Fill the buffer with random bytes
+ context.Lib.GetRandomBytes(new Span<byte>(random, RandomBufferSize));
+
+ try
+ {
+ fixed (byte* sigPtr = &MemoryMarshal.GetReference(signature),
+ digestPtr = &MemoryMarshal.GetReference(digest))
+ {
+ //Sign the message hash and write the output to the signature buffer
+ if (context.Lib._signHash(context.Context, sigPtr, digestPtr, &keyPair, random) != 1)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+ }
+ finally
+ {
+ //Erase entropy
+ MemoryUtil.InitializeBlock(random, RandomBufferSize);
+
+ //Clear the keypair, contains the secret key, even if its stack allocated
+ MemoryUtil.ZeroStruct(&keyPair);
+ }
+
+ //Signature size is always 64 bytes
+ return SignatureSize;
+ }
+
+ /// <summary>
+ /// Generates an x-only Schnor encoded public key from the specified secret key on the
+ /// current context and writes it to the specified buffer.
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="secretKey">The 32byte secret key used to derrive the public key from</param>
+ /// <param name="pubKeyBuffer">The buffer to write the x-only Schnor encoded public key</param>
+ /// <returns>The number of bytes written to the output buffer, or 0 if the operation failed</returns>
+ /// <exception cref="CryptographicException"></exception>
+ public static ERRNO GeneratePubKeyFromSecret(this in Secp256k1Context context, ReadOnlySpan<byte> secretKey, Span<byte> pubKeyBuffer)
+ {
+ if (secretKey.Length != sizeof(Secp256k1SecretKey))
+ {
+ throw new CryptographicException($"Your secret key must be exactly {sizeof(Secp256k1SecretKey)} bytes long");
+ }
+
+ if (pubKeyBuffer.Length < XOnlyPublicKeySize)
+ {
+ throw new CryptographicException($"Your public key buffer must be at least {XOnlyPublicKeySize} bytes long");
+ }
+
+ //Protect for released lib
+ context.Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Stack allocated keypair and x-only public key
+ Secp256k1PublicKey xOnlyPubKey = new();
+ Secp256k1SecretKey secKeyStruct = MemoryMarshal.Read<Secp256k1SecretKey>(secretKey);
+ KeyPair keyPair = new();
+
+ try
+ {
+ //Init context and keypair
+ if (!context.CreateKeyPair(&keyPair, &secKeyStruct))
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //X-only public key from the keypair
+ if (context.Lib._createXonly(context.Context, &xOnlyPubKey, 0, &keyPair) != 1)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ fixed (byte* pubBuffer = &MemoryMarshal.GetReference(pubKeyBuffer))
+ {
+ //Serialize the public key to the buffer as an X-only public key without leading status byte
+ if (context.Lib._serializeXonly(context.Context, pubBuffer, &xOnlyPubKey) != 1)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+ }
+ finally
+ {
+ //Clear the keypair, contains the secret key, even if its stack allocated
+ MemoryUtil.ZeroStruct(&keyPair);
+ }
+
+ //PubKey length is constant
+ return XOnlyPublicKeySize;
+ }
+
+ /// <summary>
+ /// Verifies that a given secret key is valid using the current context
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="secretKey">The secret key to verify</param>
+ /// <returns>A boolean value that indicates if the secret key is valid or not</returns>
+ /// <exception cref="CryptographicException"></exception>
+ public static bool VerifySecretKey(this in Secp256k1Context context, ReadOnlySpan<byte> secretKey)
+ {
+ if (secretKey.Length != sizeof(Secp256k1SecretKey))
+ {
+ throw new CryptographicException($"Your secret key must be exactly {sizeof(Secp256k1SecretKey)} bytes long");
+ }
+
+ context.Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Get sec key ref and verify
+ fixed(byte* ptr = &MemoryMarshal.GetReference(secretKey))
+ {
+ return context.Lib._secKeyVerify.Invoke(context.Context, ptr) == 1;
+ }
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ private readonly ref struct EcdhHashFuncState
+ {
+ public readonly IntPtr HashFunc { get; init; }
+ public readonly IntPtr Opaque { get; init; }
+ public readonly int OutLen { get; init; }
+ }
+
+ /// <summary>
+ /// Verifies that a given secret key is valid using the current context
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="secretKey">The secret key to verify</param>
+ /// <returns>A boolean value that indicates if the secret key is valid or not</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static bool ComputeSharedKey(this in Secp256k1Context context, Span<byte> data, ReadOnlySpan<byte> xOnlyPubKey, ReadOnlySpan<byte> secretKey, Secp256k1EcdhHashFunc callback, IntPtr opaque)
+ {
+ if (secretKey.Length != sizeof(Secp256k1SecretKey))
+ {
+ throw new ArgumentException($"Your secret key buffer must be exactly {sizeof(Secp256k1SecretKey)} bytes long");
+ }
+
+ //Init callback state struct
+ EcdhHashFuncState state = new()
+ {
+ HashFunc = Marshal.GetFunctionPointerForDelegate(callback),
+ Opaque = opaque,
+ OutLen = data.Length
+ };
+
+ context.Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Stack allocated keypair and x-only public key
+ Secp256k1PublicKey peerPubKey = new();
+
+ //Parse the public key from the buffer
+ fixed (byte* pubkeyPtr = &MemoryMarshal.GetReference(xOnlyPubKey))
+ {
+ context.Lib._xOnlyPubkeyParse(context.Context, &peerPubKey, pubkeyPtr);
+ }
+
+ fixed (byte* dataPtr = &MemoryMarshal.GetReference(data),
+ secKeyPtr = &MemoryMarshal.GetReference(secretKey))
+ {
+ return context.Lib._ecdh.Invoke(
+ context.Context,
+ dataPtr,
+ &peerPubKey,
+ secKeyPtr,
+ UmanagedEcdhHashFuncCallback,
+ &state
+ ) == 1;
+ }
+
+ /*
+ * Umanaged wrapper function for invoking the safe user callback
+ * from the unmanaged lib
+ */
+ static int UmanagedEcdhHashFuncCallback(byte* output, byte* x32, byte* y32, void* opaque)
+ {
+ //Recover the callback
+ if (opaque == null)
+ {
+ return 0;
+ }
+
+ EcdhHashFuncState* state = (EcdhHashFuncState*)opaque;
+
+ //Init user-state structure
+ Secp256HashFuncState userState = new(output, state->OutLen, x32, 32, new(opaque));
+
+ //Recover the function pointer
+ Secp256k1EcdhHashFunc callback = Marshal.GetDelegateForFunctionPointer<Secp256k1EcdhHashFunc>(state->HashFunc);
+
+ //Invoke the callback
+ return callback(in userState);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs b/lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs
new file mode 100644
index 0000000..4e1861d
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/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 NVault.Crypto.Secp256k1
+{
+ /// <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/lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs b/lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs
new file mode 100644
index 0000000..8dda269
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs
@@ -0,0 +1,284 @@
+// 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.Hashing;
+using VNLib.Utils;
+using VNLib.Utils.Native;
+using VNLib.Utils.Extensions;
+
+namespace NVault.Crypto.Secp256k1
+{
+
+ internal unsafe delegate int EcdhHasFunc(byte* output, byte* x32, byte* y32, void* data);
+
+ public unsafe class LibSecp256k1 : VnDisposeable
+ {
+ public const int SignatureSize = 64;
+ public const int RandomBufferSize = 32;
+ public const int XOnlyPublicKeySize = 32;
+
+ public static readonly int SecretKeySize = sizeof(Secp256k1SecretKey);
+
+ /*
+ * Unsafe structures that represent the native keypair and x-only public key
+ * structures. They hold character arrays
+ */
+ [StructLayout(LayoutKind.Sequential, Size = 96)]
+ internal struct KeyPair
+ {
+ public fixed byte data[96];
+ }
+
+ /// <summary>
+ /// 1:1 with the secp256k1_pubkey structure
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 64)]
+ internal struct Secp256k1PublicKey
+ {
+ public fixed byte data[64];
+ }
+
+
+ //Native methods
+ [SafeMethodName("secp256k1_context_create")]
+ internal delegate IntPtr ContextCreate(int flags);
+
+ [SafeMethodName("secp256k1_context_destroy")]
+ internal delegate void ContextDestroy(IntPtr context);
+
+ [SafeMethodName("secp256k1_context_randomize")]
+ internal delegate int RandomizeContext(IntPtr context, byte* seed32);
+
+ [SafeMethodName("secp256k1_keypair_create")]
+ internal delegate int KeypairCreate(IntPtr context, KeyPair* keyPair, byte* secretKey);
+
+ [SafeMethodName("secp256k1_keypair_xonly_pub")]
+ internal delegate int KeypairXOnlyPub(IntPtr ctx, Secp256k1PublicKey* pubkey, int pk_parity, KeyPair* keypair);
+
+ [SafeMethodName("secp256k1_xonly_pubkey_serialize")]
+ internal delegate int XOnlyPubkeySerialize(IntPtr ctx, byte* output32, Secp256k1PublicKey* pubkey);
+
+ [SafeMethodName("secp256k1_schnorrsig_sign32")]
+ internal delegate int SignHash(IntPtr ctx, byte* sig64, byte* msg32, KeyPair* keypair, byte* aux_rand32);
+
+ [SafeMethodName("secp256k1_ec_seckey_verify")]
+ internal delegate int SecKeyVerify(IntPtr ctx, in byte* seckey);
+
+ [SafeMethodName("secp256k1_ec_pubkey_serialize")]
+ internal delegate int PubKeySerialize(IntPtr ctx, byte* outPubKey, ulong* outLen, Secp256k1PublicKey* pubKey, uint flags);
+
+ [SafeMethodName("secp256k1_xonly_pubkey_parse")]
+ internal delegate int XOnlyPubkeyParse(IntPtr ctx, Secp256k1PublicKey* pubkey, byte* input32);
+
+ [SafeMethodName("secp256k1_ecdh")]
+ internal delegate int Ecdh(
+ IntPtr ctx,
+ byte* output,
+ Secp256k1PublicKey* pubkey,
+ byte* scalar,
+ EcdhHasFunc hashFunc,
+ void* dataPtr
+ );
+
+
+ /// <summary>
+ /// Loads the Secp256k1 library from the specified path and creates a wrapper class (loads methods from the library)
+ /// </summary>
+ /// <param name="dllPath">The realtive or absolute path to the shared library</param>
+ /// <param name="search">The DLL probing path pattern</param>
+ /// <returns>The <see cref="LibSecp256k1"/> library wrapper class</returns>
+ /// <exception cref="DllNotFoundException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static LibSecp256k1 LoadLibrary(string dllPath, DllImportSearchPath search, IRandomSource? random)
+ {
+ _ = dllPath?? throw new ArgumentNullException(nameof(dllPath));
+
+ //try to load the library
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(dllPath, search);
+
+ //try to create the wrapper class, if it fails, dispose the library
+ try
+ {
+ //setup fallback random source if null
+ random ??= new FallbackRandom();
+
+ //Create the lib
+ return new LibSecp256k1(lib, random);
+ }
+ catch
+ {
+ //Dispose the library if the creation failed
+ lib.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Loads the Secp256k1 library from the specified path and creates a wrapper class (loads methods from the library)
+ /// </summary>
+ /// <param name="handle">The handle to the shared library</param>
+ /// <param name="random">An optional random source to create random entropy and secrets from</param>
+ /// <returns>The <see cref="LibSecp256k1"/> library wrapper class</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static LibSecp256k1 FromHandle(SafeLibraryHandle handle, IRandomSource? random)
+ {
+ _ = handle ?? throw new ArgumentNullException(nameof(handle));
+ //setup fallback random source if null
+ random ??= new FallbackRandom();
+ //Create the lib
+ return new LibSecp256k1(handle, random);
+ }
+
+ /// <summary>
+ /// The underlying library handle
+ /// </summary>
+ public SafeLibraryHandle SafeLibHandle { get; }
+
+ internal readonly KeypairCreate _createKeyPair;
+ internal readonly ContextCreate _create;
+ internal readonly RandomizeContext _randomize;
+ internal readonly ContextDestroy _destroy;
+ internal readonly KeypairXOnlyPub _createXonly;
+ internal readonly XOnlyPubkeySerialize _serializeXonly;
+ internal readonly SignHash _signHash;
+ internal readonly SecKeyVerify _secKeyVerify;
+ internal readonly PubKeySerialize _pubKeySerialize;
+ internal readonly Ecdh _ecdh;
+ internal readonly XOnlyPubkeyParse _xOnlyPubkeyParse;
+ private readonly IRandomSource _randomSource;
+
+ /// <summary>
+ /// Creates a new instance of the <see cref="LibSecp256k1"/> class from the specified library handle
+ /// </summary>
+ /// <param name="handle">The library handle that referrences the secp256k1 platform specific library</param>
+ /// <remarks>
+ /// This method attempts to capture all the native methods from the library, which may throw if the library is not valid.
+ /// </remarks>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public LibSecp256k1(SafeLibraryHandle handle, IRandomSource randomSource)
+ {
+ //Store library handle
+ SafeLibHandle = handle ?? throw new ArgumentNullException(nameof(handle));
+
+ //Get all method handles and store them
+ _create = handle.DangerousGetMethod<ContextCreate>();
+ _createKeyPair = handle.DangerousGetMethod<KeypairCreate>();
+ _randomize = handle.DangerousGetMethod<RandomizeContext>();
+ _destroy = handle.DangerousGetMethod<ContextDestroy>();
+ _createXonly = handle.DangerousGetMethod<KeypairXOnlyPub>();
+ _serializeXonly = handle.DangerousGetMethod<XOnlyPubkeySerialize>();
+ _signHash = handle.DangerousGetMethod<SignHash>();
+ _secKeyVerify = handle.DangerousGetMethod<SecKeyVerify>();
+ _pubKeySerialize = handle.DangerousGetMethod<PubKeySerialize>();
+ _ecdh = handle.DangerousGetMethod<Ecdh>();
+ _xOnlyPubkeyParse = handle.DangerousGetMethod<XOnlyPubkeyParse>();
+
+ //Store random source
+ _randomSource = randomSource;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the <see cref="LibSecp256k1"/> class from the specified library handle
+ /// with a fallback random source
+ /// </summary>
+ /// <param name="handle">The library handle</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public LibSecp256k1(SafeLibraryHandle handle):this(handle, new FallbackRandom())
+ {}
+
+ /// <summary>
+ /// Generates a new secret key and writes it to the specified buffer. The buffer size must be exactly <see cref="SecretKeySize"/> bytes long
+ /// <para>
+ /// NOTE: You should verify this validity of the key against the library with a new <see cref="Secp256k1Context"/>
+ /// </para>
+ /// </summary>
+ /// <param name="buffer">The secret key buffer</param>
+ /// <exception cref="ArgumentException"></exception>
+ public void CreateSecretKey(Span<byte> buffer)
+ {
+ //Protect for released lib
+ SafeLibHandle.ThrowIfClosed();
+
+ if(buffer.Length != sizeof(Secp256k1SecretKey))
+ {
+ throw new ArgumentException($"Buffer must be exactly {sizeof(Secp256k1SecretKey)} bytes long", nameof(buffer));
+ }
+
+ //Fill the buffer with random bytes
+ _randomSource.GetRandomBytes(buffer);
+ }
+
+ /// <summary>
+ /// Fills the given buffer with random bytes from
+ /// the internal random source
+ /// </summary>
+ /// <param name="buffer">The buffer to fill with random data</param>
+ public void GetRandomBytes(Span<byte> buffer)
+ {
+ //Protect for released lib
+ SafeLibHandle.ThrowIfClosed();
+
+ _randomSource.GetRandomBytes(buffer);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="Secp256k1Context"/> from the current managed library
+ /// </summary>
+ /// <param name="Lib"></param>
+ /// <returns>The new <see cref="Secp256k1Context"/> object from the library</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public Secp256k1Context CreateContext()
+ {
+ //Protect for released lib
+ SafeLibHandle.ThrowIfClosed();
+
+ //Create new context
+ IntPtr context = _create(1);
+
+ if (context == IntPtr.Zero)
+ {
+ throw new OutOfMemoryException("Failed to create the new Secp256k1 context");
+ }
+
+ return new Secp256k1Context(this, context);
+ }
+
+ protected override void Free()
+ {
+ //Free native library
+ SafeLibHandle.Dispose();
+ }
+
+ private record class FallbackRandom : IRandomSource
+ {
+ public void GetRandomBytes(Span<byte> buffer)
+ {
+ //Use the random generator from the crypto lib
+ RandomHash.GetRandomBytes(buffer);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj b/lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj
new file mode 100644
index 0000000..efd78ab
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj
@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net8.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <RootNamespace>NVault.Crypto.Secp256k1</RootNamespace>
+ <AssemblyName>NVault.Crypto.Secp256k1</AssemblyName>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>NVault.Crypto.Secp256k1</Product>
+ <Description>Provides a managed library for the Bitcoin Core secp256k1 library, along with other helper types for NVault</Description>
+ <Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/NVault</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/NVault/tree/master/</RepositoryUrl>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0114" />
+ <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0114" />
+ </ItemGroup>
+
+</Project>
diff --git a/lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs b/lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs
new file mode 100644
index 0000000..c82321c
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs
@@ -0,0 +1,56 @@
+// 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 NVault.Crypto.Secp256k1
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe readonly ref struct Secp256HashFuncState
+ {
+
+ /// <summary>
+ /// The opaque pointer passed to the hash function
+ /// </summary>
+ public readonly IntPtr Opaque { get; }
+
+ private readonly byte* _output;
+ private readonly byte* _xCoord;
+ private readonly int _outputLength;
+ private readonly int _xCoordLength;
+
+ internal Secp256HashFuncState(byte* output, int outputLength, byte* xCoord, int xCoordLength, IntPtr opaque)
+ {
+ Opaque = opaque;
+ _output = output;
+ _outputLength = outputLength;
+ _xCoord = xCoord;
+ _xCoordLength = xCoordLength;
+ }
+
+ /// <summary>
+ /// Gets the output buffer as a span
+ /// </summary>
+ /// <returns>The output buffer span</returns>
+ public readonly Span<byte> GetOutput() => new(_output, _outputLength);
+
+ /// <summary>
+ /// Gets the x coordinate argument as a span
+ /// </summary>
+ /// <returns>The xcoordinate buffer span</returns>
+ public readonly ReadOnlySpan<byte> GetXCoordArg() => new(_xCoord, _xCoordLength);
+ }
+} \ No newline at end of file
diff --git a/lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs b/lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs
new file mode 100644
index 0000000..dfb3ff8
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs
@@ -0,0 +1,76 @@
+// 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 NVault.Crypto.Secp256k1.LibSecp256k1;
+
+namespace NVault.Crypto.Secp256k1
+{
+ /// <summary>
+ /// Represents a Secp256k1 context, it is used to randomize the instance, create key pairs,
+ /// and frees the context when disposed
+ /// </summary>
+ /// <param name="Lib">The <see cref="LibSecp256k1"/> library instance</param>
+ /// <param name="Context">A pointer to the initialized context instance</param>
+ public readonly record struct Secp256k1Context(LibSecp256k1 Lib, IntPtr Context) : IDisposable
+ {
+ /// <summary>
+ /// Randomizes the context with random data using the library's random source
+ /// </summary>
+ /// <returns>True if the context was successfully randomized, false otherwise</returns>
+ public unsafe readonly bool Randomize()
+ {
+ Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Randomze the context
+ byte* entropy = stackalloc byte[RandomBufferSize];
+
+ //Get random bytes
+ Lib.GetRandomBytes(new Span<byte>(entropy, RandomBufferSize));
+
+ //call native randomize method
+ bool result = Lib._randomize(Context, entropy) == 1;
+
+ //Zero the randomness buffer before returning to avoid leaking random data
+ MemoryUtil.InitializeBlock(entropy, RandomBufferSize);
+
+ return result;
+ }
+
+ internal unsafe readonly bool CreateKeyPair(KeyPair* keyPair, Secp256k1SecretKey* secretKey)
+ {
+ Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Create the keypair from the secret key
+ return Lib._createKeyPair(Context, keyPair, (byte*)secretKey) == 1;
+ }
+
+ /// <summary>
+ /// Releases the context instance and frees the memory
+ /// </summary>
+ public readonly void Dispose()
+ {
+ if (Context != IntPtr.Zero)
+ {
+ //Free the context
+ Lib._destroy(Context);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs b/lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs
new file mode 100644
index 0000000..35734ae
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs
@@ -0,0 +1,58 @@
+// 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.Memory;
+
+namespace NVault.Crypto.Secp256k1
+{
+ /// <summary>
+ /// Represents a Secp256k1 secret key, the size is fixed, and should use
+ /// the sizeof() operator to get the size
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 32)]
+ public unsafe struct Secp256k1SecretKey
+ {
+ private fixed byte data[32];
+
+ /// <summary>
+ /// Implict cast to a span of raw bytes
+ /// </summary>
+ /// <param name="key">The secret key to cast</param>
+ public static implicit operator Span<byte>(Secp256k1SecretKey key) => new(key.data, 32);
+
+ /// <summary>
+ /// Casts the secret key span to a <see cref="Secp256k1SecretKey"/> via a structure copy
+ /// </summary>
+ /// <param name="key">The key data to copy</param>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static explicit operator Secp256k1SecretKey(ReadOnlySpan<byte> key) => FromSpan(key);
+
+ /// <summary>
+ /// Creates a new <see cref="Secp256k1SecretKey"/> from a span of bytes
+ /// by copying the bytes into the struct
+ /// </summary>
+ /// <param name="span">The secret key data to copy</param>
+ /// <returns>An initilaized <see cref="Secp256k1SecretKey"/></returns>
+ public static Secp256k1SecretKey FromSpan(ReadOnlySpan<byte> span)
+ {
+ Secp256k1SecretKey newKey = new();
+ MemoryUtil.CopyStruct(span, ref newKey);
+ return newKey;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs b/lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs
new file mode 100644
index 0000000..360de21
--- /dev/null
+++ b/lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs
@@ -0,0 +1,101 @@
+// 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 NVault.Crypto.Secp256k1
+{
+
+ /// <summary>
+ /// A wrapper class for an unmanaged random source that conforms to the <see cref="IRandomSource"/> interface
+ /// </summary>
+ public class UnmanagedRandomSource : VnDisposeable, IRandomSource
+ {
+ public const string METHOD_NAME = "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="METHOD_NAME"/>
+ /// </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.DangerousGetMethod<UnmanagedRandomSourceDelegate>(METHOD_NAME);
+
+ OwnsHandle = ownsHandle;
+ }
+
+ 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