diff options
author | vnugent <public@vaughnnugent.com> | 2024-10-18 22:10:17 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-10-18 22:10:17 -0400 |
commit | 44044eb0fb28b774773e3284fd147c91d59d64e3 (patch) | |
tree | 429860e6ced91b02b7062f86c74120be5d5f0c11 /wrappers/dotnet | |
parent | 6a4a464d9fdc7821cd5c5695656a3fe385497cc5 (diff) |
refactor: Wire up unit testing and refactor c# api
Diffstat (limited to 'wrappers/dotnet')
19 files changed, 897 insertions, 732 deletions
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 @@ +<?xml version="1.0" encoding="utf-8"?> +<RunSettings> + <RunConfiguration> + <EnvironmentVariables> + <NOSCRYPT_DLL_PATH>../../../../../../../build/windows/debug/noscrypt.dll</NOSCRYPT_DLL_PATH> + </EnvironmentVariables> + </RunConfiguration> +</RunSettings>
\ 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/NCUtilCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCCipherUtil.cs index 72bb75a..5dc2a94 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCUtilCipher.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NCCipherUtil.cs @@ -15,20 +15,24 @@ 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 { - internal static class NCUtilCipher + public unsafe static class NCCipherUtil { /* * 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) + internal static nint Alloc(NCContext ctx, uint version, uint flags) { nint cipher = GetTable(ctx).NCUtilCipherAlloc(version, flags); @@ -43,7 +47,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption return cipher; } - public static uint GetFlags(NCContext ctx, nint cipher) + internal static uint GetFlags(NCContext ctx, nint cipher) { NCResult result = GetTable(ctx).NCUtilCipherGetFlags(cipher); @@ -52,9 +56,9 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption return (uint)result; } - public static void Free(NCContext ctx, nint cipher) => GetTable(ctx).NCUtilCipherFree(cipher); + internal static void Free(NCContext ctx, nint cipher) => GetTable(ctx).NCUtilCipherFree(cipher); - public static int GetIvSize(NCContext ctx, nint cipher) + internal static int GetIvSize(NCContext ctx, nint cipher) { NCResult result = GetTable(ctx).NCUtilCipherGetIvSize(cipher); @@ -63,7 +67,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption return checked((int)result); } - public static unsafe void SetProperty(NCContext ctx, nint cipher, uint property, ref readonly byte value, uint valueLen) + internal static void SetProperty(NCContext ctx, nint cipher, uint property, ref readonly byte value, uint valueLen) { fixed (byte* valPtr = &value) { @@ -73,7 +77,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption } } - public static uint GetOutputSize(NCContext ctx, nint cipher) + internal static uint GetOutputSize(NCContext ctx, nint cipher) { NCResult result = GetTable(ctx).NCUtilCipherGetOutputSize(cipher); @@ -82,7 +86,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption return (uint)result; } - public static unsafe uint ReadOutput(NCContext ctx, nint cipher, ref byte outputData, uint outLen) + internal static uint ReadOutput(NCContext ctx, nint cipher, ref byte outputData, uint outLen) { fixed (byte* outPtr = &outputData) { @@ -94,14 +98,14 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption } } - public static unsafe void InitCipher(NCContext ctx, nint cipher, byte* inputPtr, uint inputSize) + internal static void InitCipher(NCContext ctx, nint cipher, byte* inputPtr, uint inputSize) { NCResult result = GetTable(ctx).NCUtilCipherInit(cipher, inputPtr, inputSize); NCUtil.CheckResult<FunctionTable.NCUtilCipherInitDelegate>(result, raiseOnFailure: true); } - public static unsafe void Update( + internal static void Update( NCContext ctx, nint cipher, ref readonly NCSecretKey localKey, @@ -122,7 +126,46 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption } } - private static ref readonly FunctionTable GetTable(NCContext ctx) => ref ctx.Library.Functions; + +#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<byte> 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<FunctionTable.NCUtilCipherInitDelegate>(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/NoscryptCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptMessageCipher.cs index b30ea44..e44addf 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptCipher.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Encryption/NoscryptMessageCipher.cs @@ -18,6 +18,8 @@ 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; @@ -27,29 +29,54 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption /// <summary> /// The noscrypt util cipher wapper /// </summary> - /// <param name="ctx"></param> - /// <param name="flags">Cipher creation flags</param> + /// <param name="ctx">A reference to the library context object</param> + /// <param name="cipher">A pointer to an existing cipher context that this instance owns</param> /// <param name="version">The cipher specification version</param> - public sealed class NoscryptCipher(NCContext ctx, NoscryptCipherVersion version, NoscryptCipherFlags flags) : VnDisposeable + public class NoscryptMessageCipher : SafeHandleMinusOneIsInvalid { + private readonly NCContext _context; + private readonly NoscryptCipherVersion _version; private IMemoryHandle<byte>? _ivBuffer; - private readonly nint _cipher = NCUtilCipher.Alloc(ctx, (uint)version, (uint)flags); + + private NoscryptMessageCipher(NCContext ctx, nint cipher, NoscryptCipherVersion version) + : base(ownsHandle: true) + { + SetHandle(cipher); + _context = ctx; + _version = version; + } + + /// <summary> + /// Allocates and initializes a new cipher instance using the specified + /// </summary> + /// <param name="context">A reference to the noscrypt library context</param> + /// <param name="version">The encryption standard to use</param> + /// <param name="flags">The raw cipher flags to the pass to the cipher initialization function</param> + /// <returns>A new <see cref="NoscryptMessageCipher"/> instance</returns> + public static NoscryptMessageCipher Create(NCContext context, NoscryptCipherVersion version, NoscryptCipherFlags flags) + { + return new( + context, + NCCipherUtil.Alloc(context, (uint)version, (uint)flags), + version + ); + } /// <summary> /// The cipher standard version used by this instance /// </summary> - public NoscryptCipherVersion Version => version; + public NoscryptCipherVersion Version => _version; /// <summary> /// Gets the flags set for the cipher instance /// </summary> - public uint GetFlags() => NCUtilCipher.GetFlags(ctx, _cipher); + public uint GetFlags() => NCCipherUtil.GetFlags(_context, handle); /// <summary> /// Gets the cipher's initilaization vector size (or nonce) /// </summary> /// <returns>The size of the IV in bytes</returns> - public int GetIvSize() => NCUtilCipher.GetIvSize(ctx, _cipher); + public int GetIvSize() => NCCipherUtil.GetIvSize(_context, handle); /// <summary> /// Gets the internal heap buffer that holds the cipher's initalization @@ -121,9 +148,9 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption fixed (byte* inputPtr = &inputData) { - NCUtilCipher.InitCipher(ctx, _cipher, inputPtr, inputSize); + NCCipherUtil.InitCipher(_context, handle, inputPtr, inputSize); - NCUtilCipher.Update(ctx, _cipher, in localKey, in remoteKey); + NCCipherUtil.Update(_context, handle, in localKey, in remoteKey); } } @@ -153,7 +180,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption /// Gets the size of the output buffer required to read the cipher output /// </summary> /// <returns>The size of the output in bytes</returns> - public int GetOutputSize() => checked((int)NCUtilCipher.GetOutputSize(ctx, _cipher)); + public int GetOutputSize() => checked((int)NCCipherUtil.GetOutputSize(_context, handle)); /// <summary> /// Reads the output data from the cipher into the specified buffer @@ -166,7 +193,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption { ArgumentOutOfRangeException.ThrowIfLessThan(size, GetOutputSize()); - return checked((int)NCUtilCipher.ReadOutput(ctx, _cipher, ref outputData, (uint)size)); + return checked((int)NCCipherUtil.ReadOutput(_context, handle, ref outputData, (uint)size)); } /// <summary> @@ -186,7 +213,7 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption private IMemoryHandle<byte> AllocIvBuffer() { //Use the context heap to allocate the internal iv buffer - MemoryHandle<byte> buffer = MemoryUtil.SafeAlloc<byte>(ctx.Heap, GetIvSize(), zero: true); + MemoryHandle<byte> buffer = MemoryUtil.SafeAlloc<byte>(_context.Heap, GetIvSize(), zero: true); try { @@ -199,9 +226,9 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption * buffer is required so we don't need to pin managed memory * nor worry about the GC moving the buffer. */ - NCUtilCipher.SetProperty( - ctx, - _cipher, + NCCipherUtil.SetProperty( + _context, + DangerousGetHandle(), NC_ENC_SET_IV, ref buffer.GetReference(), (uint)buffer.Length @@ -217,10 +244,12 @@ namespace VNLib.Utils.Cryptography.Noscrypt.Encryption } ///<inheritdoc/> - protected override void Free() + protected override bool ReleaseHandle() { - NCUtilCipher.Free(ctx, _cipher); + 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 <https://www.gnu.org/licenses/>. - -using System; - -using VNLib.Utils.Cryptography.Noscrypt.Encryption; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - public interface INostrCrypto - { - - /// <summary> - /// Gets a nostr public key from a secret key. - /// </summary> - /// <param name="secretKey">A reference to the secret key to get the public key from</param> - /// <param name="publicKey">A reference to the public key structure to write the recovered key to</param> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="ArgumentNullException"></exception> - void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey); - - /// <summary> - /// Validates a secret key is in a valid format. - /// </summary> - /// <param name="secretKey">A readonly reference to key structure to validate</param> - /// <returns>True if the key is consiered valid against the secp256k1 curve</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="ArgumentNullException"></exception> - bool ValidateSecretKey(ref readonly NCSecretKey secretKey); - - /// <summary> - /// Allocates a new cipher instance with the supplied options. - /// </summary> - /// <param name="version">The cipher specification version</param> - /// <param name="flags">The cipher initialziation flags</param> - /// <returns>The cipher instance</returns> - NoscryptCipher AllocCipher(NoscryptCipherVersion version, NoscryptCipherFlags flags); - - /// <summary> - /// Signs the supplied data with the secret key and random32 nonce, then writes - /// the message signature to the supplied sig64 buffer. - /// </summary> - /// <param name="secretKey">The secret key used to sign the message</param> - /// <param name="random32">A highly secure random nonce used to seed the signature</param> - /// <param name="data">A pointer to the first byte in the message to sign</param> - /// <param name="dataSize">The size of the message in bytes</param> - /// <param name="sig64">A pointer to the first byte of a 64 byte buffer used to write the message signature to</param> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="ArgumentNullException"></exception> - void SignData( - ref readonly NCSecretKey secretKey, - ref readonly byte random32, - ref readonly byte data, - uint dataSize, - ref byte sig64 - ); - - /// <summary> - /// Performs cryptographic verification of the supplied data - /// against the supplied public key. - /// </summary> - /// <param name="pubKey">The signer's public key</param> - /// <param name="data">A pointer to the first byte in the message to sign</param> - /// <param name="dataSize">The number of bytes in the message</param> - /// <param name="sig64">A pointer to the signature buffer</param> - /// <returns>True if the signature could be verified against the public key. False otherwise</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="ArgumentNullException"></exception> - bool VerifyData( - ref readonly NCPublicKey pubKey, - ref readonly byte data, - uint dataSize, - ref readonly byte sig64 - ); - } -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCKeyUtil.cs index 307bbc1..50e1c9a 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCKeyUtil.cs @@ -14,18 +14,21 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. using System; -using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using 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 { - - public static class NCUtil + /// <summary> + /// Contains utility methods for working with nostr keys + /// </summary> + public static unsafe class NCKeyUtil { /// <summary> /// Gets a span of bytes from the current secret key @@ -115,79 +118,68 @@ namespace VNLib.Utils.Cryptography.Noscrypt return ref Unsafe.As<byte, NCPublicKey>(ref asBytes); } - internal static void CheckResult<T>(NCResult result, bool raiseOnFailure) where T : Delegate + /// <summary> + /// Gets a nostr public key from a secret key. + /// </summary> + /// <param name="secretKey">A reference to the secret key to get the public key from</param> + /// <param name="publicKey">A reference to the public key structure to write the recovered key to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + public static void GetPublicKey( + NCContext context, + ref readonly NCSecretKey secretKey, + ref NCPublicKey publicKey + ) { - //Only negative values are errors - if (result >= NC_SUCCESS) - { - return; - } - - NCResult asPositive = -result; + Check(context); - // 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) + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) { - case NCErrorCodes.E_NULL_PTR: - RaiseNullArgExceptionForArgumentNumber<T>(argNumber); - break; - case NCErrorCodes.E_INVALID_ARG: - RaiseArgExceptionForArgumentNumber<T>(argNumber); - break; - case NCErrorCodes.E_ARGUMENT_OUT_OF_RANGE: - RaiseOORExceptionForArgumentNumber<T>(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; + NCResult result = GetTable(context).NCGetPublicKey( + context.DangerousGetHandle(), + pSecKey, + pPubKey + ); + + NCUtil.CheckResult<FunctionTable.NCGetPublicKeyDelegate>(result, raiseOnFailure: true); } } - private static void RaiseOperationFailedException(bool raise) + /// <summary> + /// Validates a secret key is in a valid format. + /// </summary> + /// <param name="secretKey">A readonly reference to key structure to validate</param> + /// <returns>True if the key is consiered valid against the secp256k1 curve</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + public static bool ValidateSecretKey( + NCContext context, + ref readonly NCSecretKey secretKey + ) { - if (raise) + Check(context); + + fixed (NCSecretKey* pSecKey = &secretKey) { - throw new InvalidOperationException("The operation failed for an unknown reason"); - } - } + NCResult result = GetTable(context).NCValidateSecretKey( + context.DangerousGetHandle(), + pSecKey + ); - private static void RaiseNullArgExceptionForArgumentNumber<T>(int argNumber) where T : Delegate - { - //Get delegate parameters - Type type = typeof(T); - ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; - throw new ArgumentNullException(arg.Name, "Argument is null or invalid cannot continue"); - } + NCUtil.CheckResult<FunctionTable.NCValidateSecretKeyDelegate>(result, raiseOnFailure: false); - private static void RaiseArgExceptionForArgumentNumber<T>(int argNumber) where T : Delegate - { - //Get delegate parameters - Type type = typeof(T); - ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; - throw new ArgumentException("Argument is null or invalid cannot continue", arg.Name); + return result == NC_SUCCESS; + } } - private static void RaiseOORExceptionForArgumentNumber<T>(int argNumber) where T : Delegate + private static void Check(NCContext? context) { - //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"); + 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/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 <https://www.gnu.org/licenses/>. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; - -namespace VNLib.Utils.Cryptography.Noscrypt -{ - - public static class NoscryptExtensions - { - public static void SignData( - this INostrCrypto lib, - ref readonly NCSecretKey secKey, - ReadOnlySpan<byte> random32, - ReadOnlySpan<byte> data, - Span<byte> signatureBuffer - ) - { - ArgumentOutOfRangeException.ThrowIfLessThan(signatureBuffer.Length, NC_SIGNATURE_SIZE, nameof(signatureBuffer)); - ArgumentOutOfRangeException.ThrowIfLessThan(random32.Length, 32, nameof(random32)); - ArgumentOutOfRangeException.ThrowIfZero(data.Length, nameof(data)); - - lib.SignData( - secretKey: in secKey, - random32: in MemoryMarshal.GetReference(random32), - data: in MemoryMarshal.GetReference(data), - dataSize: (uint)data.Length, - sig64: ref MemoryMarshal.GetReference(signatureBuffer) - ); - } - -#if DEBUG - /* - * Conversation key is not meant to be a public api. Callers - * should use Encrypt/Decrypt methods to handle encryption. - * - * This method exists for vector testing purposes only. - */ - public static void GetConverstationKey( - this NostrCrypto lib, - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - Span<byte> conversationKeyOut32 - ) - { - ArgumentNullException.ThrowIfNull(lib); - ArgumentOutOfRangeException.ThrowIfNotEqual(conversationKeyOut32.Length, NC_CONVERSATION_KEY_SIZE, nameof(conversationKeyOut32)); - - //Get the conversation key - lib.GetConverstationKey( - secretKey: in secretKey, - publicKey: in publicKey, - key32: ref MemoryMarshal.GetReference(conversationKeyOut32) - ); - - } -#endif - } -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs 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 } /// <summary> - /// Initializes a new NostrCrypto context wraper directly that owns the internal context. - /// This may be done once at app startup and is thread-safe for the rest of the - /// application lifetime. - /// </summary> - /// <param name="heap">The heap to allocate the context from</param> - /// <param name="entropy32">The random entropy data to initialize the context with</param> - /// <returns>The library wrapper handle</returns> - public NostrCrypto InitializeCrypto(IUnmangedHeap heap, ReadOnlySpan<byte> entropy32) - { - ArgumentNullException.ThrowIfNull(heap); - - //Create the crypto interface from the new context object - return new NostrCrypto( - context: Initialize(heap, entropy32), - ownsContext: true - ); - } - - /// <summary> - /// Initializes a new NostrCrypto context wraper directly that owns the internal context. - /// This may be done once at app startup and is thread-safe for the rest of the - /// application lifetime. + /// Initialize a new NCContext for use. This may be done once at app startup + /// and is thread-safe for the rest of the application lifetime. /// </summary> - /// <param name="heap">The heap to allocate the context from</param> - /// <param name="random">Random source used to generate context entropy</param> - /// <returns>The library wrapper handle</returns> - public NostrCrypto InitializeCrypto(IUnmangedHeap heap, IRandomSource random) + /// <param name="heap"></param> + /// <param name="enropy32">The 32byte random seed/nonce for the noscrypt context</param> + /// <returns>The inialized context</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public NCContext Initialize(IUnmangedHeap heap, IRandomSource random) { ArgumentNullException.ThrowIfNull(random); @@ -206,13 +190,9 @@ namespace VNLib.Utils.Cryptography.Noscrypt Span<byte> entropy = stackalloc byte[NC_CTX_ENTROPY_SIZE]; random.GetRandomBytes(entropy); - NostrCrypto nc = InitializeCrypto(heap, entropy); - - MemoryUtil.InitializeBlock(entropy); - - return nc; + return Initialize(heap, entropy); } - + ///<inheritdoc/> protected override void Free() { @@ -256,6 +236,16 @@ namespace VNLib.Utils.Cryptography.Noscrypt /// </summary> /// <returns>The loaded library instance</returns> /// <exception cref="DllNotFoundException"></exception> - 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/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 <https://www.gnu.org/licenses/>. - -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 -{ - /// <summary> - /// A default implementation of the <see cref="INostrCrypto"/> interface - /// </summary> - /// <param name="context">The initialized library context</param> - public unsafe class NostrCrypto(NCContext context, bool ownsContext) : VnDisposeable, INostrCrypto - { - /// <summary> - /// Gets the underlying library context. - /// </summary> - public NCContext Context => context; - - private ref readonly FunctionTable Functions => ref context.Library.Functions; - - ///<inheritdoc/> - public NoscryptCipher AllocCipher(NoscryptCipherVersion version, NoscryptCipherFlags flags) => new (context, version, flags); - - ///<inheritdoc/> - public void GetPublicKey(ref readonly NCSecretKey secretKey, ref NCPublicKey publicKey) - { - Check(); - - fixed (NCSecretKey* pSecKey = &secretKey) - fixed (NCPublicKey* pPubKey = &publicKey) - { - NCResult result = Functions.NCGetPublicKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey); - NCUtil.CheckResult<FunctionTable.NCGetPublicKeyDelegate>(result, true); - } - } - - ///<inheritdoc/> - public void SignData( - ref readonly NCSecretKey secretKey, - ref readonly byte random32, - ref readonly byte data, - uint dataSize, - ref byte sig64 - ) - { - Check(); - - fixed (NCSecretKey* pSecKey = &secretKey) - fixed (byte* pData = &data, pSig = &sig64, pRandom = &random32) - { - NCResult result = Functions.NCSignData.Invoke( - ctx: context.DangerousGetHandle(), - sk: pSecKey, - random32: pRandom, - data: pData, - dataSize, - sig64: pSig - ); - - NCUtil.CheckResult<FunctionTable.NCSignDataDelegate>(result, true); - } - } - - ///<inheritdoc/> - public bool ValidateSecretKey(ref readonly NCSecretKey secretKey) - { - Check(); - - IntPtr libCtx = context.DangerousGetHandle(); - - fixed (NCSecretKey* pSecKey = &secretKey) - { - /* - * Validate should return a result of 1 if the secret key is valid - * or a 0 if it is not. - */ - NCResult result = Functions.NCValidateSecretKey.Invoke(libCtx, pSecKey); - NCUtil.CheckResult<FunctionTable.NCValidateSecretKeyDelegate>(result, false); - - return result == NC_SUCCESS; - } - } - - ///<inheritdoc/> - public bool VerifyData( - ref readonly NCPublicKey pubKey, - ref readonly byte data, - uint dataSize, - ref readonly byte sig64 - ) - { - Check(); - - fixed(NCPublicKey* pPubKey = &pubKey) - fixed (byte* pData = &data, pSig = &sig64) - { - NCResult result = Functions.NCVerifyData.Invoke(context.DangerousGetHandle(), pPubKey, pData, dataSize, pSig); - NCUtil.CheckResult<FunctionTable.NCVerifyDataDelegate>(result, false); - - return result == NC_SUCCESS; - } - } - -#if DEBUG - - /// <summary> - /// DEBUG ONLY: Gets the conversation key for the supplied secret key and public key - /// </summary> - /// <param name="secretKey">The sender's private key</param> - /// <param name="publicKey">The receiver's public key</param> - /// <param name="key32">A pointer to the 32byte buffer to write the conversation key to</param> - public void GetConverstationKey( - ref readonly NCSecretKey secretKey, - ref readonly NCPublicKey publicKey, - ref byte key32 - ) - { - Check(); - - fixed (NCSecretKey* pSecKey = &secretKey) - fixed (NCPublicKey* pPubKey = &publicKey) - fixed (byte* pKey = &key32) - { - NCResult result = Functions.NCGetConversationKey.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, pKey); - NCUtil.CheckResult<FunctionTable.NCGetConversationKeyDelegate>(result, true); - } - } - -#endif - ///<inheritdoc/> - protected override void Free() - { - if(ownsContext) - { - context.Dispose(); - } - } - - private static void ThrowIfNullRef([DoesNotReturnIf(false)] ref readonly byte value, string name) - { - if(Unsafe.IsNullRef(in value)) - { - throw new ArgumentNullException(name); - } - } - } -} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/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 <https://www.gnu.org/licenses/>. + +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 +{ + + /// <summary> + /// Contains utility methods for signing and verifying data using the noscrypt library + /// </summary> + public unsafe static class NCSignatureUtil + { + /// <summary> + /// Signs the data using the supplied secret key and + /// entropy pointer + /// </summary> + /// <param name="context">The initialized context memory to pass to the library</param> + /// <param name="secretKey">A reference to a structure containing the private key data</param> + /// <param name="random32">A pointer to a 32 byte buffer containing high entropy random data</param> + /// <param name="data">A pointer to a buffer containing the data to sign</param> + /// <param name="dataSize">The size of the data buffer in bytes</param> + /// <param name="sig64">A pointer to a 64 byte buffer to write signature data to</param> + /// <exception cref="InvalidOperationException"></exception> + 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<FunctionTable.NCSignDataDelegate>(result, raiseOnFailure: true); + } + } + + /// <summary> + /// Verifies signed data against the supplied public key + /// </summary> + /// <param name="context">The initialized context memory to pass to the library</param> + /// <param name="publicKey">A reference to a structure containing the public key data</param> + /// <param name="data">A pointer to a buffer containing the data to verify</param> + /// <param name="dataSize">The size of the data buffer in bytes</param> + /// <param name="sig64">A pointer to a 64 byte buffer to read signature data from</param> + /// <returns>True if the signature was signed by the supplied public key, false otherwise</returns> + 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<FunctionTable.NCVerifyDataDelegate>(result, false); + + return result == NC_SUCCESS; + } + } + + /// <summary> + /// Signs the data using the supplied secret key and + /// entropy pointer + /// </summary> + /// <param name="context">The initialized context memory to pass to the library</param> + /// <param name="secretKey">A reference to a structure containing the private key data</param> + /// <param name="random32">A pointer to a 32 byte buffer containing high entropy random data</param> + /// <param name="data">A pointer to a buffer containing the data to sign</param> + /// <param name="signatureBuffer">A pointer to a 64 byte buffer to write signature data to</param> + /// <exception cref="InvalidOperationException"></exception> + public static void SignData( + NCContext context, + ref readonly NCSecretKey secretKey, + ReadOnlySpan<byte> random32, + ReadOnlySpan<byte> data, + Span<byte> signatureBuffer + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signatureBuffer.Length, NC_SIGNATURE_SIZE, nameof(signatureBuffer)); + ArgumentOutOfRangeException.ThrowIfLessThan(random32.Length, 32, nameof(random32)); + ArgumentOutOfRangeException.ThrowIfZero(data.Length, nameof(data)); + + SignData( + context, + secretKey: in secretKey, + random32: in MemoryMarshal.GetReference(random32), + data: in MemoryMarshal.GetReference(data), + dataSize: (uint)data.Length, + sig64: ref MemoryMarshal.GetReference(signatureBuffer) + ); + } + + /// <summary> + /// Verifies signed data against the supplied public key + /// </summary> + /// <param name="context">The initialized context memory to pass to the library</param> + /// <param name="publicKey">A reference to a structure containing the public key data</param> + /// <param name="data">A pointer to a buffer containing the data to verify</param> + /// <param name="signatureBuffer">A pointer to a 64 byte buffer to read signature data from</param> + /// <returns>True if the signature was signed by the supplied public key, false otherwise</returns> + public static bool VerifyData( + NCContext context, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> data, + ReadOnlySpan<byte> 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/NoscryptSigner.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NoscryptSigner.cs index c81790b..063d2c0 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Singatures/NoscryptSigner.cs @@ -14,21 +14,23 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. using System; -using VNLib.Utils.Cryptography.Noscrypt.Random; -using VNLib.Utils.Extensions; -using VNLib.Utils.Memory; +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 +namespace VNLib.Utils.Cryptography.Noscrypt.Singatures { + /// <summary> /// A simple wrapper class to sign nostr message data using /// the noscrypt library /// </summary> /// <param name="noscrypt">The noscrypt library instance</param> /// <param name="random">A random entropy pool used to source random data for signature entropy</param> - public class NoscryptSigner(INostrCrypto noscrypt, IRandomSource random) + public class NoscryptSigner(NCContext context, IRandomSource random) { /// <summary> /// Gets the size of the buffer required to hold the signature @@ -72,12 +74,16 @@ namespace VNLib.Utils.Cryptography.Noscrypt /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public string SignData( - ReadOnlySpan<byte> secretKey, - ReadOnlySpan<byte> message, + ReadOnlySpan<byte> secretKey, + ReadOnlySpan<byte> message, INostrSignatureEncoder? format = null ) { - return SignData(in NCUtil.AsSecretKey(secretKey), message, format); + return SignData( + in NCKeyUtil.AsSecretKey(secretKey), + message, + format + ); } /// <summary> @@ -90,8 +96,8 @@ namespace VNLib.Utils.Cryptography.Noscrypt /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public string SignData( - ref readonly NCSecretKey secretkey, - ReadOnlySpan<byte> message, + ref readonly NCSecretKey secretkey, + ReadOnlySpan<byte> message, INostrSignatureEncoder? format = null ) { @@ -115,8 +121,8 @@ namespace VNLib.Utils.Cryptography.Noscrypt /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public void SignData( - ref readonly NCSecretKey secretkey, - ReadOnlySpan<byte> data, + ref readonly NCSecretKey secretkey, + ReadOnlySpan<byte> data, Span<byte> signature ) { @@ -126,7 +132,35 @@ namespace VNLib.Utils.Cryptography.Noscrypt Span<byte> entropy = stackalloc byte[NC_SIG_ENTROPY_SIZE]; random.GetRandomBytes(entropy); - noscrypt.SignData(in secretkey, entropy, data, signature); + NCSignatureUtil.SignData( + context, + in secretkey, + entropy, + data, + signature + ); + } + + public bool VerifyData( + ReadOnlySpan<byte> publicKey, + ReadOnlySpan<byte> data, + ReadOnlySpan<byte> sig + ) + { + return VerifyData( + in NCKeyUtil.AsPublicKey(publicKey), + data, + sig + ); + } + + public bool VerifyData( + ref readonly NCPublicKey pk, + ReadOnlySpan<byte> data, + ReadOnlySpan<byte> 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 @@ <Project Sdk="Microsoft.NET.Sdk"> - + <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> @@ -9,7 +9,7 @@ <RootNamespace>VNLib.Utils.Cryptography.Noscrypt</RootNamespace> <AssemblyName>VNLib.Utils.Cryptography.Noscrypt</AssemblyName> </PropertyGroup> - + <PropertyGroup> <Authors>Vaughn Nugent</Authors> <Company>Vaughn Nugent</Company> @@ -19,10 +19,22 @@ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Noscrypt</PackageProjectUrl> <RepositoryUrl>https://github.com/VnUgE/noscryot/tree/master/dotnet/VNLib.Utils.Cryptography.Noscrypt</RepositoryUrl> </PropertyGroup> - + <ItemGroup> - <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0124" /> - <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0124" /> + <None Include="..\README.md"> + <Pack>True</Pack> + <PackagePath>\</PackagePath> + </None> + <None Include="..\..\..\..\LICENSE"> + <Pack>True</Pack> + <PackagePath>\</PackagePath> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> </ItemGroup> - + + <ItemGroup> + <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0202" /> + <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0202" /> + </ItemGroup> + </Project> diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs 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 <https://www.gnu.org/licenses/>. + +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<T>(NCResult result, bool raiseOnFailure) where T : Delegate + { + //Only negative values are errors + if (result >= NC_SUCCESS) + { + return; + } + + NCResult asPositive = -result; + + // Error code are only 8 bits, if an argument error occured, the + // argument number will be in the next upper 8 bits + NCErrorCodes errorCode = (NCErrorCodes)(asPositive & 0xFF); + byte argNumber = (byte)(asPositive >> 8 & 0xFF); + + switch (errorCode) + { + case NCErrorCodes.E_NULL_PTR: + RaiseNullArgExceptionForArgumentNumber<T>(argNumber); + break; + case NCErrorCodes.E_INVALID_ARG: + RaiseArgExceptionForArgumentNumber<T>(argNumber); + break; + case NCErrorCodes.E_ARGUMENT_OUT_OF_RANGE: + RaiseOORExceptionForArgumentNumber<T>(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<T>(int argNumber) where T : Delegate + { + //Get delegate parameters + Type type = typeof(T); + ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; + throw new ArgumentNullException(arg.Name, "Argument is null or invalid cannot continue"); + } + + private static void RaiseArgExceptionForArgumentNumber<T>(int argNumber) where T : Delegate + { + //Get delegate parameters + Type type = typeof(T); + ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; + throw new ArgumentException("Argument is null or invalid cannot continue", arg.Name); + } + + private static void RaiseOORExceptionForArgumentNumber<T>(int argNumber) where T : Delegate + { + //Get delegate parameters + Type type = typeof(T); + ParameterInfo arg = type.GetMethod("Invoke")!.GetParameters()[argNumber]; + throw new ArgumentOutOfRangeException(arg.Name, "Argument is out of range of acceptable values"); + } + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/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<byte> 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<byte> seed = RandomHash.GetRandomBytes(32); ReadOnlySpan<byte> secretKey = RandomHash.GetRandomBytes(32); Span<byte> 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<byte> 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<byte> knownSec, ReadOnlySpan<byte> kownPub) + static void TestKnownKeys(NCContext context, ReadOnlySpan<byte> knownSec, ReadOnlySpan<byte> 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<byte> 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<ArgumentNullException>(() => crypto.ValidateSecretKey(ref NCSecretKey.NullRef)); + NCKeyUtil.ValidateSecretKey(context, in secKey); + Assert.ThrowsException<ArgumentNullException>(() => NCKeyUtil.ValidateSecretKey(null!, ref NCSecretKey.NullRef)); + Assert.ThrowsException<ArgumentNullException>(() => NCKeyUtil.ValidateSecretKey(context, ref NCSecretKey.NullRef)); //public key - Assert.ThrowsException<ArgumentNullException>(() => crypto.GetPublicKey(ref NCSecretKey.NullRef, ref pubKey)); - Assert.ThrowsException<ArgumentNullException>(() => 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<byte> ctBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); - - foreach (EncryptionVector v in GetEncryptionVectors()) - { - - ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); - ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); - ReadOnlySpan<byte> plainText = Encoding.UTF8.GetBytes(v.plaintext); - ReadOnlySpan<byte> nonce = Convert.FromHexString(v.nonce); - ReadOnlySpan<byte> 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<byte> outputBuffer = ctBuffer.AsSpan(0, cipher.GetOutputSize()); - - Assert.AreEqual<int>(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<byte> ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); - - foreach (EncryptionVector vector in GetEncryptionVectors()) - { - ReadOnlySpan<byte> secKey1 = Convert.FromHexString(vector.sec1); - ReadOnlySpan<byte> secKey2 = Convert.FromHexString(vector.sec2); - ReadOnlySpan<byte> expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); - ReadOnlySpan<byte> message = Convert.FromBase64String(vector.payload); - - NCPublicKey pub2 = default; + Assert.ThrowsException<ArgumentNullException>(() => NCKeyUtil.GetPublicKey(null!, in secKey, ref pubKey)); + Assert.ThrowsException<ArgumentNullException>(() => NCKeyUtil.GetPublicKey(context, ref NCSecretKey.NullRef, ref pubKey)); + Assert.ThrowsException<ArgumentNullException>(() => NCKeyUtil.GetPublicKey(context, in secKey, ref NCPublicKey.NullRef)); + + /* + * VERIFY DATA + */ + //Null context + Assert.ThrowsException<ArgumentNullException>(() => + NCSignatureUtil.VerifyData(null!, ref pubKey, bin32, bin64) + ); - //Recover public keys - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); + //Null pubkey + Assert.ThrowsException<ArgumentNullException>(() => + 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<ArgumentOutOfRangeException>(() => + NCSignatureUtil.VerifyData(context, ref pubKey, [], bin64) + ); - int outLen = msgCipher.GetOutputSize(); - Assert.IsTrue(outLen == expectedPt.Length); + //No signature + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + NCSignatureUtil.VerifyData(context, ref pubKey, bin32, []) + ); - Span<byte> plaintext = ptBuffer.AsSpan(0, outLen); + //Signature too small + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + 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<ArgumentNullException>(() => + NCSignatureUtil.SignData(null!, ref secKey, bin32, bin32, bin64) + ); + //Null secret key + Assert.ThrowsException<ArgumentNullException>(() => + NCSignatureUtil.SignData(context, ref NCSecretKey.NullRef, bin32, bin32, bin64) + ); - //Converstation key is only available in debug builds -#if DEBUG + //No entropy + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + NCSignatureUtil.SignData(context, ref secKey, [], bin32, bin64) + ); - [TestMethod()] - public void ConverstationKeyTest() - { - using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); - - Span<byte> convKeyOut = stackalloc byte[32]; + //No data + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, [], bin64) + ); - foreach (EncryptionVector v in GetEncryptionVectors()) - { - ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); - ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); - ReadOnlySpan<byte> conversationKey = Convert.FromHexString(v.conversation_key); + //No signature + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, bin32, []) + ); - NCPublicKey pubkey2 = default; - nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pubkey2); + //Signature too small + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + NCSignatureUtil.SignData(context, ref secKey, bin32, bin32, bin32) + ); - nc.GetConverstationKey( - in NCUtil.AsSecretKey(secKey1), - in pubkey2, - convKeyOut - ); + //Entropy too small + Assert.ThrowsException<ArgumentOutOfRangeException>(() => + 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<EncryptionVector>()!) - .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/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<byte> ctBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan<byte> plainText = Encoding.UTF8.GetBytes(v.plaintext); + ReadOnlySpan<byte> nonce = Convert.FromHexString(v.nonce); + ReadOnlySpan<byte> 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<byte> outputBuffer = ctBuffer.AsSpan(0, cipher.GetOutputSize()); + + Assert.AreEqual<int>(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<byte> ptBuffer = MemoryUtil.SafeAllocNearestPage(1200, false); + + foreach (EncryptionVector vector in GetEncryptionVectors()) + { + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(vector.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(vector.sec2); + ReadOnlySpan<byte> expectedPt = Encoding.UTF8.GetBytes(vector.plaintext); + ReadOnlySpan<byte> 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<byte> 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<byte> convKeyOut = stackalloc byte[32]; + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan<byte> 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<EncryptionVector>()!) + .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/VNLib.Utils.Cryptography.NoscryptTests.csproj index 5917b15..a4542a6 100644 --- a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/VNLib.Utils.Cryptography.NoscryptTests.csproj @@ -1,32 +1,30 @@ -<Project Sdk="Microsoft.NET.Sdk"> - +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> - <IsPackable>false</IsPackable> <IsTestProject>true</IsTestProject> + <AssemblyVersion>0.2.0.0</AssemblyVersion> + <FileVersion>0.2.0.0</FileVersion> + <InformationalVersion>0.2.0-c-sharp.95+Branch.c-sharp.Sha.6a4a464d9fdc7821cd5c5695656a3fe385497cc5</InformationalVersion> + <Version>0.2.0-c-sharp0095</Version> </PropertyGroup> - <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> - <PackageReference Include="MSTest.TestAdapter" Version="3.4.1" /> - <PackageReference Include="MSTest.TestFramework" Version="3.4.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="3.6.1" /> + <PackageReference Include="MSTest.TestFramework" Version="3.6.1" /> <PackageReference Include="coverlet.collector" Version="6.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> - <ItemGroup> <ProjectReference Include="..\src\VNLib.Utils.Cryptography.Noscrypt.csproj" /> </ItemGroup> - <ItemGroup> <None Update="nip44.vectors.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> - -</Project> +</Project>
\ No newline at end of file |