Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Intrinsics and some performance optimisations #58

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/NaCl.Core/Base/ChaCha20Base.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ public override void ProcessKeyStreamBlock(ReadOnlySpan<byte> nonce, int counter
ArrayUtils.StoreArray16UInt32LittleEndian(block, 0, state);
}

#if INTRINSICS
public override unsafe void ProcessStream(ReadOnlySpan<byte> nonce, Span<byte> output, ReadOnlySpan<byte> input, int initialCounter, int offset = 0)
{
Span<uint> state = stackalloc uint[BLOCK_SIZE_IN_INTS];
SetInitialState(state, nonce, initialCounter);
fixed (uint* x = state)
fixed (byte* m = input, c = output.Slice(offset))
{
ChaCha20BaseIntrinsics.ChaCha20(x, m, c, (ulong)input.Length);
}
}
#endif

/// <summary>
/// Process a pseudorandom keystream block, converting the key and part of the <paramref name="nonce"> into a <paramref name="subkey">, and the remainder of the <paramref name="nonce">.
/// </summary>
Expand Down
570 changes: 570 additions & 0 deletions src/NaCl.Core/Base/ChaCha20BaseIntrinsics.cs

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion src/NaCl.Core/Base/Snuffle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public Snuffle(ReadOnlyMemory<byte> key, int initialCounter)
/// <returns>ByteBuffer.</returns>
public abstract void ProcessKeyStreamBlock(ReadOnlySpan<byte> nonce, int counter, Span<byte> block);

#if INTRINSICS
public abstract void ProcessStream(ReadOnlySpan<byte> nonce, Span<byte> output, ReadOnlySpan<byte> input, int initialCounter, int offset = 0);
#endif

/// <summary>
/// The size of the randomly generated nonces.
/// ChaCha20 uses 12-byte nonces, but XSalsa20 and XChaCha20 use 24-byte nonces.
Expand Down Expand Up @@ -87,7 +91,7 @@ public virtual byte[] Encrypt(ReadOnlySpan<byte> plaintext)

var ciphertext = new byte[plaintext.Length + NonceSizeInBytes];

#if NETCOREAPP3_1
#if SPANSTACKALLOC
Span<byte> nonce = stackalloc byte[NonceSizeInBytes];
RandomNumberGenerator.Fill(nonce);

Expand Down Expand Up @@ -202,6 +206,9 @@ public virtual void Decrypt(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> no
/// <param name="offset">The output's starting offset.</param>
private void Process(ReadOnlySpan<byte> nonce, Span<byte> output, ReadOnlySpan<byte> input, int offset = 0)
{
#if INTRINSICS
ProcessStream(nonce, output, input, InitialCounter, offset);
#else
var length = input.Length;
var numBlocks = (length / BLOCK_SIZE_IN_BYTES) + 1;

Expand Down Expand Up @@ -236,6 +243,7 @@ private void Process(ReadOnlySpan<byte> nonce, Span<byte> output, ReadOnlySpan<b
owner.Memory.Span.Clear();
}
}
#endif
}

/// <summary>
Expand Down
74 changes: 52 additions & 22 deletions src/NaCl.Core/Base/SnufflePoly1305.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
namespace NaCl.Core.Base
{
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Cryptography;
using Microsoft.Toolkit.HighPerformance.Buffers;

using Internal;

Expand Down Expand Up @@ -107,14 +109,19 @@ public virtual byte[] Encrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> plain

var ciphertext = _snuffle.Encrypt(plaintext, nonce);

var tag = Poly1305.ComputeMac(GetMacKey(nonce), GetMacDataRfc8439(associatedData, ciphertext));

// Array.Resize(ref ciphertext, ciphertext.Length + Poly1305.MAC_TAG_SIZE_IN_BYTES);
// Array.Copy(tag, 0, ciphertext, ciphertext.Length - Poly1305.MAC_TAG_SIZE_IN_BYTES, tag.Length);

// return ciphertext;
// return ciphertext.Concat(tag).ToArray(); // could be inefficient
return CryptoBytes.Combine(ciphertext, tag);
using (var macKey = GetMacKey(nonce))
using (var macData = GetMacDataRfc8439(associatedData, ciphertext))
{
var tag = Poly1305.ComputeMac(macKey.Span, macData.Span);
macKey.Span.Clear();
macData.Span.Clear();
// Array.Resize(ref ciphertext, ciphertext.Length + Poly1305.MAC_TAG_SIZE_IN_BYTES);
// Array.Copy(tag, 0, ciphertext, ciphertext.Length - Poly1305.MAC_TAG_SIZE_IN_BYTES, tag.Length);

// return ciphertext;
// return ciphertext.Concat(tag).ToArray(); // could be inefficient
return CryptoBytes.Combine(ciphertext, tag);
}
}

/// <summary>
Expand Down Expand Up @@ -144,7 +151,13 @@ public void Encrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> plaintext, Span
// throw new ArgumentException($"The {nameof(plaintext)} is too long.");

_snuffle.Encrypt(plaintext, nonce, ciphertext);
Poly1305.ComputeMac(GetMacKey(nonce), GetMacDataRfc8439(associatedData, ciphertext), tag);
using (var macKey = GetMacKey(nonce))
using (var macData = GetMacDataRfc8439(associatedData, ciphertext))
{
Poly1305.ComputeMac(macKey.Span, macData.Span, tag);
macKey.Span.Clear();
macData.Span.Clear();
}
}

/// <summary>
Expand Down Expand Up @@ -217,7 +230,13 @@ public virtual byte[] Decrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> ciphe

try
{
Poly1305.VerifyMac(GetMacKey(nonce), GetMacDataRfc8439(associatedData, ciphertext.Slice(0, limit)), ciphertext.Slice(limit, Poly1305.MAC_TAG_SIZE_IN_BYTES));
using (var macKey = GetMacKey(nonce))
using (var macData = GetMacDataRfc8439(associatedData, ciphertext.Slice(0, limit)))
{
Poly1305.VerifyMac(macKey.Span, macData.Span, ciphertext.Slice(limit, Poly1305.MAC_TAG_SIZE_IN_BYTES));
macKey.Span.Clear();
macData.Span.Clear();
}
}
catch (Exception ex)
{
Expand Down Expand Up @@ -255,7 +274,13 @@ public virtual void Decrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> ciphert

try
{
Poly1305.VerifyMac(GetMacKey(nonce), GetMacDataRfc8439(associatedData, ciphertext), tag);
using (var macKey = GetMacKey(nonce))
using (var macData = GetMacDataRfc8439(associatedData, ciphertext))
{
Poly1305.VerifyMac(macKey.Span, macData.Span, tag);
macKey.Span.Clear();
macData.Span.Clear();
}
}
catch (Exception ex)
{
Expand All @@ -270,7 +295,7 @@ public virtual void Decrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> ciphert
/// </summary>
/// <param name="nonce">The nonce.</param>
/// <returns>System.Byte[].</returns>
private Span<byte> GetMacKey(ReadOnlySpan<byte> nonce)
private SpanOwner<byte> GetMacKey(ReadOnlySpan<byte> nonce)
{
//var firstBlock = new byte[Snuffle.BLOCK_SIZE_IN_BYTES];
//_macKeySnuffle.ProcessKeyStreamBlock(nonce, 0, firstBlock);
Expand All @@ -279,10 +304,16 @@ private Span<byte> GetMacKey(ReadOnlySpan<byte> nonce)
//Array.Copy(firstBlock, result, result.Length);
//return result;

Span<byte> firstBlock = new byte[Snuffle.BLOCK_SIZE_IN_BYTES];
_macKeySnuffle.ProcessKeyStreamBlock(nonce, 0, firstBlock);
using (var firstBlock = SpanOwner<byte>.Allocate(Snuffle.BLOCK_SIZE_IN_BYTES, AllocationMode.Clear))
{
//Span<byte> firstBlock = new byte[Snuffle.BLOCK_SIZE_IN_BYTES];
_macKeySnuffle.ProcessKeyStreamBlock(nonce, 0, firstBlock.Span);

return firstBlock.Slice(0, Poly1305.MAC_KEY_SIZE_IN_BYTES);
var macKey = SpanOwner<byte>.Allocate(Poly1305.MAC_KEY_SIZE_IN_BYTES, AllocationMode.Clear);
firstBlock.Span.Slice(0, Poly1305.MAC_KEY_SIZE_IN_BYTES).CopyTo(macKey.Span);
firstBlock.Span.Clear();
return macKey;
}
}

/// <summary>
Expand All @@ -291,24 +322,23 @@ private Span<byte> GetMacKey(ReadOnlySpan<byte> nonce)
/// <param name="aad">The associated data.</param>
/// <param name="ciphertext">The ciphertext.</param>
/// <returns>System.Byte[].</returns>
private byte[] GetMacDataRfc8439(ReadOnlySpan<byte> aad, ReadOnlySpan<byte> ciphertext)
private SpanOwner<byte> GetMacDataRfc8439(ReadOnlySpan<byte> aad, ReadOnlySpan<byte> ciphertext)
{
var aadPaddedLen = (aad.Length % 16 == 0) ? aad.Length : (aad.Length + 16 - aad.Length % 16);
var ciphertextLen = ciphertext.Length;
var ciphertextPaddedLen = (ciphertextLen % 16 == 0) ? ciphertextLen : (ciphertextLen + 16 - ciphertextLen % 16);

var macData = new byte[aadPaddedLen + ciphertextPaddedLen + 16];
var macData = SpanOwner<byte>.Allocate(aadPaddedLen + ciphertextPaddedLen + 16, AllocationMode.Clear);

// Mac Text
//aad.CopyTo(macData);
Array.Copy(aad.ToArray(), macData, aad.Length);
Array.Copy(ciphertext.ToArray(), 0, macData, aadPaddedLen, ciphertextLen);
aad.CopyTo(macData.Span);
ciphertext.CopyTo(macData.Span.Slice(aadPaddedLen, ciphertextLen));

// Mac Length
//macData[aadPaddedLen + ciphertextPaddedLen] = (byte)aad.Length;
//macData[aadPaddedLen + ciphertextPaddedLen + 8] = (byte)ciphertextLen;
SetMacLength(macData, aadPaddedLen + ciphertextPaddedLen, aad.Length);
SetMacLength(macData, aadPaddedLen + ciphertextPaddedLen + sizeof(ulong), ciphertextLen);
SetMacLength(macData.Span, aadPaddedLen + ciphertextPaddedLen, aad.Length);
SetMacLength(macData.Span, aadPaddedLen + ciphertextPaddedLen + sizeof(ulong), ciphertextLen);

return macData;
}
Expand Down
163 changes: 163 additions & 0 deletions src/NaCl.Core/Internal/SpanOwner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
//using Microsoft.Toolkit.HighPerformance.Buffers.Views;
//using Microsoft.Toolkit.HighPerformance.Extensions;

namespace Microsoft.Toolkit.HighPerformance.Buffers
{
/// <summary>
/// An <see langword="enum"/> that indicates a mode to use when allocating buffers.
/// </summary>
public enum AllocationMode
{
/// <summary>
/// The default allocation mode for pooled memory (rented buffers are not cleared).
/// </summary>
Default,

/// <summary>
/// Clear pooled buffers when renting them.
/// </summary>
Clear
}

/// <summary>
/// A stack-only type with the ability to rent a buffer of a specified length and getting a <see cref="Span{T}"/> from it.
/// This type mirrors <see cref="MemoryOwner{T}"/> but without allocations and with further optimizations.
/// As this is a stack-only type, it relies on the duck-typed <see cref="IDisposable"/> pattern introduced with C# 8.
/// It should be used like so:
/// <code>
/// using (SpanOwner&lt;byte> buffer = SpanOwner&lt;byte>.Allocate(1024))
/// {
/// // Use the buffer here...
/// }
/// </code>
/// As soon as the code leaves the scope of that <see langword="using"/> block, the underlying buffer will automatically
/// be disposed. The APIs in <see cref="SpanOwner{T}"/> rely on this pattern for extra performance, eg. they don't perform
/// the additional checks that are done in <see cref="MemoryOwner{T}"/> to ensure that the buffer hasn't been disposed
/// before returning a <see cref="Memory{T}"/> or <see cref="Span{T}"/> instance from it.
/// As such, this type should always be used with a <see langword="using"/> block or expression.
/// Not doing so will cause the underlying buffer not to be returned to the shared pool.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
//[DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly ref struct SpanOwner<T>
{
#pragma warning disable IDE0032
/// <summary>
/// The usable length within <see cref="array"/>.
/// </summary>
private readonly int length;
#pragma warning restore IDE0032

/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private readonly T[] array;

/// <summary>
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
private SpanOwner(int length, AllocationMode mode)
{
this.length = length;
this.array = ArrayPool<T>.Shared.Rent(length);

if (mode == AllocationMode.Clear)
{
this.array.AsSpan(0, length).Clear();
}
}

/// <summary>
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
/// </summary>
public static SpanOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new SpanOwner<T>(0, AllocationMode.Default);
}

/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size) => new SpanOwner<T>(size, AllocationMode.Default);

/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new SpanOwner<T>(size, mode);

/// <summary>
/// Gets the number of items in the current instance
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.length;
}

/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Span<T>(array, 0, this.length);
}

/// <summary>
/// Returns a reference to the first element within the current instance, with no bounds check.
/// </summary>
/// <returns>A reference to the first element within the current instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetReference()
{
//return ref array.DangerousGetReference();
return ref MemoryMarshal.GetReference<T>(array);
}

/// <summary>
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
ArrayPool<T>.Shared.Return(array);
}

/// <inheritdoc/>
public override string ToString()
{
if (typeof(T) == typeof(char) &&
this.array is char[] chars)
{
return new string(chars, 0, this.length);
}

// Same representation used in Span<T>
return $"Microsoft.Toolkit.HighPerformance.Buffers.SpanOwner<{typeof(T)}>[{this.length}]";
}
}
}
Loading