From 44044eb0fb28b774773e3284fd147c91d59d64e3 Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 18 Oct 2024 22:10:17 -0400 Subject: refactor: Wire up unit testing and refactor c# api --- wrappers/dotnet/Taskfile.yaml | 60 ----- .../VNLib.Utils.Cryptography.Noscrypt/.runsettings | 8 + .../VNLib.Utils.Cryptography.Noscrypt/README.md | 4 + .../Taskfile.yaml | 81 ++++++ .../src/Encryption/NCCipherUtil.cs | 171 +++++++++++++ .../src/Encryption/NCUtilCipher.cs | 128 ---------- .../src/Encryption/NoscryptCipher.cs | 227 ----------------- .../src/Encryption/NoscryptMessageCipher.cs | 256 +++++++++++++++++++ .../src/INostrCrypto.cs | 88 ------- .../src/NCKeyUtil.cs | 185 ++++++++++++++ .../src/NCUtil.cs | 193 --------------- .../src/NoscryptExtensions.cs | 75 ------ .../src/NoscryptLibrary.cs | 56 ++--- .../src/NoscryptSigner.cs | 133 ---------- .../src/NostrCrypto.cs | 167 ------------- .../src/Singatures/NCSignatureUtil.cs | 175 +++++++++++++ .../src/Singatures/NoscryptSigner.cs | 167 +++++++++++++ .../src/VNLib.Utils.Cryptography.Noscrypt.csproj | 24 +- .../src/internal/FunctionTable.cs | 2 +- .../src/internal/NCUtil.cs | 104 ++++++++ .../tests/LibNoscryptTests.cs | 272 +++++++-------------- .../tests/NVault.Crypto.NoscryptTests.csproj | 32 --- .../tests/NoscryptVectorTests.cs | 185 ++++++++++++++ .../VNLib.Utils.Cryptography.NoscryptTests.csproj | 30 +++ 24 files changed, 1494 insertions(+), 1329 deletions(-) delete mode 100644 wrappers/dotnet/Taskfile.yaml create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/.runsettings create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/README.md create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/Taskfile.yaml create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCCipherUtil.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCUtilCipher.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptCipher.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptMessageCipher.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCKeyUtil.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NCSignatureUtil.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NoscryptSigner.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCUtil.cs delete mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs create mode 100644 wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj (limited to 'wrappers/dotnet') diff --git a/wrappers/dotnet/Taskfile.yaml b/wrappers/dotnet/Taskfile.yaml deleted file mode 100644 index 9690e07..0000000 --- a/wrappers/dotnet/Taskfile.yaml +++ /dev/null @@ -1,60 +0,0 @@ -#Builds c# libraries for produc - -version: '3' - -vars: - INT_DIR: '{{.SCRATCH_DIR}}/obj/{{.MODULE_NAME}}/' - MS_ARGS: '/p:RunAnalyzersDuringBuild=false /p:IntermediateOutputPath="{{.INT_DIR}}" /p:UseCommonOutputDirectory=true /p:BuildInParallel=true /p:MultiProcessorCompilation=true /p:ErrorOnDuplicatePublishOutputFiles=false' - PACK_OUT: '{{.OUTPUT_DIR}}/{{.HEAD_SHA}}/pkg' - -tasks: - -#called by build pipeline to build module - build: - dir: '{{.USER_WORKING_DIR}}' - cmds: - - echo "building module {{.MODULE_NAME}}" - - #build debug mode first - - task: build_debug - - task: build_release - - publish: - dir: '{{.USER_WORKING_DIR}}' - cmds: - - #push packages to the sleet feed (feed path is vnbuild global) - - sleet push "{{.PACK_OUT}}/debug/" --source debug --config "{{.SLEET_CONFIG_PATH}}" --force - - sleet push "{{.PACK_OUT}}/release/" --source release --config "{{.SLEET_CONFIG_PATH}}" --force - -#called by build pipeline to clean module - clean: - dir: '{{.USER_WORKING_DIR}}' - cmds: - #clean solution - - dotnet clean /p:BuildInParallel=true /p:MultiProcessorCompilation=true - - for: [ obj/, bin/ ] - cmd: powershell rm -Recurse -Force "{{.ITEM}}" - -#Build tasks that use the solution file to build the module - build_debug: - dir: '{{.USER_WORKING_DIR}}' - internal: true - cmds: - - cd {{.MODULE_DIR}} && dotnet publish -c debug {{.MS_ARGS}} - - cd {{.MODULE_DIR}} && dotnet pack -c debug {{.MS_ARGS}} -o "{{.PACK_OUT}}/debug/" - - build_release: - dir: '{{.USER_WORKING_DIR}}' - internal: true - cmds: - - cd {{.MODULE_DIR}} && dotnet publish -c release {{.MS_ARGS}} - - cd {{.MODULE_DIR}} && dotnet pack -c release {{.MS_ARGS}} -o "{{.PACK_OUT}}/release/" - - - packsource: - dir: '{{.USER_WORKING_DIR}}' - internal: true - cmds: - #copy source code to target - - powershell -Command "Get-ChildItem -Include *.cs,*.csproj -Recurse | Where { \$_.FullName -notlike '*\obj\*' -and \$_.FullName -notlike '*\bin\*' } | Resolve-Path -Relative | tar --files-from - -czf '{{.TARGET}}/src.tgz'" \ No newline at end of file diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/.runsettings b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/.runsettings new file mode 100644 index 0000000..bbc95aa --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/.runsettings @@ -0,0 +1,8 @@ + + + + + ../../../../../../../build/windows/debug/noscrypt.dll + + + \ No newline at end of file diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/README.md b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/README.md new file mode 100644 index 0000000..34f68bf --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/README.md @@ -0,0 +1,4 @@ + +# VNLib.Utils.Cryptography.Noscrypt + +_A dotnet C# warpper library for noscrypt which is part of the VNLib collection of libraries_ \ No newline at end of file diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/Taskfile.yaml b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/Taskfile.yaml new file mode 100644 index 0000000..dfb739f --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/Taskfile.yaml @@ -0,0 +1,81 @@ +#Builds c# libraries for produc + +version: '3' + +vars: + TARGET_FRAMEWORK: '{{ .TARGET_FRAMEWORK | default "net8.0" }}' + +tasks: + +#called by build pipeline to build module + build: + dir: '{{ .USER_WORKING_DIR }}' + vars: + PACK_OUT: '{{ .OUTPUT_DIR }}/{{ .HEAD_SHA }}/pkg' + MS_ARGS: + /p:RunAnalyzersDuringBuild=false + /p:UseCommonOutputDirectory=true + /p:BuildInParallel=true + /p:MultiProcessorCompilation=true + /p:ErrorOnDuplicatePublishOutputFiles=false + cmds: + - cmd: echo "building project {{ .PROJECT_NAME }}" + silent: true + + #updates the project versions for all inlcuded .NET projects + - cmd: dotnet-gitversion.exe /updateprojectfiles + + - cmd: dotnet publish -c debug {{ .MS_ARGS }} + - cmd: dotnet publish -c release {{ .MS_ARGS }} + + #create nuget packages and write them directly to the output directory + - cmd: dotnet pack {{ .MS_ARGS }} -c debug -o "{{ .PACK_OUT }}/debug/" + - cmd: dotnet pack {{ .MS_ARGS }} -c release -o "{{ .PACK_OUT }}/release/" + + postbuild_success: + deps: + - task: pack_source + - task: pack_artifacts + vars: { CONFIG: 'debug' } + - task: pack_artifacts + vars: { CONFIG: 'release' } + + cmds: + - cmd: echo 'artifacts packaged' + silent: true + + pack_artifacts: + dir: '{{ .USER_WORKING_DIR }}' + internal: true + vars: + SOURCE: 'bin/{{ .CONFIG }}/{{ .TARGET_FRAMEWORK }}/publish' + TARGET: '{{ .USER_WORKING_DIR }}/bin/{{ .CONFIG }}.tgz' + cmds: + - cmd: cd {{ .SOURCE }} && tar -czf '{{ lower .TARGET }}' . + + pack_source: + dir: '{{ .USER_WORKING_DIR }}' + internal: true + vars: + TARGET: '{{ .USER_WORKING_DIR }}/bin' + INCLUDES: + src + tests + README.md + Taskfile.yaml + EXCLUDES: + --exclude='*obj/' + --exclude='*bin/' + cmds: + #copy source code to target + - cmd: cd .. && tar {{ .EXCLUDES }} -czf '{{ .TARGET }}/src.tgz' {{ .INCLUDES }} + +#called by build pipeline to clean module + clean: + dir: '{{ .USER_WORKING_DIR }}' + cmds: + #clean solution + - dotnet clean /p:BuildInParallel=true /p:MultiProcessorCompilation=true + - for: [ obj/, bin/ ] + cmd: '{{ if eq OS "windows" }}powershell rm -Recurse -Force{{ else }}rm -rf{{ end }} "{{ .ITEM }}"' + diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCCipherUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCCipherUtil.cs new file mode 100644 index 0000000..5dc2a94 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCCipherUtil.cs @@ -0,0 +1,171 @@ +// 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.Cryptography.Noscrypt.@internal; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt.Encryption +{ + public unsafe static class NCCipherUtil + { + /* + * This class wraps the low-level cipher functions provided by + * the Noscrypt utility side-car library. + */ + + internal static nint Alloc(NCContext ctx, uint version, uint flags) + { + nint cipher = GetTable(ctx).NCUtilCipherAlloc(version, flags); + + if (cipher == nint.Zero) + { + throw new OutOfMemoryException("Failed to allocate cipher context"); + } + + //Ensure flags are identical to those set during allocation + Debug.Assert(GetFlags(ctx, cipher) == flags); + + return cipher; + } + + internal static uint GetFlags(NCContext ctx, nint cipher) + { + NCResult result = GetTable(ctx).NCUtilCipherGetFlags(cipher); + + NCUtil.CheckResult(result, raiseOnFailure: true); + + return (uint)result; + } + + internal static void Free(NCContext ctx, nint cipher) => GetTable(ctx).NCUtilCipherFree(cipher); + + internal static int GetIvSize(NCContext ctx, nint cipher) + { + NCResult result = GetTable(ctx).NCUtilCipherGetIvSize(cipher); + + NCUtil.CheckResult(result, raiseOnFailure: true); + + return checked((int)result); + } + + internal static void SetProperty(NCContext ctx, nint cipher, uint property, ref readonly byte value, uint valueLen) + { + fixed (byte* valPtr = &value) + { + NCResult result = GetTable(ctx).NCUtilCipherSetProperty(cipher, property, valPtr, valueLen); + + NCUtil.CheckResult(result, raiseOnFailure: true); + } + } + + internal static uint GetOutputSize(NCContext ctx, nint cipher) + { + NCResult result = GetTable(ctx).NCUtilCipherGetOutputSize(cipher); + + NCUtil.CheckResult(result, raiseOnFailure: true); + + return (uint)result; + } + + internal static uint ReadOutput(NCContext ctx, nint cipher, ref byte outputData, uint outLen) + { + fixed (byte* outPtr = &outputData) + { + NCResult result = GetTable(ctx).NCUtilCipherReadOutput(cipher, outPtr, outLen); + + NCUtil.CheckResult(result, raiseOnFailure: true); + + return (uint)result; + } + } + + internal static void InitCipher(NCContext ctx, nint cipher, byte* inputPtr, uint inputSize) + { + NCResult result = GetTable(ctx).NCUtilCipherInit(cipher, inputPtr, inputSize); + + NCUtil.CheckResult(result, raiseOnFailure: true); + } + + internal static void Update( + NCContext ctx, + nint cipher, + ref readonly NCSecretKey localKey, + ref readonly NCPublicKey remoteKey + ) + { + fixed (NCSecretKey* sk = &localKey) + fixed (NCPublicKey* pk = &remoteKey) + { + NCResult result = GetTable(ctx).NCUtilCipherUpdate( + cipher: cipher, + libContext: ctx.DangerousGetHandle(), + secKey: sk, + pubKey: pk + ); + + NCUtil.CheckResult(result, raiseOnFailure: true); + } + } + + +#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( + NCContext ctx, + ref readonly NCSecretKey localKey, + ref readonly NCPublicKey remoteKey, + Span conversationKeyOut32 + ) + { + ArgumentNullException.ThrowIfNull(ctx); + ArgumentOutOfRangeException.ThrowIfNotEqual( + conversationKeyOut32.Length, + NC_CONVERSATION_KEY_SIZE, + nameof(conversationKeyOut32) + ); + + fixed (NCSecretKey* sk = &localKey) + fixed (NCPublicKey* pk = &remoteKey) + fixed(byte* convKey32Ptr = &MemoryMarshal.GetReference(conversationKeyOut32)) + { + NCResult result = GetTable(ctx).NCGetConversationKey( + ctx: ctx.DangerousGetHandle(), + sk, + pk, + convKey32Ptr + ); + + NCUtil.CheckResult(result, raiseOnFailure: true); + } + } +#endif + + private static ref readonly FunctionTable GetTable(NCContext ctx) + => ref ctx.Library.Functions; + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCUtilCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCUtilCipher.cs deleted file mode 100644 index 72bb75a..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCUtilCipher.cs +++ /dev/null @@ -1,128 +0,0 @@ -// 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 VNLib.Utils.Cryptography.Noscrypt.@internal; - -using NCResult = System.Int64; - -namespace VNLib.Utils.Cryptography.Noscrypt.Encryption -{ - internal static class NCUtilCipher - { - /* - * This class wraps the low-level cipher functions provided by - * the Noscrypt utility side-car library. - */ - - public static nint Alloc(NCContext ctx, uint version, uint flags) - { - nint cipher = GetTable(ctx).NCUtilCipherAlloc(version, flags); - - if (cipher == nint.Zero) - { - throw new OutOfMemoryException("Failed to allocate cipher context"); - } - - //Ensure flags are identical to those set during allocation - Debug.Assert(GetFlags(ctx, cipher) == flags); - - return cipher; - } - - public static uint GetFlags(NCContext ctx, nint cipher) - { - NCResult result = GetTable(ctx).NCUtilCipherGetFlags(cipher); - - NCUtil.CheckResult(result, raiseOnFailure: true); - - return (uint)result; - } - - public static void Free(NCContext ctx, nint cipher) => GetTable(ctx).NCUtilCipherFree(cipher); - - public static int GetIvSize(NCContext ctx, nint cipher) - { - NCResult result = GetTable(ctx).NCUtilCipherGetIvSize(cipher); - - NCUtil.CheckResult(result, raiseOnFailure: true); - - return checked((int)result); - } - - public static unsafe void SetProperty(NCContext ctx, nint cipher, uint property, ref readonly byte value, uint valueLen) - { - fixed (byte* valPtr = &value) - { - NCResult result = GetTable(ctx).NCUtilCipherSetProperty(cipher, property, valPtr, valueLen); - - NCUtil.CheckResult(result, raiseOnFailure: true); - } - } - - public static uint GetOutputSize(NCContext ctx, nint cipher) - { - NCResult result = GetTable(ctx).NCUtilCipherGetOutputSize(cipher); - - NCUtil.CheckResult(result, raiseOnFailure: true); - - return (uint)result; - } - - public static unsafe uint ReadOutput(NCContext ctx, nint cipher, ref byte outputData, uint outLen) - { - fixed (byte* outPtr = &outputData) - { - NCResult result = GetTable(ctx).NCUtilCipherReadOutput(cipher, outPtr, outLen); - - NCUtil.CheckResult(result, raiseOnFailure: true); - - return (uint)result; - } - } - - public static unsafe void InitCipher(NCContext ctx, nint cipher, byte* inputPtr, uint inputSize) - { - NCResult result = GetTable(ctx).NCUtilCipherInit(cipher, inputPtr, inputSize); - - NCUtil.CheckResult(result, raiseOnFailure: true); - } - - public static unsafe void Update( - NCContext ctx, - nint cipher, - ref readonly NCSecretKey localKey, - ref readonly NCPublicKey remoteKey - ) - { - fixed (NCSecretKey* sk = &localKey) - fixed (NCPublicKey* pk = &remoteKey) - { - NCResult result = GetTable(ctx).NCUtilCipherUpdate( - cipher: cipher, - libContext: ctx.DangerousGetHandle(), - secKey: sk, - pubKey: pk - ); - - NCUtil.CheckResult(result, raiseOnFailure: true); - } - } - - private static ref readonly FunctionTable GetTable(NCContext ctx) => ref ctx.Library.Functions; - } - -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptCipher.cs deleted file mode 100644 index b30ea44..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptCipher.cs +++ /dev/null @@ -1,227 +0,0 @@ -// 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.Threading; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using VNLib.Utils.Memory; -using VNLib.Utils.Cryptography.Noscrypt.Random; -using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; - -namespace VNLib.Utils.Cryptography.Noscrypt.Encryption -{ - /// - /// The noscrypt util cipher wapper - /// - /// - /// Cipher creation flags - /// The cipher specification version - public sealed class NoscryptCipher(NCContext ctx, NoscryptCipherVersion version, NoscryptCipherFlags flags) : VnDisposeable - { - private IMemoryHandle? _ivBuffer; - private readonly nint _cipher = NCUtilCipher.Alloc(ctx, (uint)version, (uint)flags); - - /// - /// The cipher standard version used by this instance - /// - public NoscryptCipherVersion Version => version; - - /// - /// Gets the flags set for the cipher instance - /// - public uint GetFlags() => NCUtilCipher.GetFlags(ctx, _cipher); - - /// - /// Gets the cipher's initilaization vector size (or nonce) - /// - /// The size of the IV in bytes - public int GetIvSize() => NCUtilCipher.GetIvSize(ctx, _cipher); - - /// - /// Gets the internal heap buffer that holds the cipher's initalization - /// vector. - /// - /// The mutable span of the cipher's IV buffer - public Span IvBuffer - { - get => LazyInitializer.EnsureInitialized(ref _ivBuffer, AllocIvBuffer).Span; - } - - /// - /// Sets the cipher's initialization vector to a random value using - /// the specified random source - /// - /// The random source - public void SetRandomIv(IRandomSource rng) - { - ArgumentNullException.ThrowIfNull(rng); - rng.GetRandomBytes(IvBuffer); - } - - /// - /// Performs the cipher operation on the input data using the specified - /// local and remote keys. - /// - /// The secret key of the local user - /// The public key of the remote user - /// A pointer to the first byte in the buffer sequence - /// The size of the input buffer in bytes - /// - /// - /// If the flag is - /// set, this function may be considered independent and called repeatedly. - /// - public unsafe void Update( - ref readonly NCSecretKey localKey, - ref readonly NCPublicKey remoteKey, - ref readonly byte inputData, - uint inputSize - ) - { - if (Unsafe.IsNullRef(in localKey)) - { - throw new ArgumentNullException(nameof(localKey)); - } - - if (Unsafe.IsNullRef(in remoteKey)) - { - throw new ArgumentNullException(nameof(remoteKey)); - } - - if (Unsafe.IsNullRef(in inputData)) - { - throw new ArgumentNullException(nameof(inputData)); - } - - /* - * Initializing the cipher requires the context holding a pointer - * to the input data, so it has to be pinned in a fixed statment - * for the duration of the update operation. - * - * So init and update must be done as an atomic operation. - * - * If ciphers have the Reusable flag set this function may be called - * repeatedly. The results of this operation can be considered - * independent. - */ - - fixed (byte* inputPtr = &inputData) - { - NCUtilCipher.InitCipher(ctx, _cipher, inputPtr, inputSize); - - NCUtilCipher.Update(ctx, _cipher, in localKey, in remoteKey); - } - } - - /// - /// Performs the cipher operation on the input data using the specified - /// local and remote keys. - /// - /// The secret key of the local user - /// The public key of the remote user - /// The buffer sequence to read the input data from - /// - public void Update( - ref readonly NCSecretKey localKey, - ref readonly NCPublicKey remoteKey, - ReadOnlySpan input - ) - { - Update( - in localKey, - in remoteKey, - inputData: ref MemoryMarshal.GetReference(input), //If empty, null ref will throw - inputSize: (uint)input.Length - ); - } - - /// - /// Gets the size of the output buffer required to read the cipher output - /// - /// The size of the output in bytes - public int GetOutputSize() => checked((int)NCUtilCipher.GetOutputSize(ctx, _cipher)); - - /// - /// Reads the output data from the cipher into the specified buffer - /// - /// A reference to the first byte in the buffer sequence - /// The size of the buffer sequence - /// The number of bytes written to the buffer - /// - public int ReadOutput(ref byte outputData, int size) - { - ArgumentOutOfRangeException.ThrowIfLessThan(size, GetOutputSize()); - - return checked((int)NCUtilCipher.ReadOutput(ctx, _cipher, ref outputData, (uint)size)); - } - - /// - /// Reads the output data from the cipher into the specified buffer - /// - /// The buffer sequence to write output data to - /// The number of bytes written to the buffer - /// - public int ReadOutput(Span buffer) - { - return ReadOutput( - ref MemoryMarshal.GetReference(buffer), - buffer.Length - ); - } - - private IMemoryHandle AllocIvBuffer() - { - //Use the context heap to allocate the internal iv buffer - MemoryHandle buffer = MemoryUtil.SafeAlloc(ctx.Heap, GetIvSize(), zero: true); - - try - { - /* - * Assign the buffer reference to the cipher context - * - * NOTE: This pointer will be held as long as the cipher - * context is allocated. So the buffer must be held until - * the cipher is freed. Because of this an umnanaged heap - * buffer is required so we don't need to pin managed memory - * nor worry about the GC moving the buffer. - */ - NCUtilCipher.SetProperty( - ctx, - _cipher, - NC_ENC_SET_IV, - ref buffer.GetReference(), - (uint)buffer.Length - ); - } - catch - { - buffer.Dispose(); - throw; - } - - return buffer; - } - - /// - protected override void Free() - { - NCUtilCipher.Free(ctx, _cipher); - _ivBuffer?.Dispose(); - } - } - -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptMessageCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptMessageCipher.cs new file mode 100644 index 0000000..e44addf --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptMessageCipher.cs @@ -0,0 +1,256 @@ +// 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.Threading; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +using VNLib.Utils.Memory; +using VNLib.Utils.Cryptography.Noscrypt.Random; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt.Encryption +{ + /// + /// The noscrypt util cipher wapper + /// + /// A reference to the library context object + /// A pointer to an existing cipher context that this instance owns + /// The cipher specification version + public class NoscryptMessageCipher : SafeHandleMinusOneIsInvalid + { + private readonly NCContext _context; + private readonly NoscryptCipherVersion _version; + private IMemoryHandle? _ivBuffer; + + private NoscryptMessageCipher(NCContext ctx, nint cipher, NoscryptCipherVersion version) + : base(ownsHandle: true) + { + SetHandle(cipher); + _context = ctx; + _version = version; + } + + /// + /// Allocates and initializes a new cipher instance using the specified + /// + /// A reference to the noscrypt library context + /// The encryption standard to use + /// The raw cipher flags to the pass to the cipher initialization function + /// A new instance + public static NoscryptMessageCipher Create(NCContext context, NoscryptCipherVersion version, NoscryptCipherFlags flags) + { + return new( + context, + NCCipherUtil.Alloc(context, (uint)version, (uint)flags), + version + ); + } + + /// + /// The cipher standard version used by this instance + /// + public NoscryptCipherVersion Version => _version; + + /// + /// Gets the flags set for the cipher instance + /// + public uint GetFlags() => NCCipherUtil.GetFlags(_context, handle); + + /// + /// Gets the cipher's initilaization vector size (or nonce) + /// + /// The size of the IV in bytes + public int GetIvSize() => NCCipherUtil.GetIvSize(_context, handle); + + /// + /// Gets the internal heap buffer that holds the cipher's initalization + /// vector. + /// + /// The mutable span of the cipher's IV buffer + public Span IvBuffer + { + get => LazyInitializer.EnsureInitialized(ref _ivBuffer, AllocIvBuffer).Span; + } + + /// + /// Sets the cipher's initialization vector to a random value using + /// the specified random source + /// + /// The random source + public void SetRandomIv(IRandomSource rng) + { + ArgumentNullException.ThrowIfNull(rng); + rng.GetRandomBytes(IvBuffer); + } + + /// + /// Performs the cipher operation on the input data using the specified + /// local and remote keys. + /// + /// The secret key of the local user + /// The public key of the remote user + /// A pointer to the first byte in the buffer sequence + /// The size of the input buffer in bytes + /// + /// + /// If the flag is + /// set, this function may be considered independent and called repeatedly. + /// + public unsafe void Update( + ref readonly NCSecretKey localKey, + ref readonly NCPublicKey remoteKey, + ref readonly byte inputData, + uint inputSize + ) + { + if (Unsafe.IsNullRef(in localKey)) + { + throw new ArgumentNullException(nameof(localKey)); + } + + if (Unsafe.IsNullRef(in remoteKey)) + { + throw new ArgumentNullException(nameof(remoteKey)); + } + + if (Unsafe.IsNullRef(in inputData)) + { + throw new ArgumentNullException(nameof(inputData)); + } + + /* + * Initializing the cipher requires the context holding a pointer + * to the input data, so it has to be pinned in a fixed statment + * for the duration of the update operation. + * + * So init and update must be done as an atomic operation. + * + * If ciphers have the Reusable flag set this function may be called + * repeatedly. The results of this operation can be considered + * independent. + */ + + fixed (byte* inputPtr = &inputData) + { + NCCipherUtil.InitCipher(_context, handle, inputPtr, inputSize); + + NCCipherUtil.Update(_context, handle, in localKey, in remoteKey); + } + } + + /// + /// Performs the cipher operation on the input data using the specified + /// local and remote keys. + /// + /// The secret key of the local user + /// The public key of the remote user + /// The buffer sequence to read the input data from + /// + public void Update( + ref readonly NCSecretKey localKey, + ref readonly NCPublicKey remoteKey, + ReadOnlySpan input + ) + { + Update( + in localKey, + in remoteKey, + inputData: ref MemoryMarshal.GetReference(input), //If empty, null ref will throw + inputSize: (uint)input.Length + ); + } + + /// + /// Gets the size of the output buffer required to read the cipher output + /// + /// The size of the output in bytes + public int GetOutputSize() => checked((int)NCCipherUtil.GetOutputSize(_context, handle)); + + /// + /// Reads the output data from the cipher into the specified buffer + /// + /// A reference to the first byte in the buffer sequence + /// The size of the buffer sequence + /// The number of bytes written to the buffer + /// + public int ReadOutput(ref byte outputData, int size) + { + ArgumentOutOfRangeException.ThrowIfLessThan(size, GetOutputSize()); + + return checked((int)NCCipherUtil.ReadOutput(_context, handle, ref outputData, (uint)size)); + } + + /// + /// Reads the output data from the cipher into the specified buffer + /// + /// The buffer sequence to write output data to + /// The number of bytes written to the buffer + /// + public int ReadOutput(Span buffer) + { + return ReadOutput( + ref MemoryMarshal.GetReference(buffer), + buffer.Length + ); + } + + private IMemoryHandle AllocIvBuffer() + { + //Use the context heap to allocate the internal iv buffer + MemoryHandle buffer = MemoryUtil.SafeAlloc(_context.Heap, GetIvSize(), zero: true); + + try + { + /* + * Assign the buffer reference to the cipher context + * + * NOTE: This pointer will be held as long as the cipher + * context is allocated. So the buffer must be held until + * the cipher is freed. Because of this an umnanaged heap + * buffer is required so we don't need to pin managed memory + * nor worry about the GC moving the buffer. + */ + NCCipherUtil.SetProperty( + _context, + DangerousGetHandle(), + NC_ENC_SET_IV, + ref buffer.GetReference(), + (uint)buffer.Length + ); + } + catch + { + buffer.Dispose(); + throw; + } + + return buffer; + } + + /// + protected override bool ReleaseHandle() + { + NCCipherUtil.Free(_context, handle); + _ivBuffer?.Dispose(); + + return true; + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs deleted file mode 100644 index 9b4d36c..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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.Cryptography.Noscrypt.Encryption; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - public interface INostrCrypto - { - - /// - /// Gets a nostr public key from a secret key. - /// - /// A reference to the secret key to get the public key from - /// A reference to the public key structure to write the recovered key to - /// - /// - void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey); - - /// - /// Validates a secret key is in a valid format. - /// - /// A readonly reference to key structure to validate - /// True if the key is consiered valid against the secp256k1 curve - /// - /// - bool ValidateSecretKey(ref readonly NCSecretKey secretKey); - - /// - /// Allocates a new cipher instance with the supplied options. - /// - /// The cipher specification version - /// The cipher initialziation flags - /// The cipher instance - NoscryptCipher AllocCipher(NoscryptCipherVersion version, NoscryptCipherFlags flags); - - /// - /// Signs the supplied data with the secret key and random32 nonce, then writes - /// the message signature to the supplied sig64 buffer. - /// - /// The secret key used to sign the message - /// A highly secure random nonce used to seed the signature - /// A pointer to the first byte in the message to sign - /// The size of the message in bytes - /// A pointer to the first byte of a 64 byte buffer used to write the message signature to - /// - /// - void SignData( - ref readonly NCSecretKey secretKey, - ref readonly byte random32, - ref readonly byte data, - uint dataSize, - ref byte sig64 - ); - - /// - /// Performs cryptographic verification of the supplied data - /// against the supplied public key. - /// - /// The signer's public key - /// A pointer to the first byte in the message to sign - /// The number of bytes in the message - /// A pointer to the signature buffer - /// True if the signature could be verified against the public key. False otherwise - /// - /// - bool VerifyData( - ref readonly NCPublicKey pubKey, - ref readonly byte data, - uint dataSize, - ref readonly byte sig64 - ); - } -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCKeyUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCKeyUtil.cs new file mode 100644 index 0000000..50e1c9a --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCKeyUtil.cs @@ -0,0 +1,185 @@ +// 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 System.Runtime.CompilerServices; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Cryptography.Noscrypt.@internal; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// + /// Contains utility methods for working with nostr keys + /// + public static unsafe class NCKeyUtil + { + /// + /// 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.ThrowIfLessThan(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.ThrowIfLessThan(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.ThrowIfLessThan(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.ThrowIfLessThan(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As(ref asBytes); + } + + /// + /// Gets a nostr public key from a secret key. + /// + /// A reference to the secret key to get the public key from + /// A reference to the public key structure to write the recovered key to + /// + /// + public static void GetPublicKey( + NCContext context, + ref readonly NCSecretKey secretKey, + ref NCPublicKey publicKey + ) + { + Check(context); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + { + NCResult result = GetTable(context).NCGetPublicKey( + context.DangerousGetHandle(), + pSecKey, + pPubKey + ); + + NCUtil.CheckResult(result, raiseOnFailure: true); + } + } + + /// + /// Validates a secret key is in a valid format. + /// + /// A readonly reference to key structure to validate + /// True if the key is consiered valid against the secp256k1 curve + /// + /// + public static bool ValidateSecretKey( + NCContext context, + ref readonly NCSecretKey secretKey + ) + { + Check(context); + + fixed (NCSecretKey* pSecKey = &secretKey) + { + NCResult result = GetTable(context).NCValidateSecretKey( + context.DangerousGetHandle(), + pSecKey + ); + + NCUtil.CheckResult(result, raiseOnFailure: false); + + return result == NC_SUCCESS; + } + } + + private static void Check(NCContext? context) + { + ArgumentNullException.ThrowIfNull(context); + context.ThrowIfClosed(); + } + + private static ref readonly FunctionTable GetTable(NCContext ctx) + => ref ctx.Library.Functions; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs deleted file mode 100644 index 307bbc1..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs +++ /dev/null @@ -1,193 +0,0 @@ -// 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 VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; - -using NCResult = System.Int64; - -namespace VNLib.Utils.Cryptography.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.ThrowIfLessThan(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.ThrowIfLessThan(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.ThrowIfLessThan(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.ThrowIfLessThan(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, 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 - NCErrorCodes errorCode = (NCErrorCodes)(asPositive & 0xFF); - byte argNumber = (byte)((asPositive >> 8) & 0xFF); - - switch (errorCode) - { - case NCErrorCodes.E_NULL_PTR: - RaiseNullArgExceptionForArgumentNumber(argNumber); - break; - case NCErrorCodes.E_INVALID_ARG: - RaiseArgExceptionForArgumentNumber(argNumber); - break; - case NCErrorCodes.E_ARGUMENT_OUT_OF_RANGE: - RaiseOORExceptionForArgumentNumber(argNumber); - break; - case NCErrorCodes.E_INVALID_CTX: - throw new InvalidOperationException("The library context object is null or invalid"); - case NCErrorCodes.E_OPERATION_FAILED: - RaiseOperationFailedException(raiseOnFailure); - break; - case NCErrorCodes.E_VERSION_NOT_SUPPORTED: - throw new NotSupportedException("The requested version is not supported"); - - default: - if(raiseOnFailure) - { - throw new InvalidOperationException($"The operation failed with error, code: {errorCode} for arugment {argNumber:x}"); - } - break; - } - } - - private static void RaiseOperationFailedException(bool raise) - { - if (raise) - { - 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/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs deleted file mode 100644 index e96ff96..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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 System.Runtime.InteropServices; - -using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - - public static class NoscryptExtensions - { - public static void SignData( - this INostrCrypto lib, - ref readonly NCSecretKey secKey, - ReadOnlySpan random32, - ReadOnlySpan data, - Span 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 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 index 35c6a49..2df63eb 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs @@ -37,6 +37,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt public unsafe sealed class NoscryptLibrary(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable { public const string NoscryptDefaultLibraryName = "noscrypt"; + public const string NoscryptDllPathEnvName = "NOSCRYPT_DLL_PATH"; //Constant values match the noscrypt.h header public const int NC_SEC_KEY_SIZE = 0x20; @@ -172,33 +173,16 @@ namespace VNLib.Utils.Cryptography.Noscrypt } /// - /// 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 - ); - } - - /// - /// 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. + /// 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 heap to allocate the context from - /// Random source used to generate context entropy - /// The library wrapper handle - public NostrCrypto InitializeCrypto(IUnmangedHeap heap, IRandomSource random) + /// + /// The 32byte random seed/nonce for the noscrypt context + /// The inialized context + /// + /// + /// + public NCContext Initialize(IUnmangedHeap heap, IRandomSource random) { ArgumentNullException.ThrowIfNull(random); @@ -206,13 +190,9 @@ namespace VNLib.Utils.Cryptography.Noscrypt Span entropy = stackalloc byte[NC_CTX_ENTROPY_SIZE]; random.GetRandomBytes(entropy); - NostrCrypto nc = InitializeCrypto(heap, entropy); - - MemoryUtil.InitializeBlock(entropy); - - return nc; + return Initialize(heap, entropy); } - + /// protected override void Free() { @@ -256,6 +236,16 @@ namespace VNLib.Utils.Cryptography.Noscrypt /// /// The loaded library instance /// - public static NoscryptLibrary LoadDefault() => Load(NoscryptDefaultLibraryName, DllImportSearchPath.SafeDirectories); + public static NoscryptLibrary LoadDefault() + { + string? libPath = Environment.GetEnvironmentVariable(NoscryptDllPathEnvName); + libPath ??= NoscryptDefaultLibraryName; + + Console.WriteLine("Loading library {0}", libPath); + + libPath = libPath.Replace("\"", ""); + + return Load(libPath); + } } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs deleted file mode 100644 index c81790b..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs +++ /dev/null @@ -1,133 +0,0 @@ -// 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.Cryptography.Noscrypt.Random; -using VNLib.Utils.Extensions; -using VNLib.Utils.Memory; - -using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - /// - /// A simple wrapper class to sign nostr message data using - /// the noscrypt library - /// - /// The noscrypt library instance - /// A random entropy pool used to source random data for signature entropy - public class NoscryptSigner(INostrCrypto noscrypt, IRandomSource random) - { - /// - /// Gets the size of the buffer required to hold the signature - /// - public static int SignatureBufferSize => NC_SIGNATURE_SIZE; - - /// - /// Signs a message using the specified private key and message data - /// - /// The hexadecimal private key used to sign the message - /// The message data to sign - /// A encoder used to convert the signature data to an encoded string - /// The string encoded nostr signature - /// - /// - public string SignData(string hexPrivateKey, ReadOnlySpan 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); - } - } - - /// - /// Signs a message using the specified secret key and message data - /// - /// The secret key data buffer - /// The message data to sign - /// A encoder used to convert the signature data to an encoded string - /// The string encoded nostr signature - /// - /// - public string SignData( - ReadOnlySpan secretKey, - ReadOnlySpan message, - INostrSignatureEncoder? format = null - ) - { - return SignData(in NCUtil.AsSecretKey(secretKey), message, format); - } - - /// - /// Signs a message using the specified secret key and message data - /// - /// A reference to the secret key structurer - /// The message data to sign - /// A encoder used to convert the signature data to an encoded string - /// The string encoded nostr signature - /// - /// - public string SignData( - ref readonly NCSecretKey secretkey, - ReadOnlySpan message, - INostrSignatureEncoder? format = null - ) - { - //Default to hex encoding because that is the default NIP-01 format - format ??= HexSignatureEncoder.Instance; - - Span sigBuffer = stackalloc byte[SignatureBufferSize]; - - SignData(message, sigBuffer); - - return format.GetString(sigBuffer); - } - - - /// - /// Signs a message using the specified secret key and message data - /// - /// A reference to the secret key structurer - /// The message data to sign - /// A buffer to write signature data to - /// - /// - public void SignData( - ref readonly NCSecretKey secretkey, - ReadOnlySpan data, - Span signature - ) - { - ArgumentOutOfRangeException.ThrowIfLessThan(signature.Length, NC_SIGNATURE_SIZE, nameof(signature)); - - //Signature generation required random entropy to be secure - Span 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 deleted file mode 100644 index 1e833d2..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs +++ /dev/null @@ -1,167 +0,0 @@ -// 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 System.Diagnostics.CodeAnalysis; - -using VNLib.Utils.Cryptography.Noscrypt.@internal; -using VNLib.Utils.Cryptography.Noscrypt.Encryption; -using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; - -using NCResult = System.Int64; - -namespace VNLib.Utils.Cryptography.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 NoscryptCipher AllocCipher(NoscryptCipherVersion version, NoscryptCipherFlags flags) => new (context, version, flags); - - /// - 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(result, true); - } - } - - /// - 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(result, true); - } - } - - /// - 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, false); - - return result == NC_SUCCESS; - } - } - - /// - 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(result, false); - - return result == NC_SUCCESS; - } - } - -#if DEBUG - - /// - /// DEBUG ONLY: Gets the conversation key for the supplied secret key and public key - /// - /// The sender's private key - /// The receiver's public key - /// A pointer to the 32byte buffer to write the conversation key to - 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(result, true); - } - } - -#endif - /// - 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/Singatures/NCSignatureUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NCSignatureUtil.cs new file mode 100644 index 0000000..2755ceb --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NCSignatureUtil.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.Runtime.InteropServices; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Cryptography.Noscrypt.@internal; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt.Singatures +{ + + /// + /// Contains utility methods for signing and verifying data using the noscrypt library + /// + public unsafe static class NCSignatureUtil + { + /// + /// Signs the data using the supplied secret key and + /// entropy pointer + /// + /// The initialized context memory to pass to the library + /// A reference to a structure containing the private key data + /// A pointer to a 32 byte buffer containing high entropy random data + /// A pointer to a buffer containing the data to sign + /// The size of the data buffer in bytes + /// A pointer to a 64 byte buffer to write signature data to + /// + public static void SignData( + NCContext context, + ref readonly NCSecretKey secretKey, + ref readonly byte random32, + ref readonly byte data, + uint dataSize, + ref byte sig64 + ) + { + Check(context); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (byte* pData = &data, pSig = &sig64, pRandom = &random32) + { + NCResult result = GetTable(context).NCSignData( + ctx: context.DangerousGetHandle(), + sk: pSecKey, + random32: pRandom, + data: pData, + dataSize, + sig64: pSig + ); + + NCUtil.CheckResult(result, raiseOnFailure: true); + } + } + + /// + /// Verifies signed data against the supplied public key + /// + /// The initialized context memory to pass to the library + /// A reference to a structure containing the public key data + /// A pointer to a buffer containing the data to verify + /// The size of the data buffer in bytes + /// A pointer to a 64 byte buffer to read signature data from + /// True if the signature was signed by the supplied public key, false otherwise + public static bool VerifyData( + NCContext context, + ref readonly NCPublicKey publicKey, + ref readonly byte data, + uint dataSize, + ref readonly byte sig64 + ) + { + Check(context); + + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pData = &data, pSig = &sig64) + { + NCResult result = GetTable(context).NCVerifyData( + context.DangerousGetHandle(), + pk: pPubKey, + data: pData, + dataSize, + sig64: pSig + ); + + NCUtil.CheckResult(result, false); + + return result == NC_SUCCESS; + } + } + + /// + /// Signs the data using the supplied secret key and + /// entropy pointer + /// + /// The initialized context memory to pass to the library + /// A reference to a structure containing the private key data + /// A pointer to a 32 byte buffer containing high entropy random data + /// A pointer to a buffer containing the data to sign + /// A pointer to a 64 byte buffer to write signature data to + /// + public static void SignData( + NCContext context, + ref readonly NCSecretKey secretKey, + ReadOnlySpan random32, + ReadOnlySpan data, + Span signatureBuffer + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signatureBuffer.Length, NC_SIGNATURE_SIZE, nameof(signatureBuffer)); + ArgumentOutOfRangeException.ThrowIfLessThan(random32.Length, 32, nameof(random32)); + ArgumentOutOfRangeException.ThrowIfZero(data.Length, nameof(data)); + + SignData( + context, + secretKey: in secretKey, + random32: in MemoryMarshal.GetReference(random32), + data: in MemoryMarshal.GetReference(data), + dataSize: (uint)data.Length, + sig64: ref MemoryMarshal.GetReference(signatureBuffer) + ); + } + + /// + /// Verifies signed data against the supplied public key + /// + /// The initialized context memory to pass to the library + /// A reference to a structure containing the public key data + /// A pointer to a buffer containing the data to verify + /// A pointer to a 64 byte buffer to read signature data from + /// True if the signature was signed by the supplied public key, false otherwise + public static bool VerifyData( + NCContext context, + ref readonly NCPublicKey publicKey, + ReadOnlySpan data, + ReadOnlySpan signatureBuffer + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signatureBuffer.Length, NC_SIGNATURE_SIZE, nameof(signatureBuffer)); + ArgumentOutOfRangeException.ThrowIfZero(data.Length, nameof(data)); + + return VerifyData( + context, + publicKey: in publicKey, + data: in MemoryMarshal.GetReference(data), + dataSize: (uint)data.Length, + sig64: ref MemoryMarshal.GetReference(signatureBuffer) + ); + } + + private static void Check(NCContext? context) + { + ArgumentNullException.ThrowIfNull(context); + context.ThrowIfClosed(); + } + + private static ref readonly FunctionTable GetTable(NCContext ctx) + => ref ctx.Library.Functions; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NoscryptSigner.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NoscryptSigner.cs new file mode 100644 index 0000000..063d2c0 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NoscryptSigner.cs @@ -0,0 +1,167 @@ +// 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.Memory; +using VNLib.Utils.Extensions; +using VNLib.Utils.Cryptography.Noscrypt; +using VNLib.Utils.Cryptography.Noscrypt.Random; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt.Singatures +{ + + /// + /// A simple wrapper class to sign nostr message data using + /// the noscrypt library + /// + /// The noscrypt library instance + /// A random entropy pool used to source random data for signature entropy + public class NoscryptSigner(NCContext context, IRandomSource random) + { + /// + /// Gets the size of the buffer required to hold the signature + /// + public static int SignatureBufferSize => NC_SIGNATURE_SIZE; + + /// + /// Signs a message using the specified private key and message data + /// + /// The hexadecimal private key used to sign the message + /// The message data to sign + /// A encoder used to convert the signature data to an encoded string + /// The string encoded nostr signature + /// + /// + public string SignData(string hexPrivateKey, ReadOnlySpan 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); + } + } + + /// + /// Signs a message using the specified secret key and message data + /// + /// The secret key data buffer + /// The message data to sign + /// A encoder used to convert the signature data to an encoded string + /// The string encoded nostr signature + /// + /// + public string SignData( + ReadOnlySpan secretKey, + ReadOnlySpan message, + INostrSignatureEncoder? format = null + ) + { + return SignData( + in NCKeyUtil.AsSecretKey(secretKey), + message, + format + ); + } + + /// + /// Signs a message using the specified secret key and message data + /// + /// A reference to the secret key structurer + /// The message data to sign + /// A encoder used to convert the signature data to an encoded string + /// The string encoded nostr signature + /// + /// + public string SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan message, + INostrSignatureEncoder? format = null + ) + { + //Default to hex encoding because that is the default NIP-01 format + format ??= HexSignatureEncoder.Instance; + + Span sigBuffer = stackalloc byte[SignatureBufferSize]; + + SignData(message, sigBuffer); + + return format.GetString(sigBuffer); + } + + + /// + /// Signs a message using the specified secret key and message data + /// + /// A reference to the secret key structurer + /// The message data to sign + /// A buffer to write signature data to + /// + /// + public void SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan data, + Span signature + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signature.Length, NC_SIGNATURE_SIZE, nameof(signature)); + + //Signature generation required random entropy to be secure + Span entropy = stackalloc byte[NC_SIG_ENTROPY_SIZE]; + random.GetRandomBytes(entropy); + + NCSignatureUtil.SignData( + context, + in secretkey, + entropy, + data, + signature + ); + } + + public bool VerifyData( + ReadOnlySpan publicKey, + ReadOnlySpan data, + ReadOnlySpan sig + ) + { + return VerifyData( + in NCKeyUtil.AsPublicKey(publicKey), + data, + sig + ); + } + + public bool VerifyData( + ref readonly NCPublicKey pk, + ReadOnlySpan data, + ReadOnlySpan sig + ) + { + return NCSignatureUtil.VerifyData(context, in pk, data, sig); + } + } + +} 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 index 4d1868c..29a4b8f 100644 --- 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 @@ -1,5 +1,5 @@  - + net8.0 enable @@ -9,7 +9,7 @@ VNLib.Utils.Cryptography.Noscrypt VNLib.Utils.Cryptography.Noscrypt - + Vaughn Nugent Vaughn Nugent @@ -19,10 +19,22 @@ https://www.vaughnnugent.com/resources/software/modules/Noscrypt https://github.com/VnUgE/noscryot/tree/master/dotnet/VNLib.Utils.Cryptography.Noscrypt - + - - + + True + \ + + + True + \ + Always + - + + + + + + diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs index 0cda5e2..269ae4d 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs @@ -119,7 +119,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.@internal 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); + internal delegate NCResult NCVerifyDataDelegate(IntPtr ctx, NCPublicKey* pk, byte* data, uint dataSize, byte* sig64); [SafeMethodName("NCGetConversationKey")] internal delegate NCResult NCGetConversationKeyDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, byte* keyOut32); diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCUtil.cs new file mode 100644 index 0000000..16f46c6 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCUtil.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 . + +using System; +using System.Reflection; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt.@internal +{ + + public static class NCUtil + { + + + internal static void CheckResult(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 + NCErrorCodes errorCode = (NCErrorCodes)(asPositive & 0xFF); + byte argNumber = (byte)(asPositive >> 8 & 0xFF); + + switch (errorCode) + { + case NCErrorCodes.E_NULL_PTR: + RaiseNullArgExceptionForArgumentNumber(argNumber); + break; + case NCErrorCodes.E_INVALID_ARG: + RaiseArgExceptionForArgumentNumber(argNumber); + break; + case NCErrorCodes.E_ARGUMENT_OUT_OF_RANGE: + RaiseOORExceptionForArgumentNumber(argNumber); + break; + case NCErrorCodes.E_INVALID_CTX: + throw new InvalidOperationException("The library context object is null or invalid"); + case NCErrorCodes.E_OPERATION_FAILED: + RaiseOperationFailedException(raiseOnFailure); + break; + case NCErrorCodes.E_VERSION_NOT_SUPPORTED: + throw new NotSupportedException("The requested version is not supported"); + + default: + if (raiseOnFailure) + { + throw new InvalidOperationException($"The operation failed with error, code: {errorCode} for arugment {argNumber:x}"); + } + break; + } + } + + private static void RaiseOperationFailedException(bool raise) + { + if (raise) + { + 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/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs index ffa9cb6..b8d9623 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs @@ -1,25 +1,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Text; -using System.Text.Json; - using VNLib.Hashing; using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; -using VNLib.Utils.Cryptography.Noscrypt.Encryption; using VNLib.Utils.Cryptography.Noscrypt.Random; +using VNLib.Utils.Cryptography.Noscrypt.Singatures; namespace VNLib.Utils.Cryptography.Noscrypt.Tests { [TestClass()] public class LibNoscryptTests : IDisposable { - - const string NoscryptLibWinDebug = @"../../../../../../../build/windows/Debug/noscrypt.dll"; - const string NoscryptLinuxDebug = @"../../../../../../../build/linux/libnoscrypt.so"; - - //Keys generated using npx noskey package const string TestPrivateKeyHex = "98c642360e7163a66cee5d9a842b252345b6f3f3e21bd3b7635d5e6c20c7ea36"; const string TestPublicKeyHex = "0db15182c4ad3418b4fbab75304be7ade9cfa430a21c1c5320c9298f54ea5406"; @@ -27,56 +17,40 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests const string TestPrivateKeyHex2 = "3032cb8da355f9e72c9a94bbabae80ca99d3a38de1aed094b432a9fe3432e1f2"; const string TestPublicKeyHex2 = "421181660af5d39eb95e48a0a66c41ae393ba94ffeca94703ef81afbed724e5a"; - const string Nip44VectorTestFile = "nip44.vectors.json"; - #nullable disable private NoscryptLibrary _testLib; - private JsonDocument _testVectors; #nullable enable [TestInitialize] public void Initialize() { - _testLib = Environment.OSVersion.Platform switch - { - PlatformID.Win32NT => NoscryptLibrary.Load(NoscryptLibWinDebug), - PlatformID.Unix => NoscryptLibrary.Load(NoscryptLinuxDebug), - _ => throw new PlatformNotSupportedException() - }; - - _testVectors = JsonDocument.Parse(File.ReadAllText(Nip44VectorTestFile)); + _testLib = NoscryptLibrary.LoadDefault(); } - [TestMethod()] public void InitializeTest() { - //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); - //Init new context and interface - NCContext context = _testLib.Initialize(MemoryUtil.Shared, seed); - - using NostrCrypto crypto = new(context, true); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); } [TestMethod()] public void ValidateSecretKeyTest() { //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); ReadOnlySpan secretKey = RandomHash.GetRandomBytes(32); Span publicKey = stackalloc byte[32]; - using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); //validate the secret key - Assert.IsTrue(crypto.ValidateSecretKey(in NCUtil.AsSecretKey(secretKey))); + Assert.IsTrue(NCKeyUtil.ValidateSecretKey(context, in NCKeyUtil.AsSecretKey(secretKey))); //Generate the public key - crypto.GetPublicKey( - in NCUtil.AsSecretKey(secretKey), - ref NCUtil.AsPublicKey(publicKey) + NCKeyUtil.GetPublicKey( + context, + in NCKeyUtil.AsSecretKey(secretKey), + ref NCKeyUtil.AsPublicKey(publicKey) ); //Make sure the does not contain all zeros @@ -86,33 +60,31 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests [TestMethod()] public void TestGetPublicKey() { - //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); - - using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); //Test known key 1 TestKnownKeys( - crypto, + context, Convert.FromHexString(TestPrivateKeyHex), Convert.FromHexString(TestPublicKeyHex) ); //Test known key 2 TestKnownKeys( - crypto, + context, Convert.FromHexString(TestPrivateKeyHex2), Convert.FromHexString(TestPublicKeyHex2) ); - static void TestKnownKeys(NostrCrypto lib, ReadOnlySpan knownSec, ReadOnlySpan kownPub) + static void TestKnownKeys(NCContext context, ReadOnlySpan knownSec, ReadOnlySpan kownPub) { NCPublicKey pubKey; //Invoke test function - lib.GetPublicKey( - in NCUtil.AsSecretKey(knownSec), + NCKeyUtil.GetPublicKey( + context, + in NCKeyUtil.AsSecretKey(knownSec), ref pubKey ); @@ -125,176 +97,104 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Tests [TestMethod()] public void TestPublicApiArgValidations() { - //Random context seed - ReadOnlySpan seed = RandomHash.GetRandomBytes(32); - - using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + byte[] bin16 = new byte[16]; + byte[] bin32 = new byte[32]; + byte[] bin64 = new byte[64]; 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 NCSecretKey.NullRef)); + NCKeyUtil.ValidateSecretKey(context, in secKey); + Assert.ThrowsException(() => NCKeyUtil.ValidateSecretKey(null!, ref NCSecretKey.NullRef)); + Assert.ThrowsException(() => NCKeyUtil.ValidateSecretKey(context, ref NCSecretKey.NullRef)); //public key - Assert.ThrowsException(() => crypto.GetPublicKey(ref NCSecretKey.NullRef, ref pubKey)); - Assert.ThrowsException(() => crypto.GetPublicKey(in secKey, ref NCPublicKey.NullRef)); - } - - [TestMethod()] - public void CorrectEncryptionTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); - - using NoscryptCipher cipher = nc.AllocCipher(NoscryptCipherVersion.Nip44, NoscryptCipherFlags.EncryptDefault); - - using IMemoryHandle ctBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); - - foreach (EncryptionVector v in GetEncryptionVectors()) - { - - ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); - ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); - ReadOnlySpan plainText = Encoding.UTF8.GetBytes(v.plaintext); - ReadOnlySpan nonce = Convert.FromHexString(v.nonce); - ReadOnlySpan message = Convert.FromBase64String(v.payload); - - NCPublicKey pub2; - - //Recover public keys - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); - - //Assign existing nonce - nonce.CopyTo(cipher.IvBuffer); - - cipher.Update( - in NCUtil.AsSecretKey(secKey1), - in pub2, - plainText - ); - - Span outputBuffer = ctBuffer.AsSpan(0, cipher.GetOutputSize()); - - Assert.AreEqual(cipher.ReadOutput(outputBuffer), message.Length); - - //Make sure the cipher text matches the expected payload - if (!outputBuffer.SequenceEqual(message)) - { - Console.WriteLine($"Input data: {v.plaintext}"); - Console.WriteLine($" \n{Convert.ToHexString(outputBuffer)}\n{Convert.ToHexString(message)}"); - Assert.Fail($"Cipher text does not match expected message"); - } - } - } - - [TestMethod()] - public void CorrectDecryptionTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, NCFallbackRandom.Shared); - - using NoscryptCipher msgCipher = nc.AllocCipher(NoscryptCipherVersion.Nip44, NoscryptCipherFlags.DecryptDefault); - - using IMemoryHandle ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); - - foreach (EncryptionVector vector in GetEncryptionVectors()) - { - ReadOnlySpan secKey1 = Convert.FromHexString(vector.sec1); - ReadOnlySpan secKey2 = Convert.FromHexString(vector.sec2); - ReadOnlySpan expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); - ReadOnlySpan message = Convert.FromBase64String(vector.payload); - - NCPublicKey pub2 = default; + Assert.ThrowsException(() => NCKeyUtil.GetPublicKey(null!, in secKey, ref pubKey)); + Assert.ThrowsException(() => NCKeyUtil.GetPublicKey(context, ref NCSecretKey.NullRef, ref pubKey)); + Assert.ThrowsException(() => NCKeyUtil.GetPublicKey(context, in secKey, ref NCPublicKey.NullRef)); + + /* + * VERIFY DATA + */ + //Null context + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(null!, ref pubKey, bin32, bin64) + ); - //Recover public keys - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); + //Null pubkey + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref NCPublicKey.NullRef, bin32, bin64) + ); - //update performs the decryption operation (mac is also verified by default) - msgCipher.Update( - in NCUtil.AsSecretKey(secKey1), - in pub2, - message - ); + //No data buffer + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref pubKey, [], bin64) + ); - int outLen = msgCipher.GetOutputSize(); - Assert.IsTrue(outLen == expectedPt.Length); + //No signature + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref pubKey, bin32, []) + ); - Span plaintext = ptBuffer.AsSpan(0, outLen); + //Signature too small + Assert.ThrowsException(() => + NCSignatureUtil.VerifyData(context, ref pubKey, bin32, bin32) + ); - msgCipher.ReadOutput(plaintext); + /* + * SIGN DATA + */ - if (!plaintext.SequenceEqual(expectedPt)) - { - Console.WriteLine($"Input data: {vector.plaintext}"); - Console.WriteLine($" \n{Convert.ToHexString(plaintext)}\n{Convert.ToHexString(expectedPt)}"); - Assert.Fail("Decrypted data does not match expected plaintext"); - } - } - } + //Null context + Assert.ThrowsException(() => + NCSignatureUtil.SignData(null!, ref secKey, bin32, bin32, bin64) + ); + //Null secret key + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref NCSecretKey.NullRef, bin32, bin32, bin64) + ); - //Converstation key is only available in debug builds -#if DEBUG + //No entropy + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, [], bin32, bin64) + ); - [TestMethod()] - public void ConverstationKeyTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); - - Span convKeyOut = stackalloc byte[32]; + //No data + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, [], bin64) + ); - foreach (EncryptionVector v in GetEncryptionVectors()) - { - ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); - ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); - ReadOnlySpan conversationKey = Convert.FromHexString(v.conversation_key); + //No signature + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, bin32, []) + ); - NCPublicKey pubkey2 = default; - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pubkey2); + //Signature too small + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, bin32, bin32) + ); - nc.GetConverstationKey( - in NCUtil.AsSecretKey(secKey1), - in pubkey2, - convKeyOut - ); + //Entropy too small + Assert.ThrowsException(() => + NCSignatureUtil.SignData(context, ref secKey, bin16, bin32, bin32) + ); - Assert.IsTrue(conversationKey.SequenceEqual(convKeyOut)); + /* + * Cipher api + */ - MemoryUtil.InitializeBlock(convKeyOut); - } - } -#endif + NoscryptSigner signer = new(context, NCFallbackRandom.Shared); - private EncryptionVector[] GetEncryptionVectors() - { - return _testVectors.RootElement.GetProperty("v2") - .GetProperty("valid") - .GetProperty("encrypt_decrypt") - .EnumerateArray() - .Select(v => v.Deserialize()!) - .ToArray(); + } void IDisposable.Dispose() { _testLib.Dispose(); - _testVectors.Dispose(); GC.SuppressFinalize(this); } - - private sealed class EncryptionVector - { - public string sec1 { get; set; } = string.Empty; - - public string sec2 { get; set; } = string.Empty; - - public string nonce { get; set; } = string.Empty; - - public string plaintext { get; set; } = string.Empty; - - public string payload { get; set; } = string.Empty; - - public string conversation_key { get; set; } = string.Empty; - } } } diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj deleted file mode 100644 index 5917b15..0000000 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - Always - - - - diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs new file mode 100644 index 0000000..1a2f5d2 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NoscryptVectorTests.cs @@ -0,0 +1,185 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System; +using System.Text; +using System.Text.Json; +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; +using VNLib.Utils.Cryptography.Noscrypt.Encryption; +using VNLib.Utils.Cryptography.Noscrypt.Random; + +namespace VNLib.Utils.Cryptography.Noscrypt.Tests +{ + + [TestClass()] + public class NoscryptVectorTests : IDisposable + { + const string Nip44VectorTestFile = "nip44.vectors.json"; + +#nullable disable + private NoscryptLibrary _testLib; + private JsonDocument _testVectors; +#nullable enable + + [TestInitialize] + public void Initialize() + { + _testLib = NoscryptLibrary.LoadDefault(); + _testVectors = JsonDocument.Parse(File.ReadAllText(Nip44VectorTestFile)); + } + + [TestMethod()] + public void CorrectEncryptionTest() + { + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + using NoscryptMessageCipher cipher = NoscryptMessageCipher.Create(context, NoscryptCipherVersion.Nip44, NoscryptCipherFlags.EncryptDefault); + + using IMemoryHandle ctBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + + ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan plainText = Encoding.UTF8.GetBytes(v.plaintext); + ReadOnlySpan nonce = Convert.FromHexString(v.nonce); + ReadOnlySpan message = Convert.FromBase64String(v.payload); + + NCPublicKey pub2; + + //Recover public keys + NCKeyUtil.GetPublicKey(context, in NCKeyUtil.AsSecretKey(secKey2), ref pub2); + + //Assign existing nonce + nonce.CopyTo(cipher.IvBuffer); + + cipher.Update( + in NCKeyUtil.AsSecretKey(secKey1), + in pub2, + plainText + ); + + Span outputBuffer = ctBuffer.AsSpan(0, cipher.GetOutputSize()); + + Assert.AreEqual(cipher.ReadOutput(outputBuffer), message.Length); + + //Make sure the cipher text matches the expected payload + if (!outputBuffer.SequenceEqual(message)) + { + Console.WriteLine($"Input data: {v.plaintext}"); + Console.WriteLine($" \n{Convert.ToHexString(outputBuffer)}\n{Convert.ToHexString(message)}"); + Assert.Fail($"Cipher text does not match expected message"); + } + } + } + + [TestMethod()] + public void CorrectDecryptionTest() + { + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + using NoscryptMessageCipher msgCipher = NoscryptMessageCipher.Create(context, NoscryptCipherVersion.Nip44, NoscryptCipherFlags.DecryptDefault); + + using IMemoryHandle ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); + + foreach (EncryptionVector vector in GetEncryptionVectors()) + { + ReadOnlySpan secKey1 = Convert.FromHexString(vector.sec1); + ReadOnlySpan secKey2 = Convert.FromHexString(vector.sec2); + ReadOnlySpan expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); + ReadOnlySpan message = Convert.FromBase64String(vector.payload); + + NCPublicKey pub2 = default; + + //Recover public keys + NCKeyUtil.GetPublicKey(context, in NCKeyUtil.AsSecretKey(secKey2), ref pub2); + + //update performs the decryption operation (mac is also verified by default) + msgCipher.Update( + in NCKeyUtil.AsSecretKey(secKey1), + in pub2, + message + ); + + int outLen = msgCipher.GetOutputSize(); + Assert.IsTrue(outLen == expectedPt.Length); + + Span plaintext = ptBuffer.AsSpan(0, outLen); + + msgCipher.ReadOutput(plaintext); + + if (!plaintext.SequenceEqual(expectedPt)) + { + Console.WriteLine($"Input data: {vector.plaintext}"); + Console.WriteLine($" \n{Convert.ToHexString(plaintext)}\n{Convert.ToHexString(expectedPt)}"); + Assert.Fail("Decrypted data does not match expected plaintext"); + } + } + } + + + //Converstation key is only available in debug builds +#if DEBUG + + [TestMethod()] + public void ConverstationKeyTest() + { + using NCContext context = _testLib.Initialize(MemoryUtil.Shared, NCFallbackRandom.Shared); + + Span convKeyOut = stackalloc byte[32]; + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + ReadOnlySpan secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan conversationKey = Convert.FromHexString(v.conversation_key); + + NCPublicKey pubkey2 = default; + NCKeyUtil.GetPublicKey(context, in NCKeyUtil.AsSecretKey(secKey2), ref pubkey2); + + NCCipherUtil.GetConverstationKey( + context, + in NCKeyUtil.AsSecretKey(secKey1), + in pubkey2, + convKeyOut + ); + + Assert.IsTrue(conversationKey.SequenceEqual(convKeyOut)); + + MemoryUtil.InitializeBlock(convKeyOut); + } + } +#endif + + private EncryptionVector[] GetEncryptionVectors() + { + return _testVectors.RootElement.GetProperty("v2") + .GetProperty("valid") + .GetProperty("encrypt_decrypt") + .EnumerateArray() + .Select(v => v.Deserialize()!) + .ToArray(); + } + + void IDisposable.Dispose() + { + _testLib.Dispose(); + _testVectors.Dispose(); + GC.SuppressFinalize(this); + } + + private sealed class EncryptionVector + { + public string sec1 { get; set; } = string.Empty; + + public string sec2 { get; set; } = string.Empty; + + public string nonce { get; set; } = string.Empty; + + public string plaintext { get; set; } = string.Empty; + + public string payload { get; set; } = string.Empty; + + public string conversation_key { get; set; } = string.Empty; + } + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj new file mode 100644 index 0000000..a4542a6 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj @@ -0,0 +1,30 @@ + + + net8.0 + enable + enable + false + true + 0.2.0.0 + 0.2.0.0 + 0.2.0-c-sharp.95+Branch.c-sharp.Sha.6a4a464d9fdc7821cd5c5695656a3fe385497cc5 + 0.2.0-c-sharp0095 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + Always + + + \ No newline at end of file -- cgit