using System.Buffers; using System.Text; namespace SpaceWizards.Sodium; using static Interop.Libsodium; public static class SodiumHelpers { static SodiumHelpers() { SodiumCore.EnsureInit(); } // TODO: hex2bin and base642bin /// /// sodium_memcmp /// public static unsafe bool MemoryCompare(ReadOnlySpan b1, ReadOnlySpan b2) { if (b1.Length != b2.Length) throw new ArgumentException("Both input spans must be the same length."); fixed (byte* b1Ptr = b1) fixed (byte* b2Ptr = b2) { return sodium_memcmp(b1Ptr, b2Ptr, (nuint)b1.Length) == 0; } } /// /// sodium_bin2hex /// /// The subsection of that was filled, EXCLUDING null terminator. /// /// If you need a version that gives back a string, just use the BCL instead. /// public static unsafe Span Bin2Hex(Span hex, ReadOnlySpan bin) { var needSize = (long)bin.Length * 2 + 1; if (hex.Length < needSize) throw new ArgumentException("Hex must be at least (bin.Length * 2) + 1 bytes."); fixed (byte* hexPtr = hex) fixed (byte* binPtr = bin) { sodium_bin2hex((sbyte*)hexPtr, (nuint)hex.Length, binPtr, (nuint)bin.Length); } return hex[..(int)(needSize - 1)]; } /// /// sodium_bin2hex, returns string. /// public static string Bin2Hex(ReadOnlySpan bin) { var buffer = ArrayPool.Shared.Rent(bin.Length * 2 + 1); try { var data = Bin2Hex(buffer, bin); return Encoding.ASCII.GetString(data); } finally { ArrayPool.Shared.Return(buffer); } } public static int Base64EncodedLength(int length, SodiumBase64Variant variant) => checked((int)sodium_base64_ENCODED_LEN((ulong)length, (ulong)variant)); /// /// sodium_bin2base64 /// /// The subsection of that was filled, EXCLUDING null terminator. public static unsafe Span Bin2Base64(Span b64, ReadOnlySpan bin, SodiumBase64Variant variant) { var needSize = sodium_base64_ENCODED_LEN((ulong)bin.Length, (ulong)variant); if ((ulong)b64.Length < needSize) throw new ArgumentException("B64 is too short for encoded data"); fixed (byte* b64Ptr = b64) fixed (byte* binPtr = bin) { sodium_bin2base64((sbyte*)b64Ptr, (nuint)b64.Length, binPtr, (nuint)bin.Length, (int)variant); } return b64[..(int)(needSize - 1)]; } /// /// sodium_bin2hex, returns string. /// public static string Bin2Base64(ReadOnlySpan bin, SodiumBase64Variant variant) { var buffer = ArrayPool.Shared.Rent(Base64EncodedLength(bin.Length, variant)); try { var data = Bin2Base64(buffer, bin, variant); return Encoding.ASCII.GetString(data); } finally { ArrayPool.Shared.Return(buffer); } } /// /// sodium_increment /// public static unsafe void Increment(Span n) { fixed (byte* nPtr = n) { sodium_increment(nPtr, (nuint)n.Length); } } /// /// sodium_add /// public static unsafe void Add(Span a, ReadOnlySpan b) { if (a.Length != b.Length) throw new ArgumentException("Both input numbers must be the same length."); fixed (byte* aPtr = a) fixed (byte* bPtr = b) { sodium_add(aPtr, bPtr, (nuint)a.Length); } } /// /// sodium_sub /// public static unsafe void Sub(Span a, ReadOnlySpan b) { if (a.Length != b.Length) throw new ArgumentException("Both input numbers must be the same length."); fixed (byte* aPtr = a) fixed (byte* bPtr = b) { sodium_sub(aPtr, bPtr, (nuint)a.Length); } } /// /// sodium_compare /// public static unsafe int Compare(ReadOnlySpan b1, ReadOnlySpan b2) { if (b1.Length != b2.Length) throw new ArgumentException("Both input numbers must be the same length."); fixed (byte* b1Ptr = b1) fixed (byte* b2Ptr = b2) { return sodium_compare(b1Ptr, b2Ptr, (nuint)b1.Length); } } /// /// sodium_is_zero /// public static unsafe bool IsZero(ReadOnlySpan n) { fixed (byte* nPtr = n) { return sodium_is_zero(nPtr, (nuint)n.Length) == 1; } } /// /// sodium_stackzero /// public static void StackZero(int len) { sodium_stackzero((nuint)len); } } public enum SodiumBase64Variant { Original = sodium_base64_VARIANT_ORIGINAL, OriginalNoPadding = sodium_base64_VARIANT_ORIGINAL_NO_PADDING, OriginalUrlSafe = sodium_base64_VARIANT_URLSAFE, OriginalUrlSafeNoPadding = sodium_base64_VARIANT_URLSAFE_NO_PADDING, }