Skip to content

Commit

Permalink
vectorized Atbash cipher for ASCII
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter-Juhasz committed Dec 27, 2024
1 parent c54a678 commit 0f07b0a
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ public class AtbashBenchmarks
private static readonly AtbashCipher General = new();
private static readonly AsciiAtbashCipher Optimized = new();

private static readonly char[] Input = new char[43];
private static readonly char[] Output = new char[43];
private const string Plaintext = "The quick brown fox jumps over the lazy dog.";
private static readonly char[] Output = new char[64];

[Benchmark]
public void Atbash()
{
General.Encrypt(Input, Output, out _);
General.Encrypt(Plaintext, Output, out _);
}

[Benchmark]
public void SlowXor_I64_K32()
{
Optimized.Encrypt(Input, Output, out _);
Optimized.Encrypt(Plaintext, Output, out _);
}
}
2 changes: 1 addition & 1 deletion perf/Science.Cryptography.Ciphers.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<ShiftCipherBenchmarks>();
BenchmarkRunner.Run<AtbashBenchmarks>();
//BenchmarkRunner.Run(typeof(Program).Assembly);
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
using System;
using System.Composition;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

using TVector = System.Runtime.Intrinsics.Vector128<short>;

namespace Science.Cryptography.Ciphers.Specialized;

Expand All @@ -9,8 +15,15 @@ namespace Science.Cryptography.Ciphers.Specialized;
[Export("ASCII-Atbash", typeof(ICipher))]
public class AsciiAtbashCipher : ReciprocalCipher
{
private const int A = (int)'A';
private const int a = (int)'a';
private const int APlusZ = (int)('A' + 'Z');
private const int LowercaseAPlusZ = (int)('a' + 'z');

private static readonly TVector VectorOfAPlusZ = Vector128.Create((short)('A' + 'Z'));
private static readonly TVector VectorOfLowercaseAPlusZ = Vector128.Create((short)('a' + 'z'));
private static readonly TVector VectorOfAMinus1 = Vector128.Create((short)('A' - 1));
private static readonly TVector VectorOfZPlus1 = Vector128.Create((short)('Z' + 1));
private static readonly TVector VectorOfLowercaseAMinus1 = Vector128.Create((short)('a' - 1));
private static readonly TVector VectorOfLowercaseZPlus1 = Vector128.Create((short)('z' + 1));

protected override void Crypt(ReadOnlySpan<char> text, Span<char> result, out int written)
{
Expand All @@ -19,18 +32,89 @@ protected override void Crypt(ReadOnlySpan<char> text, Span<char> result, out in
throw new ArgumentException("Size of output buffer is insufficient.", nameof(result));
}

if (Avx2.IsSupported)
{
// process the vectorized input
var vectorCount = text.Length / TVector.Count;
var totalVectorizedLength = vectorCount * TVector.Count;
var vectorizedText = MemoryMarshal.Cast<char, short>(text);
var vectorizedResult = MemoryMarshal.Cast<char, short>(result);
for (int offset = 0; offset < totalVectorizedLength; offset += TVector.Count)
{
var input = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(vectorizedText[offset..]));
var output = CryptBlockAvx2(input);
output.StoreUnsafe(ref MemoryMarshal.GetReference(vectorizedResult[offset..]));
}

// process the remaining input
if (totalVectorizedLength < text.Length)
{
var remainingInput = text[totalVectorizedLength..];
var remainingOutput = result[totalVectorizedLength..];
CryptSlow(remainingInput, remainingOutput);
}
}
else
{
CryptSlow(text, result);
}

written = text.Length;
}

internal static void CryptSlow(ReadOnlySpan<char> text, Span<char> result)
{
for (int i = 0; i < text.Length; i++)
{
var ch = text[i];
var idx = (int)ch;
result[i] = ch switch
{
>= 'A' and <= 'Z' => (char)(A + (25 - (idx - A))),
>= 'a' and <= 'z' => (char)(a + (25 - (idx - a))),
>= 'A' and <= 'Z' => (char)(APlusZ - idx),
>= 'a' and <= 'z' => (char)(LowercaseAPlusZ - idx),
_ => ch,
};
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TVector CryptBlockAvx2(TVector input)
{
// uppercase
var isUpperMask = Avx2.And(
Avx2.CompareGreaterThan(input, VectorOfAMinus1),
Avx2.CompareLessThan(input, VectorOfZPlus1)
);
TVector transformedUppercase;
if (isUpperMask == TVector.Zero)
{
transformedUppercase = input;
}
else
{
transformedUppercase = Avx2.Subtract(VectorOfAPlusZ, input);
transformedUppercase = Avx2.BlendVariable(input, transformedUppercase, isUpperMask);
}

// lowercase
var isLowerMask = Avx2.And(
Avx2.CompareGreaterThan(input, VectorOfLowercaseAMinus1),
Avx2.CompareLessThan(input, VectorOfLowercaseZPlus1)
);
TVector transformedLowercase;
if (isLowerMask == TVector.Zero)
{
transformedLowercase = input;
}
else
{
transformedLowercase = Avx2.Subtract(VectorOfLowercaseAPlusZ, input);
transformedLowercase = Avx2.BlendVariable(transformedLowercase, transformedLowercase, isLowerMask);
}

// merge
var transformed = Avx2.BlendVariable(transformedUppercase, transformedLowercase, isLowerMask);

written = text.Length;
return transformed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Science.Cryptography.Ciphers.Specialized;

namespace Science.Cryptography.Ciphers.Tests;

[TestClass]
public class AsciiAtbashCipherTests
{
[TestMethod]
public void AsciiAtbash()
{
var cipher = new AsciiAtbashCipher();

const string plaintext = "AbcdefghijklmnopqrstuvwxyzZYX";
const string ciphertext = "ZyxwvutsrqponmlkjihgfedcbaABC";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Science.Cryptography.Ciphers.Specialized;

namespace Science.Cryptography.Ciphers.Tests;

[TestClass]
public class AtbashCipherTests
{
[TestMethod]
public void Atbash()
{
var cipher = new AtbashCipher();

const string plaintext = "Abcdefghijklmnopqrstuvwxyz";
const string ciphertext = "Zyxwvutsrqponmlkjihgfedcba";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}
}
24 changes: 0 additions & 24 deletions tests/Science.Cryptography.Ciphers.Tests/Ciphers/CipherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,30 +56,6 @@ public void Multiplicative_Encrypt()
Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext, 3));
}

[TestMethod]
public void Atbash()
{
var cipher = new AtbashCipher();

const string plaintext = "Abcdefghijklmnopqrstuvwxyz";
const string ciphertext = "Zyxwvutsrqponmlkjihgfedcba";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}

[TestMethod]
public void AsciiAtbash()
{
var cipher = new AsciiAtbashCipher();

const string plaintext = "Abcdefghijklmnopqrstuvwxyz";
const string ciphertext = "Zyxwvutsrqponmlkjihgfedcba";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}

/*
[TestMethod]
public void Bifid()
Expand Down

0 comments on commit 0f07b0a

Please sign in to comment.