// Copyright (C) 2024 Vaughn Nugent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using VNLib.Utils;
using VNLib.Utils.Extensions;
using VNLib.Utils.Memory;
using VNLib.Utils.Native;
using NCResult = System.Int64;
namespace VNLib.Utils.Cryptography.Noscrypt
{
///
/// Initializes the native library and provides access to the native functions
///
/// An existing noscrypt library handle
/// A value that indicates if the instance owns the library handle
public unsafe sealed class LibNoscrypt(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable
{
//Values that match the noscrypt.h header
public const int NC_SEC_KEY_SIZE = 32;
public const int NC_SEC_PUBKEY_SIZE = 32;
public const int NC_ENCRYPTION_NONCE_SIZE = 32;
public const int NC_PUBKEY_SIZE = 32;
public const int NC_SIGNATURE_SIZE = 64;
public const int NC_CONV_KEY_SIZE = 32;
public const int NC_MESSAGE_KEY_SIZE = 32;
public const int NC_HMAC_KEY_SIZE = 32;
public const int NC_ENCRYPTION_MAC_SIZE = 32;
public const int NC_CONVERSATION_KEY_SIZE = 32;
public const int CTX_ENTROPY_SIZE = 32;
public const uint NC_ENC_VERSION_NIP04 = 0x00000004u;
public const uint NC_ENC_VERSION_NIP44 = 0x00000002c;
public const NCResult NC_SUCCESS = 0;
public const byte E_NULL_PTR = 0x01;
public const byte E_INVALID_ARG = 0x02;
public const byte E_INVALID_CTX = 0x03;
public const byte E_ARGUMENT_OUT_OF_RANGE = 0x04;
public const byte E_OPERATION_FAILED = 0x05;
public const byte E_VERSION_NOT_SUPPORTED = 0x06;
private readonly FunctionTable _functions = FunctionTable.BuildFunctionTable(Library);
///
/// Gets a reference to the loaded function table for
/// the native library
///
internal ref readonly FunctionTable Functions
{
get
{
Check();
Library.ThrowIfClosed();
return ref _functions;
}
}
///
/// Gets a value that determines if the library has been released
///
internal bool IsClosed => Library.IsClosed || Library.IsInvalid;
///
/// Initialize a new NCContext for use. This may be done once at app startup
/// and is thread-safe for the rest of the application lifetime.
///
///
/// Initialization entropy buffer
/// The size of the buffer (must be 32 bytes)
/// The inialized context
///
///
///
public NCContext Initialize(IUnmangedHeap heap, ref readonly byte entropy32, int size)
{
ArgumentNullException.ThrowIfNull(heap);
//Entropy must be exactly 32 bytes
ArgumentOutOfRangeException.ThrowIfNotEqual(size, CTX_ENTROPY_SIZE);
//Get struct size
nuint ctxSize = Functions.NCGetContextStructSize.Invoke();
//Allocate the context with the struct alignment on a heap
IntPtr ctx = heap.Alloc(1, ctxSize, true);
try
{
NCResult result;
fixed (byte* p = &entropy32)
{
result = Functions.NCInitContext.Invoke(ctx, p);
}
NCUtil.CheckResult(result, true);
Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}");
return new NCContext(ctx, heap, this);
}
catch
{
heap.Free(ref ctx);
throw;
}
}
///
/// Initialize a new NCContext for use. This may be done once at app startup
/// and is thread-safe for the rest of the application lifetime.
///
///
/// The 32byte random seed/nonce for the noscrypt context
/// The inialized context
///
///
///
public NCContext Initialize(IUnmangedHeap heap, ReadOnlySpan enropy32)
{
return Initialize(
heap,
ref MemoryMarshal.GetReference(enropy32),
enropy32.Length
);
}
///
/// Initializes a new NostrCrypto context wraper directly that owns the internal context.
/// This may be done once at app startup and is thread-safe for the rest of the
/// application lifetime.
///
///
/// The heap to allocate the context from
/// The random entropy data to initialize the context with
/// The library wrapper handle
public NostrCrypto InitializeCrypto(IUnmangedHeap heap, ReadOnlySpan entropy32)
{
ArgumentNullException.ThrowIfNull(heap);
//Create the crypto interface from the new context object
return new NostrCrypto(
context: Initialize(heap, entropy32),
ownsContext: true
);
}
///
protected override void Free()
{
if (OwnsHandle)
{
Library.Dispose();
Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}");
}
}
///
/// Loads the native library from the specified path and initializes the
/// function table for use.
///
/// The native library path or name to load
/// The search path options
/// The loaded library instance
public static LibNoscrypt Load(string path, DllImportSearchPath search)
{
//Load the native library
SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search);
Trace.WriteLine($"Loaded noscrypt library 0x{handle.DangerousGetHandle():x} from {path}");
//Create the wrapper
return new LibNoscrypt(handle, true);
}
///
/// Loads the native library from the specified path and initializes the
/// function table for use.
///
/// The native library path or name to load
/// The loaded library instance
public static LibNoscrypt Load(string path) => Load(path, DllImportSearchPath.SafeDirectories);
}
}