// Copyright (C) 2024 Vaughn Nugent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using VNLib.Utils.Memory;
using static VNLib.Utils.Cryptography.Noscrypt.LibNoscrypt;
namespace VNLib.Utils.Cryptography.Noscrypt
{
///
/// Provides a set of utility methods for working with the Noscrypt library
///
public static class Nip44Util
{
///
/// Calculates the required NIP44 encryption buffer size for
/// the specified input data size
///
/// The size (in bytes) of the encoded data to encrypt
/// The exact size of the padded buffer output
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);
}
///
/// Calculates the final buffer size required to hold the encrypted data
///
/// The size (in bytes) of plaintext data to encrypt
/// The number of bytes required to store the final nip44 message
public static int CalcFinalBufferSize(int dataSize)
{
/* version + nonce + payload + mac */
return CalcBufferSize(dataSize) + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE + 1;
}
///
/// Formats the plaintext data into a buffer that can be properly encrypted.
/// The output buffer must be zeroed, or can be zeroed using the
/// parameter. Use
/// to determine the required output buffer size.
///
/// A buffer containing plaintext data to copy to the output
/// The output data buffer to format
/// A value that indicates if the buffer should be zeroed before use
public static void FormatBuffer(ReadOnlySpan plaintextData, Span 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(
in MemoryMarshal.GetReference(plaintextData),
0,
ref MemoryMarshal.GetReference(output),
sizeof(ushort),
(uint)plaintextData.Length
);
//We assume the remaining buffer is zeroed out
}
public static ReadOnlySpan GetNonceFromPayload(ReadOnlySpan 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 GetCiphertextFromPayload(ReadOnlySpan 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 GetMacFromPayload(ReadOnlySpan message)
{
//The mac is the last 32 bytes of the message
return message[^NC_ENCRYPTION_MAC_SIZE..];
}
public static ReadOnlySpan GetNonceAndCiphertext(ReadOnlySpan 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 message)
{
//The first byte is the message version
return message[0];
}
public static ReadOnlySpan GetPlaintextMessage(ReadOnlySpan plaintextPayload)
{
ushort ptLength = BinaryPrimitives.ReadUInt16BigEndian(plaintextPayload);
return plaintextPayload.Slice(sizeof(ushort), ptLength);
}
}
}