diff options
Diffstat (limited to 'wrappers')
30 files changed, 3886 insertions, 0 deletions
diff --git a/wrappers/dotnet/Taskfile.yaml b/wrappers/dotnet/Taskfile.yaml new file mode 100644 index 0000000..9690e07 --- /dev/null +++ b/wrappers/dotnet/Taskfile.yaml @@ -0,0 +1,60 @@ +#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/src/Base64SignatureEncoder.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Base64SignatureEncoder.cs new file mode 100644 index 0000000..c5078a4 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Base64SignatureEncoder.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public sealed class Base64SignatureEncoder : INostrSignatureEncoder + { + /// <summary> + /// Shared formatter instance for base64 signatures + /// </summary> + public static Base64SignatureEncoder Instance { get; } = new Base64SignatureEncoder(); + + ///<inheritdoc/> + public string GetString(ReadOnlySpan<byte> signature) => Convert.ToBase64String(signature); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs new file mode 100644 index 0000000..6a60c73 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/HexSignatureEncoder.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public sealed class HexSignatureEncoder : INostrSignatureEncoder + { + /// <summary> + /// Shared formatter instance for hex signatures + /// </summary> + public static HexSignatureEncoder Instance { get; } = new HexSignatureEncoder(); + + ///<inheritdoc/> + public string GetString(ReadOnlySpan<byte> signature) => Convert.ToHexString(signature); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs new file mode 100644 index 0000000..49c0cc0 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrCrypto.cs @@ -0,0 +1,164 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace 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> + /// 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 + ); + + /// <summary> + /// Computes a nip44 message authentication code (MAC) using the supplied key and payload. + /// </summary> + /// <param name="hmacKey32">The key returned during a + /// <see cref="Encrypt(ref readonly NCSecretKey, ref readonly NCPublicKey, ref readonly byte, ref readonly byte, ref byte, uint, ref byte)"/> + /// </param> + /// <param name="payload">A pointer to a buffer </param> + /// <param name="payloadSize">The size of the buffer to compute the mac of, in bytes</param> + /// <param name="hmacOut32">A pointer to the 32byte buffer to write the mac to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + void ComputeMac( + ref readonly byte hmacKey32, + ref readonly byte payload, + uint payloadSize, + ref byte hmacOut32 + ); + + /// <summary> + /// Verifies a nip44 message authentication code (MAC) against the supplied key and payload. + /// </summary> + /// <param name="secretKey">A pointer to the receiver's secret key</param> + /// <param name="publicKey">A pointer to senders the public key</param> + /// <param name="nonce32">A pointer to the 32byte nonce buffer</param> + /// <param name="mac32">A pointer to the 32byte message buffer</param> + /// <param name="payload">A pointer to the message buffer</param> + /// <param name="payloadSize">The size in bytes of the payload buffer</param> + /// <returns>True if the message authentication code (MAC) matches, false otherwise </returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + bool VerifyMac( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte mac32, + ref readonly byte payload, + uint payloadSize + ); + + /// <summary> + /// Encrypts a message using the supplied secret key, public key, and nonce. When this function + /// returns, the cipherText buffer will contain the encrypted message, and the hmacKeyOut32 buffer + /// will contain the key used to compute the message authentication code (MAC). + /// <para> + /// NOTE: The cipherText buffer must be at least as large as the plaintext buffer. The + /// size parameter must be the size of the number of bytes to encrypt. + /// </para> + /// </summary> + /// <param name="secretKey">A pointer to the receiver's secret key</param> + /// <param name="publicKey">A pointer to senders the public key</param> + /// <param name="nonce32">A pointer to the 32byte nonce used for message encryption</param> + /// <param name="plainText">A pointer to the plaintext buffer to encrypt</param> + /// <param name="cipherText">A pointer to the cyphertext buffer to write encrypted data to (must be as large or larger than the plaintext buffer)</param> + /// <param name="size">The size of the data to encrypt</param> + /// <param name="hmacKeyOut32"></param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentNullException"></exception> + void EncryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte plainText, + ref byte cipherText, + uint size, + ref byte hmacKeyOut32 + ); + + /// <summary> + /// Decrypts a message using the supplied secret key, public key, and the original message + /// nonce. + /// </summary> + /// <param name="secretKey">A pointer to the receiver's secret key</param> + /// <param name="publicKey">A pointer to senders the public key</param> + /// <param name="nonce32">A pointer to the 32byte nonce used for message encryption</param> + /// <param name="plainText">A pointer to the plaintext buffer to write plaintext data to (must be as large or larger than the ciphertext buffer)</param> + /// <param name="cipherText">A pointer to the cyphertext buffer to read encrypted data from</param> + /// <param name="size">The size of the buffer to decrypt</param> + void DecryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte cipherText, + ref byte plainText, + uint size + ); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs new file mode 100644 index 0000000..3a26466 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrEncryptionVersion.cs @@ -0,0 +1,43 @@ +// 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/>. + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a message encryption version used by the Nostr protocol + /// </summary> + public interface INostrEncryptionVersion + { + /// <summary> + /// The noscrypt compatible encryption version + /// </summary> + internal uint Version { get; } + + /// <summary> + /// Calculates the required payload buffer size for the specified data size + /// </summary> + /// <param name="dataSize">The size of the input data</param> + /// <returns>The estimated size of the buffer required to complete the opeation</returns> + internal int GetPayloadBufferSize(int dataSize); + + /// <summary> + /// Calculates the required message buffer size for the specified data size + /// </summary> + /// <param name="dataSize">Plain text data size</param> + /// <returns>The estimated size of the buffer required to complete the opeation</returns> + internal int GetMessageBufferSize(int dataSize); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs new file mode 100644 index 0000000..b8c69f5 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/INostrSignatureEncoder.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Encodes a message signature into it's string representation + /// </summary> + public interface INostrSignatureEncoder + { + /// <summary> + /// Creates a string of the encoded signature data + /// </summary> + /// <param name="signature">The signature data to encode into the string</param> + /// <returns>The encoded signature string</returns> + string GetString(ReadOnlySpan<byte> signature); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IRandomSource.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IRandomSource.cs new file mode 100644 index 0000000..5c5f2ac --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/IRandomSource.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents a generator for random data, that fills abinary buffer with random bytes + /// on demand. + /// </summary> + public interface IRandomSource + { + /// <summary> + /// Fills the given buffer with random bytes + /// </summary> + /// <param name="buffer">Binary buffer to fill with random data</param> + void GetRandomBytes(Span<byte> buffer); + } +}
\ No newline at end of file diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs new file mode 100644 index 0000000..8f8c6b4 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCContext.cs @@ -0,0 +1,87 @@ +// 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.Diagnostics; + +using Microsoft.Win32.SafeHandles; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using VNLib.Utils.Cryptography.Noscrypt.@internal; +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The noscrypt library context + /// </summary> + public sealed class NCContext : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly IUnmangedHeap Heap; + + /// <summary> + /// The library this context was created from + /// </summary> + public NoscryptLibrary Library { get; } + + internal NCContext(IntPtr handle, IUnmangedHeap heap, NoscryptLibrary library) :base(true) + { + ArgumentNullException.ThrowIfNull(heap); + ArgumentNullException.ThrowIfNull(library); + + Heap = heap; + Library = library; + + //Store the handle + SetHandle(handle); + } + + /// <summary> + /// Reinitializes the context with the specified entropy + /// </summary> + /// <param name="entropy">The randomness buffer used to randomize the context</param> + /// <param name="size">The random data buffer size (must be 32 bytes)</param> + public unsafe void Reinitalize(ref byte entropy, int size) + { + //Entropy must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(size, NC_CTX_ENTROPY_SIZE); + + this.ThrowIfClosed(); + fixed (byte* p = &entropy) + { + NCResult result = Library.Functions.NCReInitContext.Invoke(handle, p); + NCUtil.CheckResult<FunctionTable.NCReInitContextDelegate>(result, true); + } + } + + ///<inheritdoc/> + protected override bool ReleaseHandle() + { + if (!Library.IsClosed) + { + //destroy the context + Library.Functions.NCDestroyContext.Invoke(handle); + Trace.WriteLine($"Destroyed noscrypt context 0x{handle:x}"); + } + + //Free the handle + return Heap.Free(ref handle); + } + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs new file mode 100644 index 0000000..beb21c2 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip04EncryptionVersion.cs @@ -0,0 +1,40 @@ +// 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 static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The NIP04 encryption version used by the Nostr protocol + /// </summary> + public sealed class NCNip04EncryptionVersion : INostrEncryptionVersion + { + /// <summary> + /// A static nip04 encryption version instance + /// </summary> + public static readonly NCNip04EncryptionVersion Instance = new(); + + ///<inheritdoc/> + uint INostrEncryptionVersion.Version => NC_ENC_VERSION_NIP04; + + ///<inheritdoc/> + int INostrEncryptionVersion.GetMessageBufferSize(int dataSize) => Nip04Util.CalcBufferSize(dataSize); + + ///<inheritdoc/> + int INostrEncryptionVersion.GetPayloadBufferSize(int dataSize) => Nip04Util.CalcBufferSize(dataSize); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs new file mode 100644 index 0000000..0d5907a --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCNip44EncryptionVersion.cs @@ -0,0 +1,39 @@ +// 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 static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// The NIP44 encryption version used by the Nostr protocol + /// </summary> + public sealed class NCNip44EncryptionVersion : INostrEncryptionVersion + { + /// <summary> + /// A static nip44 encryption version instance + /// </summary> + public static readonly NCNip44EncryptionVersion Instance = new(); + + ///<inheritdoc/> + uint INostrEncryptionVersion.Version => NC_ENC_VERSION_NIP44; + + int INostrEncryptionVersion.GetMessageBufferSize(int dataSize) => Nip44Util.CalcFinalBufferSize(dataSize); + + ///<inheritdoc/> + int INostrEncryptionVersion.GetPayloadBufferSize(int dataSize) => Nip44Util.CalcBufferSize(dataSize); + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs new file mode 100644 index 0000000..57d7c3f --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCPublicKey.cs @@ -0,0 +1,37 @@ +// 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 +{ + /// <summary> + /// Represents a user's secp256k1 public key for use with the Nostrcrypt library + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_PUBKEY_SIZE)] + public unsafe struct NCPublicKey + { + /// <summary> + /// Gets a null <see cref="NCPublicKey"/> reference. + /// </summary> + public static ref NCPublicKey NullRef => ref Unsafe.NullRef<NCPublicKey>(); + + private fixed byte key[NC_SEC_PUBKEY_SIZE]; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs new file mode 100644 index 0000000..18f025b --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCSecretKey.cs @@ -0,0 +1,37 @@ +// 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.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// Represents an nostr variant of a secp265k1 secret key that matches + /// the size of the native library + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_KEY_SIZE)] + public unsafe struct NCSecretKey + { + /// <summary> + /// Gets a null reference to a secret key + /// </summary> + public static ref NCSecretKey NullRef => ref Unsafe.NullRef<NCSecretKey>(); + + private fixed byte key[NC_SEC_KEY_SIZE]; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs new file mode 100644 index 0000000..49c66c1 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NCUtil.cs @@ -0,0 +1,194 @@ +// 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 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 + { + /// <summary> + /// Gets a span of bytes from the current secret key + /// structure + /// </summary> + /// <param name="key"></param> + /// <returns>The secret key data span</returns> + public unsafe static Span<byte> 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<NCSecretKey, byte>(ref key); + return MemoryMarshal.CreateSpan(ref asBytes, sizeof(NCSecretKey)); + } + + /// <summary> + /// Gets a span of bytes from the current public key + /// structure + /// </summary> + /// <param name="key"></param> + /// <returns>The public key data as a data span</returns> + public unsafe static Span<byte> 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<NCPublicKey, byte>(ref key); + return MemoryMarshal.CreateSpan(ref asBytes, sizeof(NCPublicKey)); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="span">The secret key data</param> + /// <returns>A mutable secret key reference</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref NCSecretKey AsSecretKey(Span<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCSecretKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCSecretKey>(ref asBytes); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="span">The public key data span</param> + /// <returns>A mutable reference to the public key structure</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref NCPublicKey AsPublicKey(Span<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCPublicKey>(ref asBytes); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="span">The secret key data span</param> + /// <returns>A readonly refernce to the secret key structure</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref readonly NCSecretKey AsSecretKey(ReadOnlySpan<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCSecretKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCSecretKey>(ref asBytes); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="span">The public key data span</param> + /// <returns>A readonly reference to the public key structure</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public unsafe static ref readonly NCPublicKey AsPublicKey(ReadOnlySpan<byte> span) + { + ArgumentOutOfRangeException.ThrowIfLessThan(span.Length, sizeof(NCPublicKey), nameof(span)); + + ref byte asBytes = ref MemoryMarshal.GetReference(span); + return ref Unsafe.As<byte, NCPublicKey>(ref asBytes); + } + + 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 + byte errorCode = (byte)(asPositive & 0xFF); + byte argNumber = (byte)((asPositive >> 8) & 0xFF); + + switch (errorCode) + { + case E_NULL_PTR: + RaiseNullArgExceptionForArgumentNumber<T>(argNumber); + break; + case E_INVALID_ARG: + RaiseArgExceptionForArgumentNumber<T>(argNumber); + break; + case E_ARGUMENT_OUT_OF_RANGE: + RaiseOORExceptionForArgumentNumber<T>(argNumber); + break; + case E_INVALID_CTX: + throw new InvalidOperationException("The library context object is null or invalid"); + case E_OPERATION_FAILED: + RaiseOperationFailedException(raiseOnFailure); + break; + case E_VERSION_NOT_SUPPORTED: + throw new NotSupportedException("The requested version is not supported"); + + default: + if(raiseOnFailure) + { + throw new InvalidOperationException($"The operation failed for an unknown reason, code: {errorCode: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/src/NcFallbackRandom.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NcFallbackRandom.cs new file mode 100644 index 0000000..0949ad8 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NcFallbackRandom.cs @@ -0,0 +1,36 @@ +// 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.Hashing; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <summary> + /// A fallback crypographic random source used for default + /// rng if you wish + /// </summary> + public sealed class NcFallbackRandom : IRandomSource + { + /// <summary> + /// Gets the shared instance of the fallback random source + /// </summary> + public static NcFallbackRandom Shared { get; } = new NcFallbackRandom(); + + /// <inheritdoc/> + public void GetRandomBytes(Span<byte> buffer) => RandomHash.GetRandomBytes(buffer); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs new file mode 100644 index 0000000..c1906f0 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip04Util.cs @@ -0,0 +1,50 @@ +// 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.Buffers.Text; + +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public static class Nip04Util + { + public static bool IsValidPayload(ReadOnlySpan<char> payload) + { + /* Iv is base64 encoded so it should be 33% larger than 16 byte iv */ + ReadOnlySpan<char> iv = payload.SliceAfterParam("?iv="); + return iv.Length > 20 && iv.Length <= 26; + } + + public static ReadOnlySpan<char> GetIV(ReadOnlySpan<char> payload) => payload.SliceAfterParam("?iv="); + + public static ReadOnlySpan<char> GetCipherText(ReadOnlySpan<char> payload) => payload.SliceBeforeParam("?iv="); + + public static int CalcBufferSize(int dataSize) + { + throw new NotImplementedException(); + } + + static readonly int MaxEncodedIvLength = Base64.GetMaxEncodedToUtf8Length(16); + + public static int CalcMessageBufferSize(int dataSize) + { + int bufSize = CalcBufferSize(dataSize); + return bufSize + "?iv=".Length + MaxEncodedIvLength; + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.cs new file mode 100644 index 0000000..ddc2d68 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44MessageSegments.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + public readonly ref struct Nip44MessageSegments(ReadOnlySpan<byte> payload) + { + readonly ReadOnlySpan<byte> _payload = payload; + + public ReadOnlySpan<byte> Payload => _payload; + + public ReadOnlySpan<byte> Nonce => Nip44Util.GetNonceFromPayload(_payload); + + public ReadOnlySpan<byte> Ciphertext => Nip44Util.GetCiphertextFromPayload(_payload); + + public ReadOnlySpan<byte> Mac => Nip44Util.GetMacFromPayload(_payload); + + public ReadOnlySpan<byte> NonceAndCiphertext => Nip44Util.GetNonceAndCiphertext(_payload); + + public byte Version => Nip44Util.GetMessageVersion(_payload); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs new file mode 100644 index 0000000..2aebee1 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/Nip44Util.cs @@ -0,0 +1,190 @@ +// 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.Buffers.Binary; +using System.Runtime.InteropServices; + +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// Provides a set of utility methods for working with the Noscrypt library + /// </summary> + public static class Nip44Util + { + /// <summary> + /// Calculates the required NIP44 encryption buffer size for + /// the specified input data size + /// </summary> + /// <param name="dataSize">The size (in bytes) of the encoded data to encrypt</param> + /// <returns>The exact size of the padded buffer output</returns> + public static int CalcBufferSize(int dataSize) + { + /* + * Taken from https://github.com/nostr-protocol/nips/blob/master/44.md + * + * Not gonna lie, kinda dumb branches. I guess they want to save space + * with really tiny messages... Dunno, but whatever RTFM + */ + + //Min message size is 32 bytes + int minSize = Math.Max(dataSize, 32); + + //find the next power of 2 that will fit the min size + int nexPower = 1 << ((int)Math.Log2(minSize - 1)) + 1; + + int chunk = nexPower <= 256 ? 32 : nexPower / 8; + + return (chunk * ((int)Math.Floor((double)((minSize - 1) / chunk)) + 1)) + sizeof(ushort); + } + + /// <summary> + /// Calculates the final buffer size required to hold the encrypted data + /// </summary> + /// <param name="dataSize">The size (in bytes) of plaintext data to encrypt</param> + /// <returns>The number of bytes required to store the final nip44 message</returns> + public static int CalcFinalBufferSize(int dataSize) + { + /* version + nonce + payload + mac */ + return CalcBufferSize(dataSize) + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE + 1; + } + + /// <summary> + /// Formats the plaintext data into a buffer that can be properly encrypted. + /// The output buffer must be zeroed, or can be zeroed using the + /// <paramref name="zeroOutput"/> parameter. Use <see cref="CalcBufferSize(uint)"/> + /// to determine the required output buffer size. + /// </summary> + /// <param name="plaintextData">A buffer containing plaintext data to copy to the output</param> + /// <param name="output">The output data buffer to format</param> + /// <param name="zeroOutput">A value that indicates if the buffer should be zeroed before use</param> + public static void FormatBuffer(ReadOnlySpan<byte> plaintextData, Span<byte> output, bool zeroOutput) + { + //First zero out the buffer + if (zeroOutput) + { + MemoryUtil.InitializeBlock(output); + } + + //Make sure the output buffer is large enough so we dont overrun it + ArgumentOutOfRangeException.ThrowIfLessThan(output.Length, plaintextData.Length + sizeof(ushort), nameof(output)); + + //Write the data size to the first 2 bytes + ushort dataSize = (ushort)plaintextData.Length; + BinaryPrimitives.WriteUInt16BigEndian(output, dataSize); + + //Copy the plaintext data to the output buffer after the data size + MemoryUtil.Memmove( + src: in MemoryMarshal.GetReference(plaintextData), + srcOffset: 0, + dst: ref MemoryMarshal.GetReference(output), + dstOffset: sizeof(ushort), + elementCount: (uint)plaintextData.Length + ); + + //We assume the remaining buffer is zeroed out + } + + public static void WriteNip44Message( + ReadOnlySpan<byte> payloadBuffer, + byte version, + ReadOnlySpan<byte> mac, + Span<byte> outBuffer + ) + { + int requiredBufferSize = CalcFinalBufferSize(payloadBuffer.Length); + + //Make sure the output buffer is large enough so we dont overrun it + ArgumentOutOfRangeException.ThrowIfLessThan(outBuffer.Length, requiredBufferSize, nameof(outBuffer)); + ArgumentOutOfRangeException.ThrowIfLessThan(mac.Length, NC_ENCRYPTION_MAC_SIZE, nameof(mac)); + + //Write the version number to the first byte + outBuffer[0] = version; + + //Copy the payload buffer to the output buffer after the version number + MemoryUtil.Memmove( + src: in MemoryMarshal.GetReference(payloadBuffer), + srcOffset: 0, + dst: ref MemoryMarshal.GetReference(outBuffer), + dstOffset: 1, + elementCount: (uint)payloadBuffer.Length + ); + + //Copy the mac to the end of the output buffer + MemoryUtil.Memmove( + src: in MemoryMarshal.GetReference(mac), + srcOffset: 0, + dst: ref MemoryMarshal.GetReference(outBuffer), + dstOffset: (uint)(requiredBufferSize - NC_ENCRYPTION_MAC_SIZE), + elementCount: NC_ENCRYPTION_MAC_SIZE + ); + } + + public static ReadOnlySpan<byte> GetNonceFromPayload(ReadOnlySpan<byte> message) + { + //The nonce is 32 bytes following the 1st byte version number of the message + return message.Slice(1, NC_ENCRYPTION_NONCE_SIZE); + } + + public static ReadOnlySpan<byte> GetCiphertextFromPayload(ReadOnlySpan<byte> message) + { + //Message is between the nonce and the trailing mac + int payloadSize = message.Length - (1 + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE); + return message.Slice(1 + NC_ENCRYPTION_NONCE_SIZE, payloadSize); + } + + public static ReadOnlySpan<byte> GetMacFromPayload(ReadOnlySpan<byte> message) + { + //The mac is the last 32 bytes of the message + return message[^NC_ENCRYPTION_MAC_SIZE..]; + } + + public static ReadOnlySpan<byte> GetNonceAndCiphertext(ReadOnlySpan<byte> message) + { + //The nonce is 32 bytes following the 1st byte version number of the message + return message.Slice(1, NC_ENCRYPTION_NONCE_SIZE + GetCiphertextFromPayload(message).Length); + } + + public static byte GetMessageVersion(ReadOnlySpan<byte> message) + { + //The first byte is the message version + return message[0]; + } + + public static ReadOnlySpan<byte> GetPlaintextMessage(ReadOnlySpan<byte> plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return plaintextPayload.Slice(sizeof(ushort), ptLength); + } + + public static bool IsValidPlaintextMessage(ReadOnlySpan<byte> plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return ptLength == plaintextPayload.Length - sizeof(ushort); + } + + public static Range GetPlaintextRange(ReadOnlySpan<byte> plaintextPayload) + { + ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload); + return new Range(sizeof(ushort), ptLength); + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs new file mode 100644 index 0000000..ccae190 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptExtensions.cs @@ -0,0 +1,237 @@ +// 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 EncryptNip44( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> plainText, + Span<byte> hmackKeyOut32, + Span<byte> cipherText + ) + { + ArgumentNullException.ThrowIfNull(lib); + + //Chacha requires the output buffer to be at-least the size of the input buffer + ArgumentOutOfRangeException.ThrowIfGreaterThan(plainText.Length, cipherText.Length, nameof(plainText)); + + //Nonce must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(nonce32.Length, NC_ENCRYPTION_NONCE_SIZE, nameof(nonce32)); + + ArgumentOutOfRangeException.ThrowIfNotEqual(hmackKeyOut32.Length, NC_HMAC_KEY_SIZE, nameof(hmackKeyOut32)); + + //Encrypt data, use the plaintext buffer size as the data size + lib.EncryptNip44( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in MemoryMarshal.GetReference(nonce32), + plainText: in MemoryMarshal.GetReference(plainText), + cipherText: ref MemoryMarshal.GetReference(cipherText), + size: (uint)plainText.Length, + hmacKeyOut32: ref MemoryMarshal.GetReference(hmackKeyOut32) + ); + } + + public static unsafe void EncryptNip44( + this INostrCrypto lib, + ref NCSecretKey secretKey, + ref NCPublicKey publicKey, + void* nonce32, + void* hmacKeyOut32, + void* plainText, + void* cipherText, + uint size + ) + { + ArgumentNullException.ThrowIfNull(plainText); + ArgumentNullException.ThrowIfNull(cipherText); + ArgumentNullException.ThrowIfNull(nonce32); + + //Spans are easer to forward references from pointers without screwing up arguments + lib.EncryptNip44( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in Unsafe.AsRef<byte>(nonce32), + plainText: in Unsafe.AsRef<byte>(plainText), + cipherText: ref Unsafe.AsRef<byte>(cipherText), + size: size, + hmacKeyOut32: ref Unsafe.AsRef<byte>(hmacKeyOut32) + ); + } + + + public static void DecryptNip44( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> cipherText, + Span<byte> plainText + ) + { + ArgumentNullException.ThrowIfNull(lib); + + //Chacha requires the output buffer to be at-least the size of the input buffer + ArgumentOutOfRangeException.ThrowIfGreaterThan(cipherText.Length, plainText.Length, nameof(cipherText)); + + //Nonce must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(nonce32.Length, 32, nameof(nonce32)); + + //Decrypt data, use the ciphertext buffer size as the data size + lib.DecryptNip44( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in MemoryMarshal.GetReference(nonce32), + cipherText: in MemoryMarshal.GetReference(cipherText), + plainText: ref MemoryMarshal.GetReference(plainText), + size: (uint)cipherText.Length + ); + } + + public static unsafe void DecryptNip44( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + void* nonce32, + void* cipherText, + void* plainText, + uint size + ) + { + ArgumentNullException.ThrowIfNull(nonce32); + ArgumentNullException.ThrowIfNull(cipherText); + ArgumentNullException.ThrowIfNull(plainText); + + //Spans are easer to forward references from pointers without screwing up arguments + DecryptNip44( + lib: lib, + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: new Span<byte>(nonce32, NC_ENCRYPTION_NONCE_SIZE), + cipherText: new Span<byte>(cipherText, (int)size), + plainText: new Span<byte>(plainText, (int)size) + ); + } + + public static bool VerifyMac( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ReadOnlySpan<byte> nonce32, + ReadOnlySpan<byte> mac32, + ReadOnlySpan<byte> payload + ) + { + ArgumentNullException.ThrowIfNull(lib); + ArgumentOutOfRangeException.ThrowIfZero(payload.Length, nameof(payload)); + ArgumentOutOfRangeException.ThrowIfNotEqual(nonce32.Length, NC_ENCRYPTION_NONCE_SIZE, nameof(nonce32)); + ArgumentOutOfRangeException.ThrowIfNotEqual(mac32.Length, NC_ENCRYPTION_MAC_SIZE, nameof(mac32)); + + //Verify the HMAC + return lib.VerifyMac( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in MemoryMarshal.GetReference(nonce32), + mac32: in MemoryMarshal.GetReference(mac32), + payload: in MemoryMarshal.GetReference(payload), + payloadSize: (uint)payload.Length + ); + } + + public static unsafe bool VerifyMac( + this INostrCrypto lib, + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + void* nonce32, + void* mac32, + void* payload, + uint payloadSize + ) + { + ArgumentNullException.ThrowIfNull(nonce32); + ArgumentNullException.ThrowIfNull(mac32); + ArgumentNullException.ThrowIfNull(payload); + + return lib.VerifyMac( + secretKey: in secretKey, + publicKey: in publicKey, + nonce32: in Unsafe.AsRef<byte>(nonce32), + mac32: in Unsafe.AsRef<byte>(mac32), + payload: in Unsafe.AsRef<byte>(payload), + payloadSize: payloadSize + ); + } + + 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 new file mode 100644 index 0000000..108a713 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptLibrary.cs @@ -0,0 +1,242 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; +using VNLib.Utils.Memory; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +using VNLib.Utils.Cryptography.Noscrypt.@internal; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// Initializes the native library and provides access to the native functions + /// </summary> + /// <param name="Library">An existing noscrypt library handle</param> + /// <param name="OwnsHandle">A value that indicates if the instance owns the library handle</param> + public unsafe sealed class NoscryptLibrary(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable + { + public const string NoscryptDefaultLibraryName = "noscrypt"; + + //Constant values match the noscrypt.h header + public const int NC_SEC_KEY_SIZE = 0x20; + public const int NC_SEC_PUBKEY_SIZE = 0x20; + public const int NC_ENCRYPTION_NONCE_SIZE = 0x20; + public const int NC_PUBKEY_SIZE = 0x20; + public const int NC_SIGNATURE_SIZE = 0x40; + public const int NC_CONV_KEY_SIZE = 0x20; + public const int NC_MESSAGE_KEY_SIZE = 0x20; + public const int NC_HMAC_KEY_SIZE = 0x20; + public const int NC_ENCRYPTION_MAC_SIZE = 0x20; + public const int NC_CONVERSATION_KEY_SIZE = 0x20; + public const int NC_CTX_ENTROPY_SIZE = 0x20; + public const int NC_SIG_ENTROPY_SIZE = 0x20; + + public const uint NC_ENC_VERSION_NIP04 = 0x00000004u; + public const uint NC_ENC_VERSION_NIP44 = 0x00000002c; + + public const uint NC_ENC_SET_VERSION = 0x01u; + public const uint NC_ENC_SET_NIP44_NONCE = 0x02u; + public const uint NC_ENC_SET_NIP44_MAC_KEY = 0x03u; + public const uint NC_ENC_SET_NIP04_KEY = 0x04u; + public const uint NC_ENC_SET_NIP04_IV = 0x05u; + + //Noscrypt error codes + public const NCResult NC_SUCCESS = 0x00; + public const byte E_NULL_PTR = 0x01; + public const byte E_INVALID_ARG = 0x02; + public const byte E_INVALID_CTX = 0x03; + public const byte E_ARGUMENT_OUT_OF_RANGE = 0x04; + public const byte E_OPERATION_FAILED = 0x05; + public const byte E_VERSION_NOT_SUPPORTED = 0x06; + + private readonly FunctionTable _functions = FunctionTable.BuildFunctionTable(Library); + + /// <summary> + /// Gets a reference to the loaded function table for + /// the native library + /// </summary> + internal ref readonly FunctionTable Functions + { + get + { + Check(); + Library.ThrowIfClosed(); + return ref _functions; + } + } + + /// <summary> + /// Gets a value that determines if the library has been released + /// </summary> + internal bool IsClosed => Library.IsClosed || Library.IsInvalid; + + /// <summary> + /// 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"></param> + /// <param name="entropy32">Initialization entropy buffer</param> + /// <param name="size">The size of the buffer (must be 32 bytes)</param> + /// <returns>The inialized context</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public NCContext Initialize(IUnmangedHeap heap, ref readonly byte entropy32, int size) + { + ArgumentNullException.ThrowIfNull(heap); + + //Entropy must be exactly 32 bytes + ArgumentOutOfRangeException.ThrowIfNotEqual(size, NC_CTX_ENTROPY_SIZE); + + //Get struct size + nuint ctxSize = Functions.NCGetContextStructSize.Invoke(); + + //Allocate the context with the struct alignment on a heap + IntPtr ctx = heap.Alloc(1, ctxSize, true); + try + { + NCResult result; + fixed (byte* p = &entropy32) + { + result = Functions.NCInitContext.Invoke(ctx, p); + } + + NCUtil.CheckResult<FunctionTable.NCInitContextDelegate>(result, true); + + Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}"); + + return new NCContext(ctx, heap, this); + } + catch + { + heap.Free(ref ctx); + throw; + } + } + + /// <summary> + /// 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"></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, ReadOnlySpan<byte> enropy32) + { + return Initialize( + heap, + ref MemoryMarshal.GetReference(enropy32), + enropy32.Length + ); + } + + /// <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. + /// </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) + { + ArgumentNullException.ThrowIfNull(random); + + //Get random bytes for context entropy + Span<byte> entropy = stackalloc byte[NC_CTX_ENTROPY_SIZE]; + random.GetRandomBytes(entropy); + + NostrCrypto nc = InitializeCrypto(heap, entropy); + + MemoryUtil.InitializeBlock(entropy); + + return nc; + } + + ///<inheritdoc/> + protected override void Free() + { + if (OwnsHandle) + { + Library.Dispose(); + Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}"); + } + } + + /// <summary> + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// </summary> + /// <param name="path">The native library path or name to load</param> + /// <param name="search">The search path options</param> + /// <returns>The loaded library instance</returns> + /// <exception cref="DllNotFoundException"></exception> + public static NoscryptLibrary Load(string path, DllImportSearchPath search) + { + //Load the native library + SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search); + + Trace.WriteLine($"Loaded noscrypt library 0x{handle.DangerousGetHandle():x} from {path}"); + + //Create the wrapper + return new NoscryptLibrary(handle, true); + } + + /// <summary> + /// Loads the native library from the specified path and initializes the + /// function table for use. + /// </summary> + /// <param name="path">The native library path or name to load</param> + /// <returns>The loaded library instance</returns> + /// <exception cref="DllNotFoundException"></exception> + public static NoscryptLibrary Load(string path) => Load(path, DllImportSearchPath.SafeDirectories); + + /// <summary> + /// Attempts to load the default noscrypt library from the system search path + /// </summary> + /// <returns>The loaded library instance</returns> + /// <exception cref="DllNotFoundException"></exception> + public static NoscryptLibrary LoadDefault() => Load(NoscryptDefaultLibraryName, DllImportSearchPath.SafeDirectories); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs new file mode 100644 index 0000000..586fa46 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NoscryptSigner.cs @@ -0,0 +1,133 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + /// <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) + { + /// <summary> + /// Gets the size of the buffer required to hold the signature + /// </summary> + public static int SignatureBufferSize => NC_SIGNATURE_SIZE; + + /// <summary> + /// Signs a message using the specified private key and message data + /// </summary> + /// <param name="hexPrivateKey">The hexadecimal private key used to sign the message</param> + /// <param name="message">The message data to sign</param> + /// <param name="format">A encoder used to convert the signature data to an encoded string</param> + /// <returns>The string encoded nostr signature</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public string SignData(string hexPrivateKey, ReadOnlySpan<byte> 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); + } + } + + /// <summary> + /// Signs a message using the specified secret key and message data + /// </summary> + /// <param name="secretKey">The secret key data buffer</param> + /// <param name="message">The message data to sign</param> + /// <param name="format">A encoder used to convert the signature data to an encoded string</param> + /// <returns>The string encoded nostr signature</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public string SignData( + ReadOnlySpan<byte> secretKey, + ReadOnlySpan<byte> message, + INostrSignatureEncoder? format = null + ) + { + return SignData(in NCUtil.AsSecretKey(secretKey), message, format); + } + + /// <summary> + /// Signs a message using the specified secret key and message data + /// </summary> + /// <param name="secretkey">A reference to the secret key structurer</param> + /// <param name="message">The message data to sign</param> + /// <param name="format">A encoder used to convert the signature data to an encoded string</param> + /// <returns>The string encoded nostr signature</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public string SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan<byte> message, + INostrSignatureEncoder? format = null + ) + { + //Default to hex encoding because that is the default NIP-01 format + format ??= HexSignatureEncoder.Instance; + + Span<byte> sigBuffer = stackalloc byte[SignatureBufferSize]; + + SignData(message, sigBuffer); + + return format.GetString(sigBuffer); + } + + + /// <summary> + /// Signs a message using the specified secret key and message data + /// </summary> + /// <param name="secretkey">A reference to the secret key structurer</param> + /// <param name="data">The message data to sign</param> + /// <param name="signature">A buffer to write signature data to</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public void SignData( + ref readonly NCSecretKey secretkey, + ReadOnlySpan<byte> data, + Span<byte> signature + ) + { + ArgumentOutOfRangeException.ThrowIfLessThan(signature.Length, NC_SIGNATURE_SIZE, nameof(signature)); + + //Signature generation required random entropy to be secure + Span<byte> 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 new file mode 100644 index 0000000..36e2381 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrCrypto.cs @@ -0,0 +1,322 @@ +// 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 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 void DecryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte cipherText, + ref byte plainText, + uint size + ) + { + Check(); + + ThrowIfNullRef(in nonce32, nameof(nonce32)); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pCipherText = &cipherText, pTextPtr = &plainText, pNonce = &nonce32) + { + NCEncryptionArgs data = new(); + + //Version set first otherwise errors will occur + SetEncProperty(&data, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44); + //Only the nonce must be set, the hmac key is not needed for decryption + SetEncPropertyEx(&data, NC_ENC_SET_NIP44_NONCE, pNonce, NC_ENCRYPTION_NONCE_SIZE); + SetEncData(&data, pTextPtr, pCipherText, size); + + NCResult result = Functions.NCDecrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); + NCUtil.CheckResult<FunctionTable.NCDecryptDelegate>(result, true); + } + } + + ///<inheritdoc/> + public void EncryptNip44( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte plainText, + ref byte cipherText, + uint size, + ref byte hmackKeyOut32 + ) + { + Check(); + + ThrowIfNullRef(in nonce32, nameof(nonce32)); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pCipherText = &cipherText, + pTextPtr = &plainText, + pHmacKeyOut = &hmackKeyOut32, + pNonce = &nonce32 + ) + { + NCEncryptionArgs data = new(); + + /* + * Use the extended api to set properties correctly and validate them. + * + * The version MUST be set before continuing to set properties + * + * Since pointers are used, they must be only be set/accessed inside + * this fixed statement. + */ + SetEncProperty(&data, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44); + SetEncPropertyEx(&data, NC_ENC_SET_NIP44_MAC_KEY, pHmacKeyOut, NC_HMAC_KEY_SIZE); + SetEncPropertyEx(&data, NC_ENC_SET_NIP44_NONCE, pNonce, NC_ENCRYPTION_NONCE_SIZE); + SetEncData(&data, pTextPtr, pCipherText, size); + + NCResult result = Functions.NCEncrypt.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &data); + NCUtil.CheckResult<FunctionTable.NCEncryptDelegate>(result, true); + } + } + + ///<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; + } + } + + ///<inheritdoc/> + public bool VerifyMac( + ref readonly NCSecretKey secretKey, + ref readonly NCPublicKey publicKey, + ref readonly byte nonce32, + ref readonly byte mac32, + ref readonly byte payload, + uint payloadSize + ) + { + Check(); + + //Check pointers we need to use + ThrowIfNullRef(in nonce32, nameof(nonce32)); + ThrowIfNullRef(in mac32, nameof(mac32)); + ThrowIfNullRef(in payload, nameof(payload)); + + fixed (NCSecretKey* pSecKey = &secretKey) + fixed (NCPublicKey* pPubKey = &publicKey) + fixed (byte* pPayload = &payload, pMac = &mac32, pNonce = &nonce32) + { + + NCMacVerifyArgs args = new() + { + payloadSize = payloadSize, + payload = pPayload, + mac32 = pMac, + nonce32 = pNonce + }; + + //Exec and bypass failure + NCResult result = Functions.NCVerifyMac.Invoke(context.DangerousGetHandle(), pSecKey, pPubKey, &args); + NCUtil.CheckResult<FunctionTable.NCVerifyMacDelegate>(result, false); + + //Result should be success if the hmac is valid + return result == NC_SUCCESS; + } + } + + ///<inheritdoc/> + public void ComputeMac( + ref readonly byte hmacKey32, + ref readonly byte payload, + uint payloadSize, + ref byte hmacOut32 + ) + { + Check(); + + //Library will check for null pointers, since they are all arguments + fixed (byte* pKey = &hmacKey32, pPayload = &payload, pOut = &hmacOut32) + { + NCResult result = Functions.NCComputeMac.Invoke(context.DangerousGetHandle(), pKey, pPayload, payloadSize, pOut); + NCUtil.CheckResult<FunctionTable.NCComputeMacDelegate>(result, true); + } + } + +#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 + + + private void SetEncPropertyEx(NCEncryptionArgs* args, uint prop, byte* value, uint valueLen) + { + NCResult result = Functions.NCSetEncryptionPropertyEx(args, prop, value, valueLen); + NCUtil.CheckResult<FunctionTable.NCSetEncryptionPropertyExDelegate>(result, true); + } + + private void SetEncProperty(NCEncryptionArgs* args, uint prop, uint value) + { + NCResult result = Functions.NCSetEncryptionProperty(args, prop, value); + NCUtil.CheckResult<FunctionTable.NCSetEncryptionPropertyExDelegate>(result, true); + } + + private void SetEncData(NCEncryptionArgs* args, byte* input, byte* output, uint dataLen) + { + /* + * WARNING: + * For now this a short-cut for setting the input and output data pointers + * technically this still works and avoids the PInvoke call, but this may + * change in the future. + */ + args->dataSize = dataLen; + args->inputData = input; + args->outputData = output; + } + + ///<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/NostrMessageCipher.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs new file mode 100644 index 0000000..918d196 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/NostrMessageCipher.cs @@ -0,0 +1,423 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Authentication; + +using VNLib.Utils.Memory; + +using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + public sealed class NostrMessageCipher(INostrCrypto lib, INostrEncryptionVersion version) : VnDisposeable + { + const int Nip44MaxMessageSize = 65603; + + private readonly INostrCrypto library = lib; + + private NCSecretKey _fromKey; + private NCPublicKey _toKey; + private Buffer32 _nonce32; + private Buffer32 _mac32; + + /// <summary> + /// The message encryption version used by this instance + /// </summary> + public uint Version { get; } = version.Version; + + /// <summary> + /// The message nonce created during encryption event + /// </summary> + public unsafe Span<byte> Nonce => MemoryMarshal.CreateSpan(ref GetNonceRef(), sizeof(Buffer32)); + + /// <summary> + /// The message MAC set during encryption, and required for decryption + /// </summary> + public unsafe Span<byte> Mac => MemoryMarshal.CreateSpan(ref GetMacRef(), sizeof(Buffer32)); + + /// <summary> + /// Gets the size of the buffer required to encrypt the specified data size + /// </summary> + /// <param name="dataSize">The size of the message raw plaintext message to send</param> + /// <returns>The minimum number of bytes required for message encryption output</returns> + /// <exception cref="NotSupportedException"></exception> + public int GetPayloadBufferSize(int dataSize) + => version.GetPayloadBufferSize(dataSize); + + /// <summary> + /// Gets the size of the buffer required to hold the full encrypted message data + /// for the encryption version used + /// </summary> + /// <param name="dataSize">The plaintext data size</param> + /// <returns>The estimated size of the output buffer</returns> + public int GetMessageBufferSize(int dataSize) + => version.GetMessageBufferSize(dataSize); + + /// <summary> + /// Sets the encryption secret key for the message + /// </summary> + /// <param name="secKey">The secret key buffer</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrMessageCipher SetSecretKey(ReadOnlySpan<byte> secKey) + => SetSecretKey(in NCUtil.AsSecretKey(secKey)); + + /// <summary> + /// Sets the encryption secret key for the message + /// </summary> + /// <param name="secKey">The secret key structure to copy</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrMessageCipher SetSecretKey(ref readonly NCSecretKey secKey) + { + MemoryUtil.CloneStruct(in secKey, ref _fromKey); + return this; + } + + /// <summary> + /// Assigns the public key used to encrypt the message as the + /// receiver of the message + /// </summary> + /// <param name="pubKey">The user's public key receiving the message</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrMessageCipher SetPublicKey(ReadOnlySpan<byte> pubKey) + => SetPublicKey(in NCUtil.AsPublicKey(pubKey)); + + /// <summary> + /// Assigns the public key used to encrypt the message as the + /// receiver of the message + /// </summary> + /// <param name="pubKey">The user's public key receiving the message</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrMessageCipher SetPublicKey(ref readonly NCPublicKey pubKey) + { + MemoryUtil.CloneStruct(in pubKey, ref _toKey); + return this; + } + + /// <summary> + /// Assigns the nonce to the message. Must be <see cref="NC_ENCRYPTION_NONCE_SIZE"/> + /// in length + /// </summary> + /// <param name="nonce">The nonce value to copy</param> + /// <returns>The current instance for chaining</returns> + /// <exception cref="ArgumentException"></exception> + public NostrMessageCipher SetNonce(ReadOnlySpan<byte> nonce) + { + MemoryUtil.CopyStruct(nonce, ref _nonce32); + return this; + } + + /// <summary> + /// Assigns a random nonce using the specified random source + /// </summary> + /// <param name="rng">The random source to genrate a random nonce from</param> + /// <returns>The current instance for chaining</returns> + public NostrMessageCipher SetRandomNonce(IRandomSource rng) + { + rng.GetRandomBytes(Nonce); + return this; + } + + /// <summary> + /// Configures a 32 byte mac for the message for nip44 decryption + /// </summary> + /// <param name="mac">The message mac</param> + /// <returns>The current instance for chaining</returns> + public NostrMessageCipher SetMac(ReadOnlySpan<byte> mac) + { + MemoryUtil.CopyStruct(mac, ref _mac32); + return this; + } + + /// <summary> + /// Decrypts a full nostr encrypted message and writes the plaintext + /// data to the output buffer + /// </summary> + /// <param name="message">The nostr message buffer to decrypt</param> + /// <param name="plaintext">The output plaintext buffer</param> + /// <returns>The number of bytes written the the plaintext buffer</returns> + /// <exception cref="FormatException"></exception> + /// <exception cref="NotSupportedException"></exception> + public int DecryptMessage(ReadOnlySpan<byte> message, Span<byte> plaintext) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => DecryptNip44Message(message, plaintext), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + /// <summary> + /// Encrypts the plaintext message and writes the encrypted message to the + /// specified buffer. The output matches the format of the full nostr message + /// for the specified encryption version + /// </summary> + /// <param name="plaintext">The plaintext data to be encrypted</param> + /// <param name="message">The buffer to write the encrypted message data to</param> + /// <returns>The number of bytes written to the message buffer</returns> + /// <exception cref="NotSupportedException"></exception> + public int EncryptMessage(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44Message(plaintext, message), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44Message(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + int minRequiredOutSize = Nip44Util.CalcFinalBufferSize(plaintext.Length); + + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, minRequiredOutSize, nameof(message)); + + ForwardOnlyWriter<byte> messageWriter = new(message); + + // From spec -> concat(version, nonce, ciphertext, mac) + messageWriter.Append(0x02); // Version + messageWriter.Append<byte>(Nonce); // nonce + + //Encrypt plaintext and write directly the message buffer + int written = EncryptPayload(plaintext, messageWriter.Remaining); + + messageWriter.Advance(written); + + //Append the message mac, it was writen after the encryption operation + messageWriter.Append<byte>(Mac); + + return messageWriter.Written; + } + + /// <summary> + /// Encrypts the plaintext message and writes the encrypted message to the + /// specified buffer, along with a 32 byte mac of the message + /// </summary> + /// <param name="plaintext">The plaintext data to encrypt</param> + /// <param name="message">The message output buffer to write encrypted data to</param> + /// <param name="macOut32">A buffer to write the computed message mac to</param> + /// <returns>The number of bytes writtn to the message output buffer</returns> + /// <remarks> + /// The message buffer must be at-least the size of the output buffer, and it is not + /// initialized before the encryption operation. + /// </remarks> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public int EncryptPayload(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => EncryptNip44(plaintext, message), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int EncryptNip44(ReadOnlySpan<byte> plaintext, Span<byte> message) + { + int payloadSize = GetPayloadBufferSize(plaintext.Length); + + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + ArgumentOutOfRangeException.ThrowIfZero(message.Length, nameof(message)); + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, payloadSize, nameof(message)); + + /* + * Alloc temp buffer to copy formatted payload to data to for the encryption + * operation. Encryption will write directly to the message buffer + */ + + using UnsafeMemoryHandle<byte> ptPayloadBuf = MemoryUtil.UnsafeAllocNearestPage<byte>(payloadSize, true); + using UnsafeMemoryHandle<byte> hmacKeyBuf = MemoryUtil.UnsafeAlloc<byte>(NC_HMAC_KEY_SIZE, true); + + Debug.Assert(hmacKeyBuf.Length == NC_HMAC_KEY_SIZE); + + Nip44Util.FormatBuffer(plaintext, ptPayloadBuf.Span, false); + + library.EncryptNip44( + secretKey: in _fromKey, + publicKey: in _toKey, + nonce32: in GetNonceRef(), + plainText: in ptPayloadBuf.GetReference(), + cipherText: ref MemoryMarshal.GetReference(message), + size: (uint)payloadSize, //IMPORTANT: Format buffer will pad the buffer to the exact size + hmacKeyOut32: ref hmacKeyBuf.GetReference() //Must set the hmac key buffer + ); + + + //Compute message mac, key should be set by the encryption operation + library.ComputeMac( + hmacKey32: in hmacKeyBuf.GetReference(), + payload: in MemoryMarshal.GetReference(message), + payloadSize: (uint)payloadSize, //Again set exact playload size + hmacOut32: ref GetMacRef() + ); + + //Clear buffers + MemoryUtil.InitializeBlock(ref hmacKeyBuf.GetReference(), hmacKeyBuf.IntLength); + MemoryUtil.InitializeBlock(ref ptPayloadBuf.GetReference(), ptPayloadBuf.IntLength); + + return payloadSize; + } + + private int DecryptNip44Message(ReadOnlySpan<byte> message, Span<byte> plaintext) + { + //Full Nip44 messages must be at-least 99 bytes in length + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, 99, nameof(message)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(message.Length, Nip44MaxMessageSize, nameof(message)); + + //Message decoder used to get the nip44 message segments + Nip44MessageSegments msg = new(message); + + if (msg.Version != 0x02) + { + return 0; + } + + SetNonce(msg.Nonce); + SetMac(msg.Mac); + + //Temporary buffer to write decrypted plaintext data to + using UnsafeMemoryHandle<byte> plaintextBuffer = MemoryUtil.UnsafeAllocNearestPage<byte>(msg.Ciphertext.Length, true); + + int written = DecryptPayload(msg.Ciphertext, plaintextBuffer.Span); + + Span<byte> ptOut = plaintextBuffer.AsSpan(0, written); + + //Must check message bounds before returning a range + if (!Nip44Util.IsValidPlaintextMessage(ptOut)) + { + throw new FormatException("Plaintext data was not properly encrypted because it was not properly formatted or decryption failed"); + } + + Range msgRange = Nip44Util.GetPlaintextRange(ptOut); + Debug.Assert(msgRange.Start.Value > 0); + Debug.Assert(msgRange.End.Value > 0); + + int ptLength = msgRange.End.Value - msgRange.Start.Value; + + Debug.Assert(ptLength > 0); + + //Write the wrapped plaintext (unpadded) to the output plaintext buffer + MemoryUtil.Memmove( + src: in plaintextBuffer.GetReference(), + srcOffset: (uint)msgRange.Start.Value, + dst: ref MemoryMarshal.GetReference(plaintext), + dstOffset: 0, + elementCount: (uint)ptLength + ); + + return ptLength; + } + + /// <summary> + /// Decrypts a nostr encrypted message in it's full binary from. + /// </summary> + /// <param name="payload"></param> + /// <param name="plaintext"></param> + /// <returns>The number of bytes written to the output buffer, or an error code if an error occured during the encryption</returns> + /// <exception cref="NotSupportedException"></exception> + public int DecryptPayload(ReadOnlySpan<byte> payload, Span<byte> plaintext) + { + return Version switch + { + NC_ENC_VERSION_NIP44 => DecryptNip44Payload(payload, plaintext), + _ => throw new NotSupportedException("NIP04 encryption is not supported"), + }; + } + + private int DecryptNip44Payload(ReadOnlySpan<byte> message, Span<byte> plaintext) + { + ArgumentOutOfRangeException.ThrowIfZero(message.Length, nameof(message)); + ArgumentOutOfRangeException.ThrowIfZero(plaintext.Length, nameof(plaintext)); + + //Validate the incoming message for a nip44 message + ArgumentOutOfRangeException.ThrowIfLessThan(message.Length, 32, nameof(message)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(message.Length, Nip44MaxMessageSize, nameof(message)); + + //Plaintext buffer must be large enough to hold the decrypted message + ArgumentOutOfRangeException.ThrowIfLessThan(plaintext.Length, message.Length, nameof(plaintext)); + + bool macValid = library.VerifyMac( + in _fromKey, + in _toKey, + nonce32: in GetNonceRef(), + mac32: in GetMacRef(), + payload: ref MemoryMarshal.GetReference(message), + (uint)message.Length + ); + + if (!macValid) + { + throw new AuthenticationException("Message MAC is invalid"); + } + + library.DecryptNip44( + in _fromKey, + in _toKey, + nonce32: in GetNonceRef(), + cipherText: in MemoryMarshal.GetReference(message), + plainText: ref MemoryMarshal.GetReference(plaintext), + (uint)message.Length + ); + + //Return the number of bytes written to the output buffer + return message.Length; + } + + private unsafe ref byte GetNonceRef() + { + Debug.Assert(NC_ENCRYPTION_NONCE_SIZE == sizeof(Buffer32)); + return ref Unsafe.As<Buffer32, byte>(ref _nonce32); + } + + private unsafe ref byte GetMacRef() + { + Debug.Assert(NC_ENCRYPTION_MAC_SIZE == sizeof(Buffer32)); + return ref Unsafe.As<Buffer32, byte>(ref _mac32); + } + + protected override void Free() + { + //Zero all internal memory + MemoryUtil.ZeroStruct(ref _fromKey); + MemoryUtil.ZeroStruct(ref _toKey); + MemoryUtil.ZeroStruct(ref _nonce32); + MemoryUtil.ZeroStruct(ref _mac32); + } + + /// <summary> + /// Initializes a new <see cref="NostrMessageCipher"/> with the nip44 encryption + /// method. + /// </summary> + /// <param name="lib">The nostr crypto implementation instance to use</param> + /// <returns>The intialzied message instance</returns> + public static NostrMessageCipher CreateNip44Cipher(INostrCrypto lib) + => new(lib, NCNip44EncryptionVersion.Instance); + + + [StructLayout(LayoutKind.Sequential, Size = 32)] + unsafe struct Buffer32 + { + fixed byte value[32]; + } + } + +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/UnmanagedRandomSource.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/UnmanagedRandomSource.cs new file mode 100644 index 0000000..91ff64b --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/UnmanagedRandomSource.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.Runtime.InteropServices; + +using VNLib.Utils; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.Cryptography.Noscrypt +{ + + /// <summary> + /// A wrapper class for an unmanaged random source that conforms to the <see cref="IRandomSource"/> interface + /// </summary> + public class UnmanagedRandomSource : VnDisposeable, IRandomSource + { + /// <summary> + /// The explicit name of the unmanaged random function to get random bytes + /// </summary> + public const string RngFunctionName = "GetRandomBytes"; + + unsafe delegate void UnmanagedRandomSourceDelegate(byte* buffer, int size); + + private readonly bool OwnsHandle; + private readonly SafeLibraryHandle _library; + private readonly UnmanagedRandomSourceDelegate _getRandomBytes; + + /// <summary> + /// Loads the unmanaged random source from the given library + /// and attempts to get the random bytes method <see cref="RngFunctionName"/> + /// </summary> + /// <param name="path"></param> + /// <param name="search"></param> + /// <returns>The wrapped library that conforms to the <see cref="IRandomSource"/></returns> + public static UnmanagedRandomSource LoadLibrary(string path, DllImportSearchPath search) + { + //Try to load the library + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(path, search); + try + { + return new UnmanagedRandomSource(lib, true); + } + catch + { + //release lib + lib.Dispose(); + throw; + } + } + + /// <summary> + /// Creates the unmanaged random source from the given library + /// </summary> + /// <param name="lib">The library handle to wrap</param> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="EntryPointNotFoundException"></exception> + public UnmanagedRandomSource(SafeLibraryHandle lib, bool ownsHandle) + { + lib.ThrowIfClosed(); + + _library = lib; + + //get the method delegate + _getRandomBytes = lib.DangerousGetFunction<UnmanagedRandomSourceDelegate>(RngFunctionName); + + OwnsHandle = ownsHandle; + } + + ///<inheritdoc/> + public unsafe void GetRandomBytes(Span<byte> buffer) + { + _library.ThrowIfClosed(); + + //Fix buffer and call unmanaged method + fixed (byte* ptr = &MemoryMarshal.GetReference(buffer)) + { + _getRandomBytes(ptr, buffer.Length); + } + } + + ///<inheritdoc/> + protected override void Free() + { + if (OwnsHandle) + { + _library.Dispose(); + } + } + } +}
\ No newline at end of file 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 new file mode 100644 index 0000000..7e7f5de --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/VNLib.Utils.Cryptography.Noscrypt.csproj @@ -0,0 +1,28 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>false</ImplicitUsings> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <PackageReadmeFile>README.md</PackageReadmeFile> + <RootNamespace>VNLib.Utils.Cryptography.Noscrypt</RootNamespace> + <AssemblyName>VNLib.Utils.Cryptography.Noscrypt</AssemblyName> + </PropertyGroup> + + <PropertyGroup> + <Authors>Vaughn Nugent</Authors> + <Company>Vaughn Nugent</Company> + <Product>VNLib.Utils.Cryptography.Noscrypt</Product> + <Description>Provides a managed library wrapper for the noscrypt native library</Description> + <Copyright>Copyright © 2024 Vaughn Nugent</Copyright> + <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-ci0122" /> + <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0122" /> + </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 new file mode 100644 index 0000000..17b66b2 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/FunctionTable.cs @@ -0,0 +1,141 @@ +// 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.Native; +using VNLib.Utils.Extensions; + +using NCResult = System.Int64; + +namespace VNLib.Utils.Cryptography.Noscrypt.@internal +{ + + internal unsafe readonly struct FunctionTable + { + + public readonly NCGetContextStructSizeDelegate NCGetContextStructSize; + public readonly NCInitContextDelegate NCInitContext; + public readonly NCReInitContextDelegate NCReInitContext; + public readonly NCDestroyContextDelegate NCDestroyContext; + public readonly NCGetPublicKeyDelegate NCGetPublicKey; + public readonly NCValidateSecretKeyDelegate NCValidateSecretKey; + public readonly NCSignDataDelegate NCSignData; + public readonly NCVerifyDataDelegate NCVerifyData; + public readonly NCEncryptDelegate NCEncrypt; + public readonly NCDecryptDelegate NCDecrypt; + public readonly NCVerifyMacDelegate NCVerifyMac; + public readonly NCComputeMacDelegate NCComputeMac; + public readonly NCSetEncryptionDataDelegate NCSetEncryptionData; + public readonly NCSetEncryptionPropertyDelegate NCSetEncryptionProperty; + public readonly NCSetEncryptionPropertyExDelegate NCSetEncryptionPropertyEx; + +#if DEBUG + public readonly NCGetConversationKeyDelegate NCGetConversationKey; +#endif + + private FunctionTable(SafeLibraryHandle library) + { + //Load the required high-level api functions + NCGetContextStructSize = library.DangerousGetFunction<NCGetContextStructSizeDelegate>(); + NCInitContext = library.DangerousGetFunction<NCInitContextDelegate>(); + NCReInitContext = library.DangerousGetFunction<NCReInitContextDelegate>(); + NCDestroyContext = library.DangerousGetFunction<NCDestroyContextDelegate>(); + NCGetPublicKey = library.DangerousGetFunction<NCGetPublicKeyDelegate>(); + NCValidateSecretKey = library.DangerousGetFunction<NCValidateSecretKeyDelegate>(); + NCSignData = library.DangerousGetFunction<NCSignDataDelegate>(); + NCVerifyData = library.DangerousGetFunction<NCVerifyDataDelegate>(); + NCSignData = library.DangerousGetFunction<NCSignDataDelegate>(); + NCVerifyData = library.DangerousGetFunction<NCVerifyDataDelegate>(); + NCEncrypt = library.DangerousGetFunction<NCEncryptDelegate>(); + NCDecrypt = library.DangerousGetFunction<NCDecryptDelegate>(); + NCVerifyMac = library.DangerousGetFunction<NCVerifyMacDelegate>(); + NCComputeMac = library.DangerousGetFunction<NCComputeMacDelegate>(); + NCSetEncryptionData = library.DangerousGetFunction<NCSetEncryptionDataDelegate>(); + NCSetEncryptionProperty = library.DangerousGetFunction<NCSetEncryptionPropertyDelegate>(); + NCSetEncryptionPropertyEx = library.DangerousGetFunction<NCSetEncryptionPropertyExDelegate>(); + +#if DEBUG + NCGetConversationKey = library.DangerousGetFunction<NCGetConversationKeyDelegate>(); +#endif + } + + /// <summary> + /// Initialize a new function table from the specified library + /// </summary> + /// <param name="library"></param> + /// <returns>The function table structure</returns> + /// <exception cref="MissingMemberException"></exception> + /// <exception cref="EntryPointNotFoundException"></exception> + public static FunctionTable BuildFunctionTable(SafeLibraryHandle library) => new (library); + + /* + * ################################################ + * + * Functions match the noscrypt.h header file + * + * ################################################ + */ + + //FUCNTIONS + [SafeMethodName("NCGetContextStructSize")] + internal delegate uint NCGetContextStructSizeDelegate(); + + [SafeMethodName("NCInitContext")] + internal delegate NCResult NCInitContextDelegate(IntPtr ctx, byte* entropy32); + + [SafeMethodName("NCReInitContext")] + internal delegate NCResult NCReInitContextDelegate(IntPtr ctx, byte* entropy32); + + [SafeMethodName("NCDestroyContext")] + internal delegate NCResult NCDestroyContextDelegate(IntPtr ctx); + + [SafeMethodName("NCGetPublicKey")] + internal delegate NCResult NCGetPublicKeyDelegate(IntPtr ctx, NCSecretKey* secKey, NCPublicKey* publicKey); + + [SafeMethodName("NCValidateSecretKey")] + internal delegate NCResult NCValidateSecretKeyDelegate(IntPtr ctx, NCSecretKey* secKey); + + [SafeMethodName("NCSignData")] + internal delegate NCResult NCSignDataDelegate(IntPtr ctx, NCSecretKey* sk, byte* random32, byte* data, uint dataSize, byte* sig64); + + [SafeMethodName("NCVerifyData")] + internal delegate NCResult NCVerifyDataDelegate(IntPtr ctx, NCPublicKey* sk, byte* data, uint dataSize, byte* sig64); + + [SafeMethodName("NCEncrypt")] + internal delegate NCResult NCEncryptDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCEncryptionArgs* data); + + [SafeMethodName("NCDecrypt")] + internal delegate NCResult NCDecryptDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCEncryptionArgs* data); + + [SafeMethodName("NCVerifyMac")] + internal delegate NCResult NCVerifyMacDelegate(IntPtr ctx, NCSecretKey* sk, NCPublicKey* pk, NCMacVerifyArgs* args); + + [SafeMethodName("NCComputeMac")] + internal delegate NCResult NCComputeMacDelegate(IntPtr ctx, byte* hmacKey32, byte* payload, uint payloadSize, byte* hmacOut32); + + [SafeMethodName("NCGetConversationKey")] + internal delegate NCResult NCGetConversationKeyDelegate(nint ctx, NCSecretKey* sk, NCPublicKey* pk, byte* keyOut32); + + [SafeMethodName("NCSetEncryptionProperty")] + internal delegate NCResult NCSetEncryptionPropertyDelegate(NCEncryptionArgs* args, uint property, uint value); + + [SafeMethodName("NCSetEncryptionPropertyEx")] + internal delegate NCResult NCSetEncryptionPropertyExDelegate(NCEncryptionArgs* args, uint property, byte* value, uint valueLen); + + [SafeMethodName("NCSetEncryptionData")] + internal delegate NCResult NCSetEncryptionDataDelegate(NCEncryptionArgs* args, byte* input, byte* output, uint dataSize); + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCEncryptionArgs.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCEncryptionArgs.cs new file mode 100644 index 0000000..91f0ff5 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCEncryptionArgs.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; +using System.Runtime.InteropServices; + +namespace VNLib.Utils.Cryptography.Noscrypt.@internal +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct NCEncryptionArgs + { + public byte* nonceData; + public byte* keyData; + public byte* inputData; + public byte* outputData; + public uint dataSize; + public uint version; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCMacVerifyArgs.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCMacVerifyArgs.cs new file mode 100644 index 0000000..8a9ba1f --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/src/internal/NCMacVerifyArgs.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +using System; + +namespace VNLib.Utils.Cryptography.Noscrypt.@internal +{ + internal unsafe struct NCMacVerifyArgs + { + /* The message authentication code certifying the Nip44 payload */ + public byte* mac32; + + /* The nonce used for the original message encryption */ + public byte* nonce32; + + /* The message payload data */ + public byte* payload; + + /* The size of the payload data */ + public uint payloadSize; + } +} diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs new file mode 100644 index 0000000..73a62d9 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/LibNoscryptTests.cs @@ -0,0 +1,371 @@ +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; + +namespace VNLib.Utils.Cryptography.Noscrypt.Tests +{ + [TestClass()] + public class LibNoscryptTests : IDisposable + { + + const string NoscryptLibWinDebug = @"../../../../../../../out/build/x64-debug/Debug/noscrypt.dll"; + + + //Keys generated using npx noskey package + const string TestPrivateKeyHex = "98c642360e7163a66cee5d9a842b252345b6f3f3e21bd3b7635d5e6c20c7ea36"; + const string TestPublicKeyHex = "0db15182c4ad3418b4fbab75304be7ade9cfa430a21c1c5320c9298f54ea5406"; + + 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 = NoscryptLibrary.Load(NoscryptLibWinDebug); + _testVectors = JsonDocument.Parse(File.ReadAllText(Nip44VectorTestFile)); + } + + + [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); + } + + [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); + + //validate the secret key + Assert.IsTrue(crypto.ValidateSecretKey(in NCUtil.AsSecretKey(secretKey))); + + //Generate the public key + crypto.GetPublicKey( + in NCUtil.AsSecretKey(secretKey), + ref NCUtil.AsPublicKey(publicKey) + ); + + //Make sure the does not contain all zeros + Assert.IsTrue(publicKey.ToArray().Any(b => b != 0)); + } + + [TestMethod()] + public void TestGetPublicKey() + { + //Random context seed + ReadOnlySpan<byte> seed = RandomHash.GetRandomBytes(32); + + using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + + //Test known key 1 + TestKnownKeys( + crypto, + Convert.FromHexString(TestPrivateKeyHex), + Convert.FromHexString(TestPublicKeyHex) + ); + + //Test known key 2 + TestKnownKeys( + crypto, + Convert.FromHexString(TestPrivateKeyHex2), + Convert.FromHexString(TestPublicKeyHex2) + ); + + + static void TestKnownKeys(NostrCrypto lib, ReadOnlySpan<byte> knownSec, ReadOnlySpan<byte> kownPub) + { + NCPublicKey pubKey; + + //Invoke test function + lib.GetPublicKey( + in NCUtil.AsSecretKey(knownSec), + ref pubKey + ); + + //Make sure known key matches the generated key + Assert.IsTrue(pubKey.AsSpan().SequenceEqual(kownPub)); + } + } + + //Test argument validations + [TestMethod()] + public void TestPublicApiArgValidations() + { + //Random context seed + ReadOnlySpan<byte> seed = RandomHash.GetRandomBytes(32); + + using NostrCrypto crypto = _testLib.InitializeCrypto(MemoryUtil.Shared, seed); + + 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)); + + //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 CalcPaddedLenTest() + { + //Get valid padding test vectors + (int, int)[] paddedSizes = _testVectors.RootElement.GetProperty("v2") + .GetProperty("valid") + .GetProperty("calc_padded_len") + .EnumerateArray() + .Select(v => + { + int[] testVals = v.Deserialize<int[]>()!; + return (testVals[0], testVals[1]); + }).ToArray(); + + + foreach ((int len, int paddedLen) in paddedSizes) + { + Assert.AreEqual<int>(paddedLen, Nip44Util.CalcBufferSize(len) - 2); + } + } + + [TestMethod()] + public void CorrectEncryptionTest() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + + using NostrMessageCipher cipher = NostrMessageCipher.CreateNip44Cipher(nc); + + 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); + + int outBufferSize = cipher.GetMessageBufferSize(plainText.Length); + + Span<byte> encryptedNote = new byte[outBufferSize]; + + cipher.SetSecretKey(secKey1) + .SetPublicKey(in pub2) + .SetNonce(nonce); + + int written = cipher.EncryptMessage(plainText, encryptedNote); + Assert.IsTrue(written > 0); + + encryptedNote = encryptedNote[..written]; + + //Make sure the cipher text matches the expected payload + if (!encryptedNote.SequenceEqual(message)) + { + Console.WriteLine($"Input data: {v.plaintext}"); + Console.WriteLine($" \n{Convert.ToHexString(encryptedNote)}\n{Convert.ToHexString(message)}"); + Assert.Fail($"Cipher text does not match expected message"); + } + } + } + + [TestMethod()] + public void ValidateMessageMacs() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + + foreach (EncryptionVector v in GetEncryptionVectors()) + { + ReadOnlySpan<byte> secKey1 = Convert.FromHexString(v.sec1); + ReadOnlySpan<byte> secKey2 = Convert.FromHexString(v.sec2); + ReadOnlySpan<byte> message = Convert.FromBase64String(v.payload); + + Nip44MessageSegments nip44Message = new(message); + Assert.AreEqual<byte>(nip44Message.Version, 0x02); + + NCPublicKey pub2; + + //Recover public key2 + nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pub2); + + bool success = nc.VerifyMac( + in NCUtil.AsSecretKey(secKey1), + in pub2, + nip44Message.Nonce, + nip44Message.Mac, + nip44Message.NonceAndCiphertext + ); + + if (!success) + { + Console.WriteLine($"Failed to validate MAC for message: {v.payload}"); + Console.Write($"Mac hex value: {Convert.ToHexString(nip44Message.Mac)}"); + Assert.Fail("Failed to validate MAC for message"); + } + } + } + + //Converstation key is only available in debug builds +#if DEBUG + + [TestMethod()] + public void ConverstationKeyTest() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, RandomHash.GetRandomBytes(32)); + + 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; + nc.GetPublicKey(in NCUtil.AsSecretKey(secKey2), ref pubkey2); + + nc.GetConverstationKey( + in NCUtil.AsSecretKey(secKey1), + in pubkey2, + convKeyOut + ); + + Assert.IsTrue(conversationKey.SequenceEqual(convKeyOut)); + + MemoryUtil.InitializeBlock(convKeyOut); + } + } +#endif + + + [TestMethod()] + public void CorrectDecryptionTest() + { + using NostrCrypto nc = _testLib.InitializeCrypto(MemoryUtil.Shared, NcFallbackRandom.Shared); + + using NostrMessageCipher msgCipher = NostrMessageCipher.CreateNip44Cipher(nc); + + 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> nonce = Convert.FromHexString(vector.nonce); + ReadOnlySpan<byte> message = Convert.FromBase64String(vector.payload); + + NCPublicKey pub1 = default; + + //Recover public keys + nc.GetPublicKey(in NCUtil.AsSecretKey(secKey1), ref pub1); + + msgCipher.SetPublicKey(in pub1) + .SetSecretKey(secKey2); + + int outLen = msgCipher.DecryptMessage(message, ptBuffer.Span); + + Assert.IsTrue(outLen > 0); + + Span<byte> plaintext = ptBuffer.AsSpan(0, outLen); + + 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"); + } + else + { + Assert.IsTrue(nonce.SequenceEqual(msgCipher.Nonce)); + } + } + } + + + static byte[] CreateAndFormatPlaintextOutputBuffer(ReadOnlySpan<byte> plaintext) + { + //Compute the required plaintext buffer size + int paddedSize = Nip44Util.CalcBufferSize(plaintext.Length); + + byte[] data = new byte[paddedSize]; + + //Format the plaintext buffer + Nip44Util.FormatBuffer(plaintext, data, true); + + return data; + } + + static byte[] BuildMacData(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> nonce) + { + byte[] macData = new byte[ciphertext.Length + nonce.Length]; + + //Nonce then cipher text + nonce.CopyTo(macData); + ciphertext.CopyTo(macData.AsSpan(nonce.Length)); + + return macData; + } + + 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/NVault.Crypto.NoscryptTests.csproj new file mode 100644 index 0000000..5917b15 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/NVault.Crypto.NoscryptTests.csproj @@ -0,0 +1,32 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + + <IsPackable>false</IsPackable> + <IsTestProject>true</IsTestProject> + </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="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> diff --git a/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/nip44.vectors.json b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/nip44.vectors.json new file mode 100644 index 0000000..5353660 --- /dev/null +++ b/wrappers/dotnet/VNLib.Utils.Cryptography.Noscrypt/tests/nip44.vectors.json @@ -0,0 +1,648 @@ +{ + "v2": { + "valid": { + "get_conversation_key": [ + { + "sec1": "315e59ff51cb9209768cf7da80791ddcaae56ac9775eb25b6dee1234bc5d2268", + "pub2": "c2f9d9948dc8c7c38321e4b85c8558872eafa0641cd269db76848a6073e69133", + "conversation_key": "3dfef0ce2a4d80a25e7a328accf73448ef67096f65f79588e358d9a0eb9013f1" + }, + { + "sec1": "a1e37752c9fdc1273be53f68c5f74be7c8905728e8de75800b94262f9497c86e", + "pub2": "03bb7947065dde12ba991ea045132581d0954f042c84e06d8c00066e23c1a800", + "conversation_key": "4d14f36e81b8452128da64fe6f1eae873baae2f444b02c950b90e43553f2178b" + }, + { + "sec1": "98a5902fd67518a0c900f0fb62158f278f94a21d6f9d33d30cd3091195500311", + "pub2": "aae65c15f98e5e677b5050de82e3aba47a6fe49b3dab7863cf35d9478ba9f7d1", + "conversation_key": "9c00b769d5f54d02bf175b7284a1cbd28b6911b06cda6666b2243561ac96bad7" + }, + { + "sec1": "86ae5ac8034eb2542ce23ec2f84375655dab7f836836bbd3c54cefe9fdc9c19f", + "pub2": "59f90272378089d73f1339710c02e2be6db584e9cdbe86eed3578f0c67c23585", + "conversation_key": "19f934aafd3324e8415299b64df42049afaa051c71c98d0aa10e1081f2e3e2ba" + }, + { + "sec1": "2528c287fe822421bc0dc4c3615878eb98e8a8c31657616d08b29c00ce209e34", + "pub2": "f66ea16104c01a1c532e03f166c5370a22a5505753005a566366097150c6df60", + "conversation_key": "c833bbb292956c43366145326d53b955ffb5da4e4998a2d853611841903f5442" + }, + { + "sec1": "49808637b2d21129478041813aceb6f2c9d4929cd1303cdaf4fbdbd690905ff2", + "pub2": "74d2aab13e97827ea21baf253ad7e39b974bb2498cc747cdb168582a11847b65", + "conversation_key": "4bf304d3c8c4608864c0fe03890b90279328cd24a018ffa9eb8f8ccec06b505d" + }, + { + "sec1": "af67c382106242c5baabf856efdc0629cc1c5b4061f85b8ceaba52aa7e4b4082", + "pub2": "bdaf0001d63e7ec994fad736eab178ee3c2d7cfc925ae29f37d19224486db57b", + "conversation_key": "a3a575dd66d45e9379904047ebfb9a7873c471687d0535db00ef2daa24b391db" + }, + { + "sec1": "0e44e2d1db3c1717b05ffa0f08d102a09c554a1cbbf678ab158b259a44e682f1", + "pub2": "1ffa76c5cc7a836af6914b840483726207cb750889753d7499fb8b76aa8fe0de", + "conversation_key": "a39970a667b7f861f100e3827f4adbf6f464e2697686fe1a81aeda817d6b8bdf" + }, + { + "sec1": "5fc0070dbd0666dbddc21d788db04050b86ed8b456b080794c2a0c8e33287bb6", + "pub2": "31990752f296dd22e146c9e6f152a269d84b241cc95bb3ff8ec341628a54caf0", + "conversation_key": "72c21075f4b2349ce01a3e604e02a9ab9f07e35dd07eff746de348b4f3c6365e" + }, + { + "sec1": "1b7de0d64d9b12ddbb52ef217a3a7c47c4362ce7ea837d760dad58ab313cba64", + "pub2": "24383541dd8083b93d144b431679d70ef4eec10c98fceef1eff08b1d81d4b065", + "conversation_key": "dd152a76b44e63d1afd4dfff0785fa07b3e494a9e8401aba31ff925caeb8f5b1" + }, + { + "sec1": "df2f560e213ca5fb33b9ecde771c7c0cbd30f1cf43c2c24de54480069d9ab0af", + "pub2": "eeea26e552fc8b5e377acaa03e47daa2d7b0c787fac1e0774c9504d9094c430e", + "conversation_key": "770519e803b80f411c34aef59c3ca018608842ebf53909c48d35250bd9323af6" + }, + { + "sec1": "cffff919fcc07b8003fdc63bc8a00c0f5dc81022c1c927c62c597352190d95b9", + "pub2": "eb5c3cca1a968e26684e5b0eb733aecfc844f95a09ac4e126a9e58a4e4902f92", + "conversation_key": "46a14ee7e80e439ec75c66f04ad824b53a632b8409a29bbb7c192e43c00bb795" + }, + { + "sec1": "64ba5a685e443e881e9094647ddd32db14444bb21aa7986beeba3d1c4673ba0a", + "pub2": "50e6a4339fac1f3bf86f2401dd797af43ad45bbf58e0801a7877a3984c77c3c4", + "conversation_key": "968b9dbbfcede1664a4ca35a5d3379c064736e87aafbf0b5d114dff710b8a946" + }, + { + "sec1": "dd0c31ccce4ec8083f9b75dbf23cc2878e6d1b6baa17713841a2428f69dee91a", + "pub2": "b483e84c1339812bed25be55cff959778dfc6edde97ccd9e3649f442472c091b", + "conversation_key": "09024503c7bde07eb7865505891c1ea672bf2d9e25e18dd7a7cea6c69bf44b5d" + }, + { + "sec1": "af71313b0d95c41e968a172b33ba5ebd19d06cdf8a7a98df80ecf7af4f6f0358", + "pub2": "2a5c25266695b461ee2af927a6c44a3c598b8095b0557e9bd7f787067435bc7c", + "conversation_key": "fe5155b27c1c4b4e92a933edae23726a04802a7cc354a77ac273c85aa3c97a92" + }, + { + "sec1": "6636e8a389f75fe068a03b3edb3ea4a785e2768e3f73f48ffb1fc5e7cb7289dc", + "pub2": "514eb2064224b6a5829ea21b6e8f7d3ea15ff8e70e8555010f649eb6e09aec70", + "conversation_key": "ff7afacd4d1a6856d37ca5b546890e46e922b508639214991cf8048ddbe9745c" + }, + { + "sec1": "94b212f02a3cfb8ad147d52941d3f1dbe1753804458e6645af92c7b2ea791caa", + "pub2": "f0cac333231367a04b652a77ab4f8d658b94e86b5a8a0c472c5c7b0d4c6a40cc", + "conversation_key": "e292eaf873addfed0a457c6bd16c8effde33d6664265697f69f420ab16f6669b" + }, + { + "sec1": "aa61f9734e69ae88e5d4ced5aae881c96f0d7f16cca603d3bed9eec391136da6", + "pub2": "4303e5360a884c360221de8606b72dd316da49a37fe51e17ada4f35f671620a6", + "conversation_key": "8e7d44fd4767456df1fb61f134092a52fcd6836ebab3b00766e16732683ed848" + }, + { + "sec1": "5e914bdac54f3f8e2cba94ee898b33240019297b69e96e70c8a495943a72fc98", + "pub2": "5bd097924f606695c59f18ff8fd53c174adbafaaa71b3c0b4144a3e0a474b198", + "conversation_key": "f5a0aecf2984bf923c8cd5e7bb8be262d1a8353cb93959434b943a07cf5644bc" + }, + { + "sec1": "8b275067add6312ddee064bcdbeb9d17e88aa1df36f430b2cea5cc0413d8278a", + "pub2": "65bbbfca819c90c7579f7a82b750a18c858db1afbec8f35b3c1e0e7b5588e9b8", + "conversation_key": "2c565e7027eb46038c2263563d7af681697107e975e9914b799d425effd248d6" + }, + { + "sec1": "1ac848de312285f85e0f7ec208aac20142a1f453402af9b34ec2ec7a1f9c96fc", + "pub2": "45f7318fe96034d23ee3ddc25b77f275cc1dd329664dd51b89f89c4963868e41", + "conversation_key": "b56e970e5057a8fd929f8aad9248176b9af87819a708d9ddd56e41d1aec74088" + }, + { + "sec1": "295a1cf621de401783d29d0e89036aa1c62d13d9ad307161b4ceb535ba1b40e6", + "pub2": "840115ddc7f1034d3b21d8e2103f6cb5ab0b63cf613f4ea6e61ae3d016715cdd", + "conversation_key": "b4ee9c0b9b9fef88975773394f0a6f981ca016076143a1bb575b9ff46e804753" + }, + { + "sec1": "a28eed0fe977893856ab9667e06ace39f03abbcdb845c329a1981be438ba565d", + "pub2": "b0f38b950a5013eba5ab4237f9ed29204a59f3625c71b7e210fec565edfa288c", + "conversation_key": "9d3a802b45bc5aeeb3b303e8e18a92ddd353375710a31600d7f5fff8f3a7285b" + }, + { + "sec1": "7ab65af72a478c05f5c651bdc4876c74b63d20d04cdbf71741e46978797cd5a4", + "pub2": "f1112159161b568a9cb8c9dd6430b526c4204bcc8ce07464b0845b04c041beda", + "conversation_key": "943884cddaca5a3fef355e9e7f08a3019b0b66aa63ec90278b0f9fdb64821e79" + }, + { + "sec1": "95c79a7b75ba40f2229e85756884c138916f9d103fc8f18acc0877a7cceac9fe", + "pub2": "cad76bcbd31ca7bbda184d20cc42f725ed0bb105b13580c41330e03023f0ffb3", + "conversation_key": "81c0832a669eea13b4247c40be51ccfd15bb63fcd1bba5b4530ce0e2632f301b" + }, + { + "sec1": "baf55cc2febd4d980b4b393972dfc1acf49541e336b56d33d429bce44fa12ec9", + "pub2": "0c31cf87fe565766089b64b39460ebbfdedd4a2bc8379be73ad3c0718c912e18", + "conversation_key": "37e2344da9ecdf60ae2205d81e89d34b280b0a3f111171af7e4391ded93b8ea6" + }, + { + "sec1": "6eeec45acd2ed31693c5256026abf9f072f01c4abb61f51cf64e6956b6dc8907", + "pub2": "e501b34ed11f13d816748c0369b0c728e540df3755bab59ed3327339e16ff828", + "conversation_key": "afaa141b522ddb27bb880d768903a7f618bb8b6357728cae7fb03af639b946e6" + }, + { + "sec1": "261a076a9702af1647fb343c55b3f9a4f1096273002287df0015ba81ce5294df", + "pub2": "b2777c863878893ae100fb740c8fab4bebd2bf7be78c761a75593670380a6112", + "conversation_key": "76f8d2853de0734e51189ced523c09427c3e46338b9522cd6f74ef5e5b475c74" + }, + { + "sec1": "ed3ec71ca406552ea41faec53e19f44b8f90575eda4b7e96380f9cc73c26d6f3", + "pub2": "86425951e61f94b62e20cae24184b42e8e17afcf55bafa58645efd0172624fae", + "conversation_key": "f7ffc520a3a0e9e9b3c0967325c9bf12707f8e7a03f28b6cd69ae92cf33f7036" + }, + { + "sec1": "5a788fc43378d1303ac78639c59a58cb88b08b3859df33193e63a5a3801c722e", + "pub2": "a8cba2f87657d229db69bee07850fd6f7a2ed070171a06d006ec3a8ac562cf70", + "conversation_key": "7d705a27feeedf78b5c07283362f8e361760d3e9f78adab83e3ae5ce7aeb6409" + }, + { + "sec1": "63bffa986e382b0ac8ccc1aa93d18a7aa445116478be6f2453bad1f2d3af2344", + "pub2": "b895c70a83e782c1cf84af558d1038e6b211c6f84ede60408f519a293201031d", + "conversation_key": "3a3b8f00d4987fc6711d9be64d9c59cf9a709c6c6481c2cde404bcc7a28f174e" + }, + { + "sec1": "e4a8bcacbf445fd3721792b939ff58e691cdcba6a8ba67ac3467b45567a03e5c", + "pub2": "b54053189e8c9252c6950059c783edb10675d06d20c7b342f73ec9fa6ed39c9d", + "conversation_key": "7b3933b4ef8189d347169c7955589fc1cfc01da5239591a08a183ff6694c44ad" + }, + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", + "pub2": "0000000000000000000000000000000000000000000000000000000000000002", + "conversation_key": "8b6392dbf2ec6a2b2d5b1477fc2be84d63ef254b667cadd31bd3f444c44ae6ba", + "note": "sec1 = n-2, pub2: random, 0x02" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000002", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeb", + "conversation_key": "be234f46f60a250bef52a5ee34c758800c4ca8e5030bf4cc1a31d37ba2104d43", + "note": "sec1 = 2, pub2: rand" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000001", + "pub2": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "conversation_key": "3b4610cb7189beb9cc29eb3716ecc6102f1247e8f3101a03a1787d8908aeb54e", + "note": "sec1 == pub2" + } + ], + "get_message_keys": { + "conversation_key": "a1a3d60f3470a8612633924e91febf96dc5366ce130f658b1f0fc652c20b3b54", + "keys": [ + { + "nonce": "e1e6f880560d6d149ed83dcc7e5861ee62a5ee051f7fde9975fe5d25d2a02d72", + "chacha_key": "f145f3bed47cb70dbeaac07f3a3fe683e822b3715edb7c4fe310829014ce7d76", + "chacha_nonce": "c4ad129bb01180c0933a160c", + "hmac_key": "027c1db445f05e2eee864a0975b0ddef5b7110583c8c192de3732571ca5838c4" + }, + { + "nonce": "e1d6d28c46de60168b43d79dacc519698512ec35e8ccb12640fc8e9f26121101", + "chacha_key": "e35b88f8d4a8f1606c5082f7a64b100e5d85fcdb2e62aeafbec03fb9e860ad92", + "chacha_nonce": "22925e920cee4a50a478be90", + "hmac_key": "46a7c55d4283cb0df1d5e29540be67abfe709e3b2e14b7bf9976e6df994ded30" + }, + { + "nonce": "cfc13bef512ac9c15951ab00030dfaf2626fdca638dedb35f2993a9eeb85d650", + "chacha_key": "020783eb35fdf5b80ef8c75377f4e937efb26bcbad0e61b4190e39939860c4bf", + "chacha_nonce": "d3594987af769a52904656ac", + "hmac_key": "237ec0ccb6ebd53d179fa8fd319e092acff599ef174c1fdafd499ef2b8dee745" + }, + { + "nonce": "ea6eb84cac23c5c1607c334e8bdf66f7977a7e374052327ec28c6906cbe25967", + "chacha_key": "ff68db24b34fa62c78ac5ffeeaf19533afaedf651fb6a08384e46787f6ce94be", + "chacha_nonce": "50bb859aa2dde938cc49ec7a", + "hmac_key": "06ff32e1f7b29753a727d7927b25c2dd175aca47751462d37a2039023ec6b5a6" + }, + { + "nonce": "8c2e1dd3792802f1f9f7842e0323e5d52ad7472daf360f26e15f97290173605d", + "chacha_key": "2f9daeda8683fdeede81adac247c63cc7671fa817a1fd47352e95d9487989d8b", + "chacha_nonce": "400224ba67fc2f1b76736916", + "hmac_key": "465c05302aeeb514e41c13ed6405297e261048cfb75a6f851ffa5b445b746e4b" + }, + { + "nonce": "05c28bf3d834fa4af8143bf5201a856fa5fac1a3aee58f4c93a764fc2f722367", + "chacha_key": "1e3d45777025a035be566d80fd580def73ed6f7c043faec2c8c1c690ad31c110", + "chacha_nonce": "021905b1ea3afc17cb9bf96f", + "hmac_key": "74a6e481a89dcd130aaeb21060d7ec97ad30f0007d2cae7b1b11256cc70dfb81" + }, + { + "nonce": "5e043fb153227866e75a06d60185851bc90273bfb93342f6632a728e18a07a17", + "chacha_key": "1ea72c9293841e7737c71567d8120145a58991aaa1c436ef77bf7adb83f882f1", + "chacha_nonce": "72f69a5a5f795465cee59da8", + "hmac_key": "e9daa1a1e9a266ecaa14e970a84bce3fbbf329079bbccda626582b4e66a0d4c9" + }, + { + "nonce": "7be7338eaf06a87e274244847fe7a97f5c6a91f44adc18fcc3e411ad6f786dbf", + "chacha_key": "881e7968a1f0c2c80742ee03cd49ea587e13f22699730f1075ade01931582bf6", + "chacha_nonce": "6e69be92d61c04a276021565", + "hmac_key": "901afe79e74b19967c8829af23617d7d0ffbf1b57190c096855c6a03523a971b" + }, + { + "nonce": "94571c8d590905bad7becd892832b472f2aa5212894b6ce96e5ba719c178d976", + "chacha_key": "f80873dd48466cb12d46364a97b8705c01b9b4230cb3ec3415a6b9551dc42eef", + "chacha_nonce": "3dda53569cfcb7fac1805c35", + "hmac_key": "e9fc264345e2839a181affebc27d2f528756e66a5f87b04bf6c5f1997047051e" + }, + { + "nonce": "13a6ee974b1fd759135a2c2010e3cdda47081c78e771125e4f0c382f0284a8cb", + "chacha_key": "bc5fb403b0bed0d84cf1db872b6522072aece00363178c98ad52178d805fca85", + "chacha_nonce": "65064239186e50304cc0f156", + "hmac_key": "e872d320dde4ed3487958a8e43b48aabd3ced92bc24bb8ff1ccb57b590d9701a" + }, + { + "nonce": "082fecdb85f358367b049b08be0e82627ae1d8edb0f27327ccb593aa2613b814", + "chacha_key": "1fbdb1cf6f6ea816349baf697932b36107803de98fcd805ebe9849b8ad0e6a45", + "chacha_nonce": "2e605e1d825a3eaeb613db9c", + "hmac_key": "fae910f591cf3c7eb538c598583abad33bc0a03085a96ca4ea3a08baf17c0eec" + }, + { + "nonce": "4c19020c74932c30ec6b2d8cd0d5bb80bd0fc87da3d8b4859d2fb003810afd03", + "chacha_key": "1ab9905a0189e01cda82f843d226a82a03c4f5b6dbea9b22eb9bc953ba1370d4", + "chacha_nonce": "cbb2530ea653766e5a37a83a", + "hmac_key": "267f68acac01ac7b34b675e36c2cef5e7b7a6b697214add62a491bedd6efc178" + }, + { + "nonce": "67723a3381497b149ce24814eddd10c4c41a1e37e75af161930e6b9601afd0ff", + "chacha_key": "9ecbd25e7e2e6c97b8c27d376dcc8c5679da96578557e4e21dba3a7ef4e4ac07", + "chacha_nonce": "ef649fcf335583e8d45e3c2e", + "hmac_key": "04dbbd812fa8226fdb45924c521a62e3d40a9e2b5806c1501efdeba75b006bf1" + }, + { + "nonce": "42063fe80b093e8619b1610972b4c3ab9e76c14fd908e642cd4997cafb30f36c", + "chacha_key": "211c66531bbcc0efcdd0130f9f1ebc12a769105eb39608994bcb188fa6a73a4a", + "chacha_nonce": "67803605a7e5010d0f63f8c8", + "hmac_key": "e840e4e8921b57647369d121c5a19310648105dbdd008200ebf0d3b668704ff8" + }, + { + "nonce": "b5ac382a4be7ac03b554fe5f3043577b47ea2cd7cfc7e9ca010b1ffbb5cf1a58", + "chacha_key": "b3b5f14f10074244ee42a3837a54309f33981c7232a8b16921e815e1f7d1bb77", + "chacha_nonce": "4e62a0073087ed808be62469", + "hmac_key": "c8efa10230b5ea11633816c1230ca05fa602ace80a7598916d83bae3d3d2ccd7" + }, + { + "nonce": "e9d1eba47dd7e6c1532dc782ff63125db83042bb32841db7eeafd528f3ea7af9", + "chacha_key": "54241f68dc2e50e1db79e892c7c7a471856beeb8d51b7f4d16f16ab0645d2f1a", + "chacha_nonce": "a963ed7dc29b7b1046820a1d", + "hmac_key": "aba215c8634530dc21c70ddb3b3ee4291e0fa5fa79be0f85863747bde281c8b2" + }, + { + "nonce": "a94ecf8efeee9d7068de730fad8daf96694acb70901d762de39fa8a5039c3c49", + "chacha_key": "c0565e9e201d2381a2368d7ffe60f555223874610d3d91fbbdf3076f7b1374dd", + "chacha_nonce": "329bb3024461e84b2e1c489b", + "hmac_key": "ac42445491f092481ce4fa33b1f2274700032db64e3a15014fbe8c28550f2fec" + }, + { + "nonce": "533605ea214e70c25e9a22f792f4b78b9f83a18ab2103687c8a0075919eaaa53", + "chacha_key": "ab35a5e1e54d693ff023db8500d8d4e79ad8878c744e0eaec691e96e141d2325", + "chacha_nonce": "653d759042b85194d4d8c0a7", + "hmac_key": "b43628e37ba3c31ce80576f0a1f26d3a7c9361d29bb227433b66f49d44f167ba" + }, + { + "nonce": "7f38df30ceea1577cb60b355b4f5567ff4130c49e84fed34d779b764a9cc184c", + "chacha_key": "a37d7f211b84a551a127ff40908974eb78415395d4f6f40324428e850e8c42a3", + "chacha_nonce": "b822e2c959df32b3cb772a7c", + "hmac_key": "1ba31764f01f69b5c89ded2d7c95828e8052c55f5d36f1cd535510d61ba77420" + }, + { + "nonce": "11b37f9dbc4d0185d1c26d5f4ed98637d7c9701fffa65a65839fa4126573a4e5", + "chacha_key": "964f38d3a31158a5bfd28481247b18dd6e44d69f30ba2a40f6120c6d21d8a6ba", + "chacha_nonce": "5f72c5b87c590bcd0f93b305", + "hmac_key": "2fc4553e7cedc47f29690439890f9f19c1077ef3e9eaeef473d0711e04448918" + }, + { + "nonce": "8be790aa483d4cdd843189f71f135b3ec7e31f381312c8fe9f177aab2a48eafa", + "chacha_key": "95c8c74d633721a131316309cf6daf0804d59eaa90ea998fc35bac3d2fbb7a94", + "chacha_nonce": "409a7654c0e4bf8c2c6489be", + "hmac_key": "21bb0b06eb2b460f8ab075f497efa9a01c9cf9146f1e3986c3bf9da5689b6dc4" + }, + { + "nonce": "19fd2a718ea084827d6bd73f509229ddf856732108b59fc01819f611419fd140", + "chacha_key": "cc6714b9f5616c66143424e1413d520dae03b1a4bd202b82b0a89b0727f5cdc8", + "chacha_nonce": "1b7fd2534f015a8f795d8f32", + "hmac_key": "2bef39c4ce5c3c59b817e86351373d1554c98bc131c7e461ed19d96cfd6399a0" + }, + { + "nonce": "3c2acd893952b2f6d07d8aea76f545ca45961a93fe5757f6a5a80811d5e0255d", + "chacha_key": "c8de6c878cb469278d0af894bc181deb6194053f73da5014c2b5d2c8db6f2056", + "chacha_nonce": "6ffe4f1971b904a1b1a81b99", + "hmac_key": "df1cd69dd3646fca15594284744d4211d70e7d8472e545d276421fbb79559fd4" + }, + { + "nonce": "7dbea4cead9ac91d4137f1c0a6eebb6ba0d1fb2cc46d829fbc75f8d86aca6301", + "chacha_key": "c8e030f6aa680c3d0b597da9c92bb77c21c4285dd620c5889f9beba7446446b0", + "chacha_nonce": "a9b5a67d081d3b42e737d16f", + "hmac_key": "355a85f551bc3cce9a14461aa60994742c9bbb1c81a59ca102dc64e61726ab8e" + }, + { + "nonce": "45422e676cdae5f1071d3647d7a5f1f5adafb832668a578228aa1155a491f2f3", + "chacha_key": "758437245f03a88e2c6a32807edfabff51a91c81ca2f389b0b46f2c97119ea90", + "chacha_nonce": "263830a065af33d9c6c5aa1f", + "hmac_key": "7c581cf3489e2de203a95106bfc0de3d4032e9d5b92b2b61fb444acd99037e17" + }, + { + "nonce": "babc0c03fad24107ad60678751f5db2678041ff0d28671ede8d65bdf7aa407e9", + "chacha_key": "bd68a28bd48d9ffa3602db72c75662ac2848a0047a313d2ae2d6bc1ac153d7e9", + "chacha_nonce": "d0f9d2a1ace6c758f594ffdd", + "hmac_key": "eb435e3a642adfc9d59813051606fc21f81641afd58ea6641e2f5a9f123bb50a" + }, + { + "nonce": "7a1b8aac37d0d20b160291fad124ab697cfca53f82e326d78fef89b4b0ea8f83", + "chacha_key": "9e97875b651a1d30d17d086d1e846778b7faad6fcbc12e08b3365d700f62e4fe", + "chacha_nonce": "ccdaad5b3b7645be430992eb", + "hmac_key": "6f2f55cf35174d75752f63c06cc7cbc8441759b142999ed2d5a6d09d263e1fc4" + }, + { + "nonce": "8370e4e32d7e680a83862cab0da6136ef607014d043e64cdf5ecc0c4e20b3d9a", + "chacha_key": "1472bed5d19db9c546106de946e0649cd83cc9d4a66b087a65906e348dcf92e2", + "chacha_nonce": "ed02dece5fc3a186f123420b", + "hmac_key": "7b3f7739f49d30c6205a46b174f984bb6a9fc38e5ccfacef2dac04fcbd3b184e" + }, + { + "nonce": "9f1c5e8a29cd5677513c2e3a816551d6833ee54991eb3f00d5b68096fc8f0183", + "chacha_key": "5e1a7544e4d4dafe55941fcbdf326f19b0ca37fc49c4d47e9eec7fb68cde4975", + "chacha_nonce": "7d9acb0fdc174e3c220f40de", + "hmac_key": "e265ab116fbbb86b2aefc089a0986a0f5b77eda50c7410404ad3b4f3f385c7a7" + }, + { + "nonce": "c385aa1c37c2bfd5cc35fcdbdf601034d39195e1cabff664ceb2b787c15d0225", + "chacha_key": "06bf4e60677a13e54c4a38ab824d2ef79da22b690da2b82d0aa3e39a14ca7bdd", + "chacha_nonce": "26b450612ca5e905b937e147", + "hmac_key": "22208152be2b1f5f75e6bfcc1f87763d48bb7a74da1be3d102096f257207f8b3" + }, + { + "nonce": "3ff73528f88a50f9d35c0ddba4560bacee5b0462d0f4cb6e91caf41847040ce4", + "chacha_key": "850c8a17a23aa761d279d9901015b2bbdfdff00adbf6bc5cf22bd44d24ecabc9", + "chacha_nonce": "4a296a1fb0048e5020d3b129", + "hmac_key": "b1bf49a533c4da9b1d629b7ff30882e12d37d49c19abd7b01b7807d75ee13806" + }, + { + "nonce": "2dcf39b9d4c52f1cb9db2d516c43a7c6c3b8c401f6a4ac8f131a9e1059957036", + "chacha_key": "17f8057e6156ba7cc5310d01eda8c40f9aa388f9fd1712deb9511f13ecc37d27", + "chacha_nonce": "a8188daff807a1182200b39d", + "hmac_key": "47b89da97f68d389867b5d8a2d7ba55715a30e3d88a3cc11f3646bc2af5580ef" + } + ] + }, + "calc_padded_len": [ + [ 16, 32 ], + [ 32, 32 ], + [ 33, 64 ], + [ 37, 64 ], + [ 45, 64 ], + [ 49, 64 ], + [ 64, 64 ], + [ 65, 96 ], + [ 100, 128 ], + [ 111, 128 ], + [ 200, 224 ], + [ 250, 256 ], + [ 320, 320 ], + [ 383, 384 ], + [ 384, 384 ], + [ 400, 448 ], + [ 500, 512 ], + [ 512, 512 ], + [ 515, 640 ], + [ 700, 768 ], + [ 800, 896 ], + [ 900, 1024 ], + [ 1020, 1024 ], + [ 65536, 65536 ] + ], + "encrypt_decrypt": [ + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000001", + "sec2": "0000000000000000000000000000000000000000000000000000000000000002", + "conversation_key": "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d", + "nonce": "0000000000000000000000000000000000000000000000000000000000000001", + "plaintext": "a", + "payload": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000002", + "sec2": "0000000000000000000000000000000000000000000000000000000000000001", + "conversation_key": "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d", + "nonce": "f00000000000000000000000000000f00000000000000000000000000000000f", + "plaintext": "🍕🫃", + "payload": "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj" + }, + { + "sec1": "5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", + "sec2": "4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d", + "conversation_key": "3e2b52a63be47d34fe0a80e34e73d436d6963bc8f39827f327057a9986c20a45", + "nonce": "b635236c42db20f021bb8d1cdff5ca75dd1a0cc72ea742ad750f33010b24f73b", + "plaintext": "表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀", + "payload": "ArY1I2xC2yDwIbuNHN/1ynXdGgzHLqdCrXUPMwELJPc7s7JqlCMJBAIIjfkpHReBPXeoMCyuClwgbT419jUWU1PwaNl4FEQYKCDKVJz+97Mp3K+Q2YGa77B6gpxB/lr1QgoqpDf7wDVrDmOqGoiPjWDqy8KzLueKDcm9BVP8xeTJIxs=" + }, + { + "sec1": "8f40e50a84a7462e2b8d24c28898ef1f23359fff50d8c509e6fb7ce06e142f9c", + "sec2": "b9b0a1e9cc20100c5faa3bbe2777303d25950616c4c6a3fa2e3e046f936ec2ba", + "conversation_key": "d5a2f879123145a4b291d767428870f5a8d9e5007193321795b40183d4ab8c2b", + "nonce": "b20989adc3ddc41cd2c435952c0d59a91315d8c5218d5040573fc3749543acaf", + "plaintext": "ability🤝的 ȺȾ", + "payload": "ArIJia3D3cQc0sQ1lSwNWakTFdjFIY1QQFc/w3SVQ6yvbG2S0x4Yu86QGwPTy7mP3961I1XqB6SFFTzqDZZavhxoWMj7mEVGMQIsh2RLWI5EYQaQDIePSnXPlzf7CIt+voTD" + }, + { + "sec1": "875adb475056aec0b4809bd2db9aa00cff53a649e7b59d8edcbf4e6330b0995c", + "sec2": "9c05781112d5b0a2a7148a222e50e0bd891d6b60c5483f03456e982185944aae", + "conversation_key": "3b15c977e20bfe4b8482991274635edd94f366595b1a3d2993515705ca3cedb8", + "nonce": "8d4442713eb9d4791175cb040d98d6fc5be8864d6ec2f89cf0895a2b2b72d1b1", + "plaintext": "pepper👀їжак", + "payload": "Ao1EQnE+udR5EXXLBA2Y1vxb6IZNbsL4nPCJWisrctGxY3AduCS+jTUgAAnfvKafkmpy15+i9YMwCdccisRa8SvzW671T2JO4LFSPX31K4kYUKelSAdSPwe9NwO6LhOsnoJ+" + }, + { + "sec1": "eba1687cab6a3101bfc68fd70f214aa4cc059e9ec1b79fdb9ad0a0a4e259829f", + "sec2": "dff20d262bef9dfd94666548f556393085e6ea421c8af86e9d333fa8747e94b3", + "conversation_key": "4f1538411098cf11c8af216836444787c462d47f97287f46cf7edb2c4915b8a5", + "nonce": "2180b52ae645fcf9f5080d81b1f0b5d6f2cd77ff3c986882bb549158462f3407", + "plaintext": "( ͡° ͜ʖ ͡°)", + "payload": "AiGAtSrmRfz59QgNgbHwtdbyzXf/PJhogrtUkVhGLzQHv4qhKQwnFQ54OjVMgqCea/Vj0YqBSdhqNR777TJ4zIUk7R0fnizp6l1zwgzWv7+ee6u+0/89KIjY5q1wu6inyuiv" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "e4cd5f7ce4eea024bc71b17ad456a986a74ac426c2c62b0a15eb5c5c8f888b68", + "plaintext": "مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،", + "payload": "AuTNX3zk7qAkvHGxetRWqYanSsQmwsYrChXrXFyPiItoIBsWu1CB+sStla2M4VeANASHxM78i1CfHQQH1YbBy24Tng7emYW44ol6QkFD6D8Zq7QPl+8L1c47lx8RoODEQMvNCbOk5ffUV3/AhONHBXnffrI+0025c+uRGzfqpYki4lBqm9iYU+k3Tvjczq9wU0mkVDEaM34WiQi30MfkJdRbeeYaq6kNvGPunLb3xdjjs5DL720d61Flc5ZfoZm+CBhADy9D9XiVZYLKAlkijALJur9dATYKci6OBOoc2SJS2Clai5hOVzR0yVeyHRgRfH9aLSlWW5dXcUxTo7qqRjNf8W5+J4jF4gNQp5f5d0YA4vPAzjBwSP/5bGzNDslKfcAH" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "38d1ca0abef9e5f564e89761a86cee04574b6825d3ef2063b10ad75899e4b023", + "plaintext": "الكل في المجمو عة (5)", + "payload": "AjjRygq++eX1ZOiXYahs7gRXS2gl0+8gY7EK11iZ5LAjbOTrlfrxak5Lki42v2jMPpLSicy8eHjsWkkMtF0i925vOaKG/ZkMHh9ccQBdfTvgEGKzztedqDCAWb5TP1YwU1PsWaiiqG3+WgVvJiO4lUdMHXL7+zKKx8bgDtowzz4QAwI=" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "4f1a31909f3483a9e69c8549a55bbc9af25fa5bbecf7bd32d9896f83ef2e12e0", + "plaintext": "𝖑𝖆𝖟𝖞 社會科學院語學研究所", + "payload": "Ak8aMZCfNIOp5pyFSaVbvJryX6W77Pe9MtmJb4PvLhLgh/TsxPLFSANcT67EC1t/qxjru5ZoADjKVEt2ejdx+xGvH49mcdfbc+l+L7gJtkH7GLKpE9pQNQWNHMAmj043PAXJZ++fiJObMRR2mye5VHEANzZWkZXMrXF7YjuG10S1pOU=" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "a3e219242d85465e70adcd640b564b3feff57d2ef8745d5e7a0663b2dccceb54", + "plaintext": "🙈 🙉 🙊 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗", + "payload": "AqPiGSQthUZecK3NZAtWSz/v9X0u+HRdXnoGY7LczOtUf05aMF89q1FLwJvaFJYICZoMYgRJHFLwPiOHce7fuAc40kX0wXJvipyBJ9HzCOj7CgtnC1/cmPCHR3s5AIORmroBWglm1LiFMohv1FSPEbaBD51VXxJa4JyWpYhreSOEjn1wd0lMKC9b+osV2N2tpbs+rbpQem2tRen3sWflmCqjkG5VOVwRErCuXuPb5+hYwd8BoZbfCrsiAVLd7YT44dRtKNBx6rkabWfddKSLtreHLDysOhQUVOp/XkE7OzSkWl6sky0Hva6qJJ/V726hMlomvcLHjE41iKmW2CpcZfOedg==" + } + ], + "encrypt_decrypt_long_msg": [ + { + "conversation_key": "8fc262099ce0d0bb9b89bac05bb9e04f9bc0090acc181fef6840ccee470371ed", + "nonce": "326bcb2c943cd6bb717588c9e5a7e738edf6ed14ec5f5344caa6ef56f0b9cff7", + "pattern": "x", + "repeat": 65535, + "plaintext_sha256": "09ab7495d3e61a76f0deb12cb0306f0696cbb17ffc12131368c7a939f12f56d3", + "payload_sha256": "90714492225faba06310bff2f249ebdc2a5e609d65a629f1c87f2d4ffc55330a" + }, + { + "conversation_key": "56adbe3720339363ab9c3b8526ffce9fd77600927488bfc4b59f7a68ffe5eae0", + "nonce": "ad68da81833c2a8ff609c3d2c0335fd44fe5954f85bb580c6a8d467aa9fc5dd0", + "pattern": "!", + "repeat": 65535, + "plaintext_sha256": "6af297793b72ae092c422e552c3bb3cbc310da274bd1cf9e31023a7fe4a2d75e", + "payload_sha256": "8013e45a109fad3362133132b460a2d5bce235fe71c8b8f4014793fb52a49844" + }, + { + "conversation_key": "7fc540779979e472bb8d12480b443d1e5eb1098eae546ef2390bee499bbf46be", + "nonce": "34905e82105c20de9a2f6cd385a0d541e6bcc10601d12481ff3a7575dc622033", + "pattern": "🦄", + "repeat": 16383, + "plaintext_sha256": "a249558d161b77297bc0cb311dde7d77190f6571b25c7e4429cd19044634a61f", + "payload_sha256": "b3348422471da1f3c59d79acfe2fe103f3cd24488109e5b18734cdb5953afd15" + } + ] + }, + "invalid": { + "encrypt_msg_lengths": [ 0, 65536, 100000, 10000000 ], + "get_conversation_key": [ + { + "sec1": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "sec1 higher than curve.n" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000000", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "sec1 is 0" + }, + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", + "pub2": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "note": "pub2 is invalid, no sqrt, all-ff" + }, + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "sec1 == curve.n" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000002", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "pub2 is invalid, no sqrt" + }, + { + "sec1": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "pub2": "0000000000000000000000000000000000000000000000000000000000000000", + "note": "pub2 is point of order 3 on twist" + }, + { + "sec1": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "pub2": "eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d", + "note": "pub2 is point of order 13 on twist" + }, + { + "sec1": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "pub2": "709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f", + "note": "pub2 is point of order 3319 on twist" + } + ], + "decrypt": [ + { + "conversation_key": "ca2527a037347b91bea0c8a30fc8d9600ffd81ec00038671e3a0f0cb0fc9f642", + "nonce": "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db", + "plaintext": "n o b l e", + "payload": "#Atqupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbrhdG8VmJdU0MIDf06CUvEvdnr1cp1fiMtlM/GrE92xAc1K5odTpCzUB+mjXgbaqtntBUbTToSUoT0ovrlPwzGjyp", + "note": "unknown encryption version" + }, + { + "conversation_key": "36f04e558af246352dcf73b692fbd3646a2207bd8abd4b1cd26b234db84d9481", + "nonce": "ad408d4be8616dc84bb0bf046454a2a102edac937c35209c43cd7964c5feb781", + "plaintext": "⚠️", + "payload": "AK1AjUvoYW3IS7C/BGRUoqEC7ayTfDUgnEPNeWTF/reBZFaha6EAIRueE9D1B1RuoiuFScC0Q94yjIuxZD3JStQtE8JMNacWFs9rlYP+ZydtHhRucp+lxfdvFlaGV/sQlqZz", + "note": "unknown encryption version 0" + }, + { + "conversation_key": "ca2527a037347b91bea0c8a30fc8d9600ffd81ec00038671e3a0f0cb0fc9f642", + "nonce": "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db", + "plaintext": "n o s t r", + "payload": "Atфupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbrhdG8VmJZE0UICD06CUvEvdnr1cp1fiMtlM/GrE92xAc1EwsVCQEgWEu2gsHUVf4JAa3TpgkmFc3TWsax0v6n/Wq", + "note": "invalid base64" + }, + { + "conversation_key": "cff7bd6a3e29a450fd27f6c125d5edeb0987c475fd1e8d97591e0d4d8a89763c", + "nonce": "09ff97750b084012e15ecb84614ce88180d7b8ec0d468508a86b6d70c0361a25", + "plaintext": "¯\\_(ツ)_/¯", + "payload": "Agn/l3ULCEAS4V7LhGFM6IGA17jsDUaFCKhrbXDANholyySBfeh+EN8wNB9gaLlg4j6wdBYh+3oK+mnxWu3NKRbSvQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "note": "invalid MAC" + }, + { + "conversation_key": "cfcc9cf682dfb00b11357f65bdc45e29156b69db424d20b3596919074f5bf957", + "nonce": "65b14b0b949aaa7d52c417eb753b390e8ad6d84b23af4bec6d9bfa3e03a08af4", + "plaintext": "🥎", + "payload": "AmWxSwuUmqp9UsQX63U7OQ6K1thLI69L7G2b+j4DoIr0oRWQ8avl4OLqWZiTJ10vIgKrNqjoaX+fNhE9RqmR5g0f6BtUg1ijFMz71MO1D4lQLQfW7+UHva8PGYgQ1QpHlKgR", + "note": "invalid MAC" + }, + { + "conversation_key": "5254827d29177622d40a7b67cad014fe7137700c3c523903ebbe3e1b74d40214", + "nonce": "7ab65dbb8bbc2b8e35cafb5745314e1f050325a864d11d0475ef75b3660d91c1", + "plaintext": "elliptic-curve cryptography", + "payload": "Anq2XbuLvCuONcr7V0UxTh8FAyWoZNEdBHXvdbNmDZHB573MI7R7rrTYftpqmvUpahmBC2sngmI14/L0HjOZ7lWGJlzdh6luiOnGPc46cGxf08MRC4CIuxx3i2Lm0KqgJ7vA", + "note": "invalid padding" + }, + { + "conversation_key": "fea39aca9aa8340c3a78ae1f0902aa7e726946e4efcd7783379df8096029c496", + "nonce": "7d4283e3b54c885d6afee881f48e62f0a3f5d7a9e1cb71ccab594a7882c39330", + "plaintext": "noble", + "payload": "An1Cg+O1TIhdav7ogfSOYvCj9dep4ctxzKtZSniCw5MwRrrPJFyAQYZh5VpjC2QYzny5LIQ9v9lhqmZR4WBYRNJ0ognHVNMwiFV1SHpvUFT8HHZN/m/QarflbvDHAtO6pY16", + "note": "invalid padding" + }, + { + "conversation_key": "0c4cffb7a6f7e706ec94b2e879f1fc54ff8de38d8db87e11787694d5392d5b3f", + "nonce": "6f9fd72667c273acd23ca6653711a708434474dd9eb15c3edb01ce9a95743e9b", + "plaintext": "censorship-resistant and global social network", + "payload": "Am+f1yZnwnOs0jymZTcRpwhDRHTdnrFcPtsBzpqVdD6b2NZDaNm/TPkZGr75kbB6tCSoq7YRcbPiNfJXNch3Tf+o9+zZTMxwjgX/nm3yDKR2kHQMBhVleCB9uPuljl40AJ8kXRD0gjw+aYRJFUMK9gCETZAjjmrsCM+nGRZ1FfNsHr6Z", + "note": "invalid padding" + }, + { + "conversation_key": "5cd2d13b9e355aeb2452afbd3786870dbeecb9d355b12cb0a3b6e9da5744cd35", + "nonce": "b60036976a1ada277b948fd4caa065304b96964742b89d26f26a25263a5060bd", + "plaintext": "0", + "payload": "", + "note": "invalid payload length: 0" + }, + { + "conversation_key": "d61d3f09c7dfe1c0be91af7109b60a7d9d498920c90cbba1e137320fdd938853", + "nonce": "1a29d02c8b4527745a2ccb38bfa45655deb37bc338ab9289d756354cea1fd07c", + "plaintext": "1", + "payload": "Ag==", + "note": "invalid payload length: 4" + }, + { + "conversation_key": "873bb0fc665eb950a8e7d5971965539f6ebd645c83c08cd6a85aafbad0f0bc47", + "nonce": "c826d3c38e765ab8cc42060116cd1464b2a6ce01d33deba5dedfb48615306d4a", + "plaintext": "2", + "payload": "AqxgToSh3H7iLYRJjoWAM+vSv/Y1mgNlm6OWWjOYUClrFF8=", + "note": "invalid payload length: 48" + }, + { + "conversation_key": "9f2fef8f5401ac33f74641b568a7a30bb19409c76ffdc5eae2db6b39d2617fbe", + "nonce": "9ff6484642545221624eaac7b9ea27133a4cc2356682a6033aceeef043549861", + "plaintext": "3", + "payload": "Ap/2SEZCVFIhYk6qx7nqJxM6TMI1ZoKmAzrO7vBDVJhhuZXWiM20i/tIsbjT0KxkJs2MZjh1oXNYMO9ggfk7i47WQA==", + "note": "invalid payload length: 92" + } + ] + } + } +} |