diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2023-04-26 23:15:00 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2023-04-26 23:15:00 +0700 |
commit | 7b84d0d55ad0c1867f164c88c84dad5daeb61e46 (patch) | |
tree | 2921cac9649c840b766d740027bedc7c328b5a91 | |
parent | Refactor RecipientIdentifier (diff) | |
download | BouncyCastle.NET-ed25519-7b84d0d55ad0c1867f164c88c84dad5daeb61e46.tar.xz |
Re-add reworked SparkleEngine
-rw-r--r-- | crypto/src/crypto/digests/SparkleDigest.cs | 418 | ||||
-rw-r--r-- | crypto/src/crypto/engines/SparkleEngine.cs | 1394 | ||||
-rw-r--r-- | crypto/test/src/crypto/test/SparkleTest.cs | 709 |
3 files changed, 2094 insertions, 427 deletions
diff --git a/crypto/src/crypto/digests/SparkleDigest.cs b/crypto/src/crypto/digests/SparkleDigest.cs index 3a87d9e80..df51c9935 100644 --- a/crypto/src/crypto/digests/SparkleDigest.cs +++ b/crypto/src/crypto/digests/SparkleDigest.cs @@ -1,15 +1,9 @@ using System; -using System.Diagnostics; #if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER using System.Runtime.CompilerServices; #endif -#if NETCOREAPP3_0_OR_GREATER -using System.Buffers.Binary; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif +using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; @@ -188,19 +182,19 @@ namespace Org.BouncyCastle.Crypto.Digests // addition of last msg block (incl. padding) ProcessBlock(m_buf, 0, SPARKLE_STEPS_BIG); + Pack.UInt32_To_LE(state, 0, RATE_UINTS, output, outOff); + if (STATE_UINTS == 16) { - OutputBlock16(output, outOff); - SparkleOpt16(state, SPARKLE_STEPS_SLIM); - OutputBlock16(output, outOff + 16); - SparkleOpt16(state, SPARKLE_STEPS_SLIM); - OutputBlock16(output, outOff + 32); + SparkleEngine.SparkleOpt16(state, SPARKLE_STEPS_SLIM); + Pack.UInt32_To_LE(state, 0, RATE_UINTS, output, outOff + 16); + SparkleEngine.SparkleOpt16(state, SPARKLE_STEPS_SLIM); + Pack.UInt32_To_LE(state, 0, RATE_UINTS, output, outOff + 32); } else { - OutputBlock12(output, outOff); - SparkleOpt12(state, SPARKLE_STEPS_SLIM); - OutputBlock12(output, outOff + 16); + SparkleEngine.SparkleOpt12(state, SPARKLE_STEPS_SLIM); + Pack.UInt32_To_LE(state, 0, RATE_UINTS, output, outOff + 16); } Reset(); @@ -231,19 +225,19 @@ namespace Org.BouncyCastle.Crypto.Digests // addition of last msg block (incl. padding) ProcessBlock(m_buf, SPARKLE_STEPS_BIG); + Pack.UInt32_To_LE(state[..RATE_UINTS], output); + if (STATE_UINTS == 16) { - OutputBlock16(output); - SparkleOpt16(state, SPARKLE_STEPS_SLIM); - OutputBlock16(output[16..]); - SparkleOpt16(state, SPARKLE_STEPS_SLIM); - OutputBlock16(output[32..]); + SparkleEngine.SparkleOpt16(state, SPARKLE_STEPS_SLIM); + Pack.UInt32_To_LE(state[..RATE_UINTS], output[16..]); + SparkleEngine.SparkleOpt16(state, SPARKLE_STEPS_SLIM); + Pack.UInt32_To_LE(state[..RATE_UINTS], output[32..]); } else { - OutputBlock12(output); - SparkleOpt12(state, SPARKLE_STEPS_SLIM); - OutputBlock12(output[16..]); + SparkleEngine.SparkleOpt12(state, SPARKLE_STEPS_SLIM); + Pack.UInt32_To_LE(state[..RATE_UINTS], output[16..]); } Reset(); @@ -258,34 +252,6 @@ namespace Org.BouncyCastle.Crypto.Digests m_bufPos = 0; } -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - private void OutputBlock12(Span<byte> output) - { - Pack.UInt32_To_LE(state[..RATE_UINTS], output); - } - - private void OutputBlock16(Span<byte> output) - { - Pack.UInt32_To_LE(state[0], output); - Pack.UInt32_To_LE(state[4], output[4..]); - Pack.UInt32_To_LE(state[1], output[8..]); - Pack.UInt32_To_LE(state[5], output[12..]); - } -#else - private void OutputBlock12(byte[] output, int outOff) - { - Pack.UInt32_To_LE(state, 0, RATE_UINTS, output, outOff); - } - - private void OutputBlock16(byte[] output, int outOff) - { - Pack.UInt32_To_LE(state[0], output, outOff); - Pack.UInt32_To_LE(state[4], output, outOff + 4); - Pack.UInt32_To_LE(state[1], output, outOff + 8); - Pack.UInt32_To_LE(state[5], output, outOff + 12); - } -#endif - #if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif @@ -308,278 +274,22 @@ namespace Org.BouncyCastle.Crypto.Digests // addition of a buffer block to the state uint tx = ELL(t0 ^ t2); uint ty = ELL(t1 ^ t3); + state[0] ^= t0 ^ ty; + state[1] ^= t1 ^ tx; + state[2] ^= t2 ^ ty; + state[3] ^= t3 ^ tx; + state[4] ^= ty; + state[5] ^= tx; if (STATE_UINTS == 16) { - state[0] ^= t0 ^ ty; - state[1] ^= t2 ^ ty; - state[2] ^= ty; - state[3] ^= ty; - state[4] ^= t1 ^ tx; - state[5] ^= t3 ^ tx; - state[6] ^= tx; + state[6] ^= ty; state[7] ^= tx; - SparkleOpt16(state, steps); + SparkleEngine.SparkleOpt16(state, steps); } else { - state[0] ^= t0 ^ ty; - state[1] ^= t1 ^ tx; - state[2] ^= t2 ^ ty; - state[3] ^= t3 ^ tx; - state[4] ^= ty; - state[5] ^= tx; - SparkleOpt12(state, steps); - } - } - - private static void SparkleOpt12(uint[] state, int steps) - { - uint s00 = state[ 0]; - uint s01 = state[ 1]; - uint s02 = state[ 2]; - uint s03 = state[ 3]; - uint s04 = state[ 4]; - uint s05 = state[ 5]; - uint s06 = state[ 6]; - uint s07 = state[ 7]; - uint s08 = state[ 8]; - uint s09 = state[ 9]; - uint s10 = state[10]; - uint s11 = state[11]; - - for (int i = 0; i < steps; ++i) - { - // Add round ant - - s01 ^= RCON[i & 7]; - s03 ^= (uint)i; - - // ARXBOX layer - - ArxBoxRound(RCON[0], ref s00, ref s01); - ArxBoxRound(RCON[1], ref s02, ref s03); - ArxBoxRound(RCON[2], ref s04, ref s05); - ArxBoxRound(RCON[3], ref s06, ref s07); - ArxBoxRound(RCON[4], ref s08, ref s09); - ArxBoxRound(RCON[5], ref s10, ref s11); - - // Linear layer - - uint t024 = ELL(s00 ^ s02 ^ s04); - uint t135 = ELL(s01 ^ s03 ^ s05); - - uint u00 = s00 ^ s06; - uint u01 = s01 ^ s07; - uint u02 = s02 ^ s08; - uint u03 = s03 ^ s09; - uint u04 = s04 ^ s10; - uint u05 = s05 ^ s11; - - s06 = s00; - s07 = s01; - s08 = s02; - s09 = s03; - s10 = s04; - s11 = s05; - - s00 = u02 ^ t135; - s01 = u03 ^ t024; - s02 = u04 ^ t135; - s03 = u05 ^ t024; - s04 = u00 ^ t135; - s05 = u01 ^ t024; + SparkleEngine.SparkleOpt12(state, steps); } - - state[ 0] = s00; - state[ 1] = s01; - state[ 2] = s02; - state[ 3] = s03; - state[ 4] = s04; - state[ 5] = s05; - state[ 6] = s06; - state[ 7] = s07; - state[ 8] = s08; - state[ 9] = s09; - state[10] = s10; - state[11] = s11; - } - - private static void SparkleOpt16(uint[] state, int steps) - { - Debug.Assert((steps & 1) == 0); - -#if NETCOREAPP3_0_OR_GREATER - if (Sse2.IsSupported) - { - var s0246 = Load128(state.AsSpan(0)); - var s1357 = Load128(state.AsSpan(4)); - var s8ACE = Load128(state.AsSpan(8)); - var s9BDF = Load128(state.AsSpan(12)); - - var RC03 = Load128(RCON.AsSpan(0)); - var RC47 = Load128(RCON.AsSpan(4)); - - for (int step = 0; step < steps; ++step) - { - // Add round ant - - s1357 = Sse2.Xor(s1357, Vector128.Create(RCON[step & 7], (uint)step, 0U, 0U)); - - // ARXBOX layer - - ArxBoxRound(RC03, ref s0246, ref s1357); - ArxBoxRound(RC47, ref s8ACE, ref s9BDF); - - // Linear layer - - var t0246 = ELL(HorizontalXor(s0246)); - var t1357 = ELL(HorizontalXor(s1357)); - - var u0246 = Sse2.Xor(s0246, s8ACE); - var u1357 = Sse2.Xor(s1357, s9BDF); - - s8ACE = s0246; - s9BDF = s1357; - - s0246 = Sse2.Xor(t1357, Sse2.Shuffle(u0246, 0x39)); - s1357 = Sse2.Xor(t0246, Sse2.Shuffle(u1357, 0x39)); - } - - Store128(s0246, state.AsSpan(0)); - Store128(s1357, state.AsSpan(4)); - Store128(s8ACE, state.AsSpan(8)); - Store128(s9BDF, state.AsSpan(12)); - } - else -#endif - { - uint s00 = state[ 0]; - uint s02 = state[ 1]; - uint s04 = state[ 2]; - uint s06 = state[ 3]; - uint s01 = state[ 4]; - uint s03 = state[ 5]; - uint s05 = state[ 6]; - uint s07 = state[ 7]; - uint s08 = state[ 8]; - uint s10 = state[ 9]; - uint s12 = state[10]; - uint s14 = state[11]; - uint s09 = state[12]; - uint s11 = state[13]; - uint s13 = state[14]; - uint s15 = state[15]; - - int step = 0; - while (step < steps) - { - // STEP 1 - - // Add round ant - - s01 ^= RCON[step & 7]; - s03 ^= (uint)(step++); - - // ARXBOX layer - - ArxBoxRound(RCON[0], ref s00, ref s01); - ArxBoxRound(RCON[1], ref s02, ref s03); - ArxBoxRound(RCON[2], ref s04, ref s05); - ArxBoxRound(RCON[3], ref s06, ref s07); - ArxBoxRound(RCON[4], ref s08, ref s09); - ArxBoxRound(RCON[5], ref s10, ref s11); - ArxBoxRound(RCON[6], ref s12, ref s13); - ArxBoxRound(RCON[7], ref s14, ref s15); - - // Linear layer - - uint t0246 = ELL(s00 ^ s02 ^ s04 ^ s06); - uint t1357 = ELL(s01 ^ s03 ^ s05 ^ s07); - - uint u08 = s08; - uint u09 = s09; - - s08 = s02 ^ s10 ^ t1357; - s09 = s03 ^ s11 ^ t0246; - s10 = s04 ^ s12 ^ t1357; - s11 = s05 ^ s13 ^ t0246; - s12 = s06 ^ s14 ^ t1357; - s13 = s07 ^ s15 ^ t0246; - s14 = s00 ^ u08 ^ t1357; - s15 = s01 ^ u09 ^ t0246; - - // STEP 2 - - // Add round ant - - s09 ^= RCON[step & 7]; - s11 ^= (uint)(step++); - - // ARXBOX layer - - ArxBoxRound(RCON[0], ref s08, ref s09); - ArxBoxRound(RCON[1], ref s10, ref s11); - ArxBoxRound(RCON[2], ref s12, ref s13); - ArxBoxRound(RCON[3], ref s14, ref s15); - ArxBoxRound(RCON[4], ref s00, ref s01); - ArxBoxRound(RCON[5], ref s02, ref s03); - ArxBoxRound(RCON[6], ref s04, ref s05); - ArxBoxRound(RCON[7], ref s06, ref s07); - - // Linear layer - - uint t8ACE = ELL(s08 ^ s10 ^ s12 ^ s14); - uint t9BDF = ELL(s09 ^ s11 ^ s13 ^ s15); - - uint u00 = s00; - uint u01 = s01; - - s00 = s02 ^ s10 ^ t9BDF; - s01 = s03 ^ s11 ^ t8ACE; - s02 = s04 ^ s12 ^ t9BDF; - s03 = s05 ^ s13 ^ t8ACE; - s04 = s06 ^ s14 ^ t9BDF; - s05 = s07 ^ s15 ^ t8ACE; - s06 = u00 ^ s08 ^ t9BDF; - s07 = u01 ^ s09 ^ t8ACE; - } - - state[ 0] = s00; - state[ 1] = s02; - state[ 2] = s04; - state[ 3] = s06; - state[ 4] = s01; - state[ 5] = s03; - state[ 6] = s05; - state[ 7] = s07; - state[ 8] = s08; - state[ 9] = s10; - state[10] = s12; - state[11] = s14; - state[12] = s09; - state[13] = s11; - state[14] = s13; - state[15] = s15; - } - } - -#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif - private static void ArxBoxRound(uint rc, ref uint s00, ref uint s01) - { - s00 += Integers.RotateRight(s01, 31); - s01 ^= Integers.RotateRight(s00, 24); - s00 ^= rc; - s00 += Integers.RotateRight(s01, 17); - s01 ^= Integers.RotateRight(s00, 17); - s00 ^= rc; - s00 += s01; - s01 ^= Integers.RotateRight(s00, 31); - s00 ^= rc; - s00 += Integers.RotateRight(s01, 24); - s01 ^= Integers.RotateRight(s00, 16); - s00 ^= rc; } #if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER @@ -589,81 +299,5 @@ namespace Org.BouncyCastle.Crypto.Digests { return Integers.RotateRight(x, 16) ^ (x & 0xFFFFU); } - -#if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ArxBoxRound(Vector128<uint> rc, ref Vector128<uint> s00, ref Vector128<uint> s01) - { - s00 = Sse2.Add(s00, Sse2.ShiftRightLogical(s01, 31)); - s00 = Sse2.Add(s00, Sse2.ShiftLeftLogical(s01, 1)); - - s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 24)); - s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 8)); - - s00 = Sse2.Xor(s00, rc); - - s00 = Sse2.Add(s00, Sse2.ShiftRightLogical(s01, 17)); - s00 = Sse2.Add(s00, Sse2.ShiftLeftLogical(s01, 15)); - - s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 17)); - s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 15)); - - s00 = Sse2.Xor(s00, rc); - - s00 = Sse2.Add(s00, s01); - - s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 31)); - s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 1)); - - s00 = Sse2.Xor(s00, rc); - - s00 = Sse2.Add(s00, Sse2.ShiftRightLogical(s01, 24)); - s00 = Sse2.Add(s00, Sse2.ShiftLeftLogical(s01, 8)); - - s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 16)); - s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 16)); - - s00 = Sse2.Xor(s00, rc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128<uint> ELL(Vector128<uint> x) - { - var t = Sse2.ShiftLeftLogical(x, 16); - var u = Sse2.Xor(x, t); - return Sse2.Xor(t, Sse2.ShiftRightLogical(u, 16)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128<uint> HorizontalXor(Vector128<uint> x) - { - var t = Sse2.Xor(x, Sse2.Shuffle(x, 0x1B)); - return Sse2.Xor(t, Sse2.Shuffle(t, 0xB1)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128<uint> Load128(ReadOnlySpan<uint> t) - { - if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16) - return MemoryMarshal.Read<Vector128<uint>>(MemoryMarshal.AsBytes(t)); - - return Vector128.Create(t[0], t[1], t[2], t[3]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Store128(Vector128<uint> s, Span<uint> t) - { - var b = MemoryMarshal.AsBytes(t); - if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16) - { - MemoryMarshal.Write(b, ref s); - return; - } - - var u = s.AsUInt64(); - BinaryPrimitives.WriteUInt64LittleEndian(b[..8], u.GetElement(0)); - BinaryPrimitives.WriteUInt64LittleEndian(b[8..], u.GetElement(1)); - } -#endif } } diff --git a/crypto/src/crypto/engines/SparkleEngine.cs b/crypto/src/crypto/engines/SparkleEngine.cs new file mode 100644 index 000000000..34c9bf015 --- /dev/null +++ b/crypto/src/crypto/engines/SparkleEngine.cs @@ -0,0 +1,1394 @@ +using System; +using System.Diagnostics; +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif +#if NETCOREAPP3_0_OR_GREATER +using System.Buffers.Binary; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Engines +{ + /// <summary>Sparkle v1.2, based on the current round 3 submission, https://sparkle-lwc.github.io/ .</summary> + /// <remarks> + /// Reference C implementation: https://github.com/cryptolu/sparkle.<br/> + /// Specification: + /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/sparkle-spec-final.pdf . + /// </remarks> + public sealed class SparkleEngine + : IAeadCipher + { + public enum SparkleParameters + { + SCHWAEMM128_128, + SCHWAEMM256_128, + SCHWAEMM192_192, + SCHWAEMM256_256 + } + + private enum State + { + Uninitialized = 0, + EncInit = 1, + EncAad = 2, + EncData = 3, + EncFinal = 4, + DecInit = 5, + DecAad = 6, + DecData = 7, + DecFinal = 8, + } + + private static readonly uint[] RCON = { 0xB7E15162U, 0xBF715880U, 0x38B4DA56U, 0x324E7738U, 0xBB1185EBU, + 0x4F7C7B57U, 0xCFBFA1C8U, 0xC2B3293DU }; + + private string algorithmName; + private readonly uint[] state; + private readonly uint[] k; + private readonly uint[] npub; + private byte[] tag; + private bool encrypted; + private State m_state = State.Uninitialized; + private byte[] initialAssociatedText; + + private readonly int m_bufferSizeDecrypt; + private readonly byte[] m_buf; + private int m_bufPos = 0; + + private readonly int SCHWAEMM_KEY_LEN; + private readonly int SCHWAEMM_NONCE_LEN; + private readonly int SPARKLE_STEPS_SLIM; + private readonly int SPARKLE_STEPS_BIG; + private readonly int KEY_BYTES; + private readonly int KEY_WORDS; + private readonly int TAG_WORDS; + private readonly int TAG_BYTES; + private readonly int STATE_WORDS; + private readonly int RATE_WORDS; + private readonly int RATE_BYTES; + private readonly int CAP_MASK; + private readonly uint _A0; + private readonly uint _A1; + private readonly uint _M2; + private readonly uint _M3; + + public SparkleEngine(SparkleParameters sparkleParameters) + { + int SPARKLE_STATE; + int SCHWAEMM_TAG_LEN; + int SPARKLE_CAPACITY; + switch (sparkleParameters) + { + case SparkleParameters.SCHWAEMM128_128: + SCHWAEMM_KEY_LEN = 128; + SCHWAEMM_NONCE_LEN = 128; + SCHWAEMM_TAG_LEN = 128; + SPARKLE_STATE = 256; + SPARKLE_CAPACITY = 128; + SPARKLE_STEPS_SLIM = 7; + SPARKLE_STEPS_BIG = 10; + algorithmName = "SCHWAEMM128-128"; + break; + case SparkleParameters.SCHWAEMM256_128: + SCHWAEMM_KEY_LEN = 128; + SCHWAEMM_NONCE_LEN = 256; + SCHWAEMM_TAG_LEN = 128; + SPARKLE_STATE = 384; + SPARKLE_CAPACITY = 128; + SPARKLE_STEPS_SLIM = 7; + SPARKLE_STEPS_BIG = 11; + algorithmName = "SCHWAEMM256-128"; + break; + case SparkleParameters.SCHWAEMM192_192: + SCHWAEMM_KEY_LEN = 192; + SCHWAEMM_NONCE_LEN = 192; + SCHWAEMM_TAG_LEN = 192; + SPARKLE_STATE = 384; + SPARKLE_CAPACITY = 192; + SPARKLE_STEPS_SLIM = 7; + SPARKLE_STEPS_BIG = 11; + algorithmName = "SCHWAEMM192-192"; + break; + case SparkleParameters.SCHWAEMM256_256: + SCHWAEMM_KEY_LEN = 256; + SCHWAEMM_NONCE_LEN = 256; + SCHWAEMM_TAG_LEN = 256; + SPARKLE_STATE = 512; + SPARKLE_CAPACITY = 256; + SPARKLE_STEPS_SLIM = 8; + SPARKLE_STEPS_BIG = 12; + algorithmName = "SCHWAEMM256-256"; + break; + default: + throw new ArgumentException("Invalid definition of SCHWAEMM instance"); + } + KEY_WORDS = SCHWAEMM_KEY_LEN >> 5; + KEY_BYTES = SCHWAEMM_KEY_LEN >> 3; + TAG_WORDS = SCHWAEMM_TAG_LEN >> 5; + TAG_BYTES = SCHWAEMM_TAG_LEN >> 3; + STATE_WORDS = SPARKLE_STATE >> 5; + RATE_WORDS = SCHWAEMM_NONCE_LEN >> 5; + RATE_BYTES = SCHWAEMM_NONCE_LEN >> 3; + int CAP_BRANS = SPARKLE_CAPACITY >> 6; + int CAP_WORDS = SPARKLE_CAPACITY >> 5; + CAP_MASK = RATE_WORDS > CAP_WORDS ? CAP_WORDS - 1 : -1; + _A0 = ((((1u << CAP_BRANS))) << 24); + _A1 = (((1u ^ (1u << CAP_BRANS))) << 24); + _M2 = (((2u ^ (1u << CAP_BRANS))) << 24); + _M3 = (((3u ^ (1u << CAP_BRANS))) << 24); + state = new uint[STATE_WORDS]; + tag = new byte[TAG_BYTES]; + k = new uint[KEY_WORDS]; + npub = new uint[RATE_WORDS]; + + m_bufferSizeDecrypt = RATE_BYTES + TAG_BYTES; + m_buf = new byte[m_bufferSizeDecrypt]; + + // Relied on by ProcessBytes methods for decryption + Debug.Assert(RATE_BYTES >= TAG_BYTES); + } + + public int GetKeyBytesSize() => KEY_BYTES; + + public int GetIVBytesSize() => RATE_BYTES; + + public string AlgorithmName => algorithmName; + + public void Init(bool forEncryption, ICipherParameters parameters) + { + KeyParameter key; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ReadOnlySpan<byte> iv; +#else + byte[] iv; +#endif + + if (parameters is AeadParameters aeadParameters) + { + key = aeadParameters.Key; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + iv = aeadParameters.Nonce; +#else + iv = aeadParameters.GetNonce(); +#endif + initialAssociatedText = aeadParameters.GetAssociatedText(); + + int macSizeBits = aeadParameters.MacSize; + if (macSizeBits != TAG_BYTES * 8) + throw new ArgumentException("Invalid value for MAC size: " + macSizeBits); + } + else if (parameters is ParametersWithIV withIV) + { + key = withIV.Parameters as KeyParameter; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + iv = withIV.IV; +#else + iv = withIV.GetIV(); +#endif + initialAssociatedText = null; + } + else + { + throw new ArgumentException("invalid parameters passed to Sparkle"); + } + + if (key == null) + throw new ArgumentException("Sparkle Init parameters must include a key"); + + int expectedKeyLength = KEY_WORDS * 4; + if (expectedKeyLength != key.KeyLength) + throw new ArgumentException(algorithmName + " requires exactly " + expectedKeyLength + " bytes of key"); + + int expectedIVLength = RATE_WORDS * 4; + if (expectedIVLength != iv.Length) + throw new ArgumentException(algorithmName + " requires exactly " + expectedIVLength + " bytes of IV"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Pack.LE_To_UInt32(key.Key, k); + Pack.LE_To_UInt32(iv, npub); +#else + Pack.LE_To_UInt32(key.GetKey(), 0, k); + Pack.LE_To_UInt32(iv, 0, npub); +#endif + + m_state = forEncryption ? State.EncInit : State.DecInit; + + // TODO true might be better? + Reset(false); + } + + public void ProcessAadByte(byte input) + { + CheckAad(); + + if (m_bufPos == RATE_BYTES) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ProcessBufferAad(m_buf); +#else + ProcessBufferAad(m_buf, 0); +#endif + m_bufPos = 0; + } + + m_buf[m_bufPos++] = input; + } + + public void ProcessAadBytes(byte[] inBytes, int inOff, int len) + { + Check.DataLength(inBytes, inOff, len, "input buffer too short"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ProcessAadBytes(inBytes.AsSpan(inOff, len)); +#else + // Don't enter AAD state until we actually get input + if (len <= 0) + return; + + CheckAad(); + + if (m_bufPos > 0) + { + int available = RATE_BYTES - m_bufPos; + if (len <= available) + { + Array.Copy(inBytes, inOff, m_buf, m_bufPos, len); + m_bufPos += len; + return; + } + + Array.Copy(inBytes, inOff, m_buf, m_bufPos, available); + inOff += available; + len -= available; + + ProcessBufferAad(m_buf, 0); + //m_bufPos = 0; + } + + while (len > RATE_BYTES) + { + ProcessBufferAad(inBytes, inOff); + inOff += RATE_BYTES; + len -= RATE_BYTES; + } + + Array.Copy(inBytes, inOff, m_buf, 0, len); + m_bufPos = len; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void ProcessAadBytes(ReadOnlySpan<byte> input) + { + // Don't enter AAD state until we actually get input + if (input.IsEmpty) + return; + + CheckAad(); + + if (m_bufPos > 0) + { + int available = RATE_BYTES - m_bufPos; + if (input.Length <= available) + { + input.CopyTo(m_buf.AsSpan(m_bufPos)); + m_bufPos += input.Length; + return; + } + + input[..available].CopyTo(m_buf.AsSpan(m_bufPos)); + input = input[available..]; + + ProcessBufferAad(m_buf); + //m_bufPos = 0; + } + + while (input.Length > RATE_BYTES) + { + ProcessBufferAad(input); + input = input[RATE_BYTES..]; + } + + input.CopyTo(m_buf); + m_bufPos = input.Length; + } +#endif + + public int ProcessByte(byte input, byte[] outBytes, int outOff) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return ProcessByte(input, Spans.FromNullable(outBytes, outOff)); +#else + return ProcessBytes(new byte[]{ input }, 0, 1, outBytes, outOff); +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int ProcessByte(byte input, Span<byte> output) + { + Span<byte> singleByte = stackalloc byte[1]{ input }; + + return ProcessBytes(singleByte, output); + } +#endif + + public int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff) + { + Check.DataLength(inBytes, inOff, len, "input buffer too short"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return ProcessBytes(inBytes.AsSpan(inOff, len), Spans.FromNullable(outBytes, outOff)); +#else + bool forEncryption = CheckData(); + + int resultLength = 0; + + if (forEncryption) + { + if (m_bufPos > 0) + { + int available = RATE_BYTES - m_bufPos; + if (len <= available) + { + Array.Copy(inBytes, inOff, m_buf, m_bufPos, len); + m_bufPos += len; + return 0; + } + + Array.Copy(inBytes, inOff, m_buf, m_bufPos, available); + inOff += available; + len -= available; + + ProcessBufferEncrypt(m_buf, 0, outBytes, outOff); + resultLength = RATE_BYTES; + //m_bufPos = 0; + } + + while (len > RATE_BYTES) + { + ProcessBufferEncrypt(inBytes, inOff, outBytes, outOff + resultLength); + inOff += RATE_BYTES; + len -= RATE_BYTES; + resultLength += RATE_BYTES; + } + } + else + { + int available = m_bufferSizeDecrypt - m_bufPos; + if (len <= available) + { + Array.Copy(inBytes, inOff, m_buf, m_bufPos, len); + m_bufPos += len; + return 0; + } + + if (m_bufPos > RATE_BYTES) + { + ProcessBufferDecrypt(m_buf, 0, outBytes, outOff); + m_bufPos -= RATE_BYTES; + Array.Copy(m_buf, RATE_BYTES, m_buf, 0, m_bufPos); + resultLength = RATE_BYTES; + + available += RATE_BYTES; + if (len <= available) + { + Array.Copy(inBytes, inOff, m_buf, m_bufPos, len); + m_bufPos += len; + return resultLength; + } + } + + available = RATE_BYTES - m_bufPos; + Array.Copy(inBytes, inOff, m_buf, m_bufPos, available); + inOff += available; + len -= available; + ProcessBufferDecrypt(m_buf, 0, outBytes, outOff + resultLength); + resultLength += RATE_BYTES; + //m_bufPos = 0; + + while (len > m_bufferSizeDecrypt) + { + ProcessBufferDecrypt(inBytes, inOff, outBytes, outOff + resultLength); + inOff += RATE_BYTES; + len -= RATE_BYTES; + resultLength += RATE_BYTES; + } + } + + Array.Copy(inBytes, inOff, m_buf, 0, len); + m_bufPos = len; + + return resultLength; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output) + { + bool forEncryption = CheckData(); + + int resultLength = 0; + + if (forEncryption) + { + if (m_bufPos > 0) + { + int available = RATE_BYTES - m_bufPos; + if (input.Length <= available) + { + input.CopyTo(m_buf.AsSpan(m_bufPos)); + m_bufPos += input.Length; + return 0; + } + + input[..available].CopyTo(m_buf.AsSpan(m_bufPos)); + input = input[available..]; + + ProcessBufferEncrypt(m_buf, output); + resultLength = RATE_BYTES; + //m_bufPos = 0; + } + + while (input.Length > RATE_BYTES) + { + ProcessBufferEncrypt(input, output[resultLength..]); + input = input[RATE_BYTES..]; + resultLength += RATE_BYTES; + } + } + else + { + int available = m_bufferSizeDecrypt - m_bufPos; + if (input.Length <= available) + { + input.CopyTo(m_buf.AsSpan(m_bufPos)); + m_bufPos += input.Length; + return 0; + } + + if (m_bufPos > RATE_BYTES) + { + ProcessBufferDecrypt(m_buf, output); + m_bufPos -= RATE_BYTES; + m_buf.AsSpan(0, m_bufPos).CopyFrom(m_buf.AsSpan(RATE_BYTES)); + resultLength = RATE_BYTES; + + available += RATE_BYTES; + if (input.Length <= available) + { + input.CopyTo(m_buf.AsSpan(m_bufPos)); + m_bufPos += input.Length; + return resultLength; + } + } + + available = RATE_BYTES - m_bufPos; + input[..available].CopyTo(m_buf.AsSpan(m_bufPos)); + input = input[available..]; + ProcessBufferDecrypt(m_buf, output[resultLength..]); + resultLength += RATE_BYTES; + //m_bufPos = 0; + + while (input.Length > m_bufferSizeDecrypt) + { + ProcessBufferDecrypt(input, output[resultLength..]); + input = input[RATE_BYTES..]; + resultLength += RATE_BYTES; + } + } + + input.CopyTo(m_buf); + m_bufPos = input.Length; + + return resultLength; + } +#endif + + public int DoFinal(byte[] outBytes, int outOff) + { + bool forEncryption = CheckData(); + + int resultLength; + if (forEncryption) + { + resultLength = m_bufPos + TAG_BYTES; + Check.OutputLength(outBytes, outOff, resultLength, "output buffer too short"); + + } + else + { + if (m_bufPos < TAG_BYTES) + throw new InvalidCipherTextException("data too short"); + + m_bufPos -= TAG_BYTES; + + resultLength = m_bufPos; + Check.OutputLength(outBytes, outOff, resultLength, "output buffer too short"); + + } + + if (encrypted || m_bufPos > 0) + { + // Encryption of Last Block + // addition of ant M2 or M3 to the state + state[STATE_WORDS - 1] ^= (m_bufPos < RATE_BYTES) ? _M2 : _M3; + // combined Rho and rate-whitening (incl. padding) + // Rho and rate-whitening for the encryption of the last plaintext block. Since + // this last block may require padding, it is always copied to a buffer. + uint[] buffer = new uint[RATE_WORDS]; + for (int i = 0; i < m_bufPos; ++i) + { + buffer[i >> 2] |= (uint)m_buf[i] << ((i & 3) << 3); + } + if (m_bufPos < RATE_BYTES) + { + if (!forEncryption) + { + int tmp = (m_bufPos & 3) << 3; + buffer[m_bufPos >> 2] |= (state[m_bufPos >> 2] >> tmp) << tmp; + tmp = (m_bufPos >> 2) + 1; + Array.Copy(state, tmp, buffer, tmp, RATE_WORDS - tmp); + } + buffer[m_bufPos >> 2] ^= 0x80U << ((m_bufPos & 3) << 3); + } + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + RATE_WORDS / 2; + + uint s_i = state[i]; + uint s_j = state[j]; + if (forEncryption) + { + state[i] = s_j ^ buffer[i] ^ state[RATE_WORDS + i]; + state[j] = s_i ^ s_j ^ buffer[j] ^ state[RATE_WORDS + (j & CAP_MASK)]; + } + else + { + state[i] = s_i ^ s_j ^ buffer[i] ^ state[RATE_WORDS + i]; + state[j] = s_i ^ buffer[j] ^ state[RATE_WORDS + (j & CAP_MASK)]; + } + buffer[i] ^= s_i; + buffer[j] ^= s_j; + } + for (int i = 0; i < m_bufPos; ++i) + { + outBytes[outOff++] = (byte)(buffer[i >> 2] >> ((i & 3) << 3)); + } + + SparkleOpt(state, SPARKLE_STEPS_BIG); + } + // add key to the capacity-part of the state + for (int i = 0; i < KEY_WORDS; i++) + { + state[RATE_WORDS + i] ^= k[i]; + } + tag = new byte[TAG_BYTES]; + Pack.UInt32_To_LE(state, RATE_WORDS, TAG_WORDS, tag, 0); + if (forEncryption) + { + Array.Copy(tag, 0, outBytes, outOff, TAG_BYTES); + } + else + { + if (!Arrays.FixedTimeEquals(TAG_BYTES, tag, 0, m_buf, m_bufPos)) + throw new InvalidCipherTextException("mac check in " + AlgorithmName + " failed"); + } + Reset(!forEncryption); + return resultLength; + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int DoFinal(Span<byte> output) + { + byte[] rv = new byte[GetOutputSize(0)]; + int len = DoFinal(rv, 0); + rv.AsSpan(0, len).CopyTo(output); + return len; + } +#endif + + public byte[] GetMac() + { + return tag; + } + + public int GetUpdateOutputSize(int len) + { + // The -1 is to account for the lazy processing of a full buffer + int total = System.Math.Max(0, len - 1); + + switch (m_state) + { + case State.DecInit: + case State.DecAad: + total = System.Math.Max(0, total - TAG_BYTES); + break; + case State.DecData: + case State.DecFinal: + total = System.Math.Max(0, total + m_bufPos - TAG_BYTES); + break; + case State.EncData: + case State.EncFinal: + total = System.Math.Max(0, total + m_bufPos); + break; + default: + break; + } + + return total - total % RATE_BYTES; + } + + public int GetOutputSize(int len) + { + int total = System.Math.Max(0, len); + + switch (m_state) + { + case State.DecInit: + case State.DecAad: + return System.Math.Max(0, total - TAG_BYTES); + case State.DecData: + case State.DecFinal: + return System.Math.Max(0, total + m_bufPos - TAG_BYTES); + case State.EncData: + case State.EncFinal: + return total + m_bufPos + TAG_BYTES; + default: + return total + TAG_BYTES; + } + } + + public void Reset() + { + Reset(true); + } + + private void CheckAad() + { + switch (m_state) + { + case State.DecInit: + m_state = State.DecAad; + break; + case State.EncInit: + m_state = State.EncAad; + break; + case State.DecAad: + case State.EncAad: + break; + case State.EncFinal: + throw new InvalidOperationException(AlgorithmName + " cannot be reused for encryption"); + default: + throw new InvalidOperationException(AlgorithmName + " needs to be initialized"); + } + } + + private bool CheckData() + { + switch (m_state) + { + case State.DecInit: + case State.DecAad: + FinishAad(State.DecData); + return false; + case State.EncInit: + case State.EncAad: + FinishAad(State.EncData); + return true; + case State.DecData: + return false; + case State.EncData: + return true; + case State.EncFinal: + throw new InvalidOperationException(AlgorithmName + " cannot be reused for encryption"); + default: + throw new InvalidOperationException(AlgorithmName + " needs to be initialized"); + } + } + + private void FinishAad(State nextState) + { + // State indicates whether we ever received AAD + switch (m_state) + { + case State.DecAad: + case State.EncAad: + { + ProcessFinalAad(); + break; + } + } + + m_bufPos = 0; + m_state = nextState; + } + + private void FinishData(State nextState) + { + // TODO + //switch (asconParameters) + //{ + //case AsconParameters.ascon128: + // x1 ^= K1; + // x2 ^= K2; + // break; + //case AsconParameters.ascon128a: + // x2 ^= K1; + // x3 ^= K2; + // break; + //case AsconParameters.ascon80pq: + // x1 ^= (K0 << 32 | K1 >> 32); + // x2 ^= (K1 << 32 | K2 >> 32); + // x3 ^= K2 << 32; + // break; + //default: + // throw new InvalidOperationException(); + //} + //P(12); + //x3 ^= K1; + //x4 ^= K2; + + m_state = nextState; + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void ProcessBufferAad(ReadOnlySpan<byte> buffer) + { + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + (RATE_WORDS >> 1); + + uint si = state[i]; + uint sj = state[j]; + + uint d_i = Pack.LE_To_UInt32(buffer, i << 2); + uint d_j = Pack.LE_To_UInt32(buffer, j << 2); + + state[i] = sj ^ d_i ^ state[RATE_WORDS + i]; + state[j] = si ^ sj ^ d_j ^ state[RATE_WORDS + (j & CAP_MASK)]; + } + + SparkleOpt(state, SPARKLE_STEPS_SLIM); + } + + private void ProcessBufferDecrypt(ReadOnlySpan<byte> buffer, Span<byte> output) + { + Debug.Assert(buffer.Length >= RATE_BYTES); + + Check.OutputLength(output, RATE_BYTES, "output buffer too short"); + + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + (RATE_WORDS / 2); + + uint s_i = state[i]; + uint s_j = state[j]; + + uint d_i = Pack.LE_To_UInt32(buffer, i * 4); + uint d_j = Pack.LE_To_UInt32(buffer, j * 4); + + state[i] = s_i ^ s_j ^ d_i ^ state[RATE_WORDS + i]; + state[j] = s_i ^ d_j ^ state[RATE_WORDS + (j & CAP_MASK)]; + + Pack.UInt32_To_LE(d_i ^ s_i, output, i * 4); + Pack.UInt32_To_LE(d_j ^ s_j, output, j * 4); + } + + SparkleOpt(state, SPARKLE_STEPS_SLIM); + + encrypted = true; + } + + private void ProcessBufferEncrypt(ReadOnlySpan<byte> buffer, Span<byte> output) + { + Debug.Assert(buffer.Length >= RATE_BYTES); + + Check.OutputLength(output, RATE_BYTES, "output buffer too short"); + + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + (RATE_WORDS / 2); + + uint s_i = state[i]; + uint s_j = state[j]; + + uint d_i = Pack.LE_To_UInt32(buffer, i * 4); + uint d_j = Pack.LE_To_UInt32(buffer, j * 4); + + state[i] = s_j ^ d_i ^ state[RATE_WORDS + i]; + state[j] = s_i ^ s_j ^ d_j ^ state[RATE_WORDS + (j & CAP_MASK)]; + + Pack.UInt32_To_LE(d_i ^ s_i, output, i * 4); + Pack.UInt32_To_LE(d_j ^ s_j, output, j * 4); + } + + SparkleOpt(state, SPARKLE_STEPS_SLIM); + + encrypted = true; + } +#else + private void ProcessBufferAad(byte[] buffer, int bufOff) + { + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + (RATE_WORDS / 2); + + uint s_i = state[i]; + uint s_j = state[j]; + + uint d_i = Pack.LE_To_UInt32(buffer, bufOff + (i * 4)); + uint d_j = Pack.LE_To_UInt32(buffer, bufOff + (j * 4)); + + state[i] = s_j ^ d_i ^ state[RATE_WORDS + i]; + state[j] = s_i ^ s_j ^ d_j ^ state[RATE_WORDS + (j & CAP_MASK)]; + } + + SparkleOpt(state, SPARKLE_STEPS_SLIM); + } + + private void ProcessBufferDecrypt(byte[] buffer, int bufOff, byte[] output, int outOff) + { + Debug.Assert(bufOff <= buffer.Length - RATE_BYTES); + + Check.OutputLength(output, outOff, RATE_BYTES, "output buffer too short"); + + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + (RATE_WORDS / 2); + + uint s_i = state[i]; + uint s_j = state[j]; + + uint d_i = Pack.LE_To_UInt32(buffer, bufOff + (i * 4)); + uint d_j = Pack.LE_To_UInt32(buffer, bufOff + (j * 4)); + + state[i] = s_i ^ s_j ^ d_i ^ state[RATE_WORDS + i]; + state[j] = s_i ^ d_j ^ state[RATE_WORDS + (j & CAP_MASK)]; + + Pack.UInt32_To_LE(d_i ^ s_i, output, outOff + (i * 4)); + Pack.UInt32_To_LE(d_j ^ s_j, output, outOff + (j * 4)); + } + + SparkleOpt(state, SPARKLE_STEPS_SLIM); + + encrypted = true; + } + + private void ProcessBufferEncrypt(byte[] buffer, int bufOff, byte[] output, int outOff) + { + Debug.Assert(bufOff <= buffer.Length - RATE_BYTES); + + Check.OutputLength(output, outOff, RATE_BYTES, "output buffer too short"); + + for (int i = 0; i < RATE_WORDS / 2; ++i) + { + int j = i + (RATE_WORDS / 2); + + uint s_i = state[i]; + uint s_j = state[j]; + + uint d_i = Pack.LE_To_UInt32(buffer, bufOff + (i * 4)); + uint d_j = Pack.LE_To_UInt32(buffer, bufOff + (j * 4)); + + state[i] = s_j ^ d_i ^ state[RATE_WORDS + i]; + state[j] = s_i ^ s_j ^ d_j ^ state[RATE_WORDS + (j & CAP_MASK)]; + + Pack.UInt32_To_LE(d_i ^ s_i, output, outOff + (i * 4)); + Pack.UInt32_To_LE(d_j ^ s_j, output, outOff + (j * 4)); + } + + SparkleOpt(state, SPARKLE_STEPS_SLIM); + + encrypted = true; + } +#endif + + private void ProcessFinalAad() + { + // Authentication of Last Block + + // addition of ant A0 or A1 to the state + state[STATE_WORDS - 1] ^= (m_bufPos < RATE_BYTES) ? _A0 : _A1; + + // Rho and rate-whitening for the authentication of the last associated-data block. + uint[] buffer = new uint[RATE_WORDS]; + for (int i = 0; i < m_bufPos; ++i) + { + buffer[i >> 2] |= (uint)m_buf[i] << ((i & 3) << 3); + } + if (m_bufPos < RATE_BYTES) + { // padding + buffer[m_bufPos >> 2] |= 0x80U << ((m_bufPos & 3) << 3); + } + for (int i = 0, j = RATE_WORDS / 2; i < RATE_WORDS / 2; i++, j++) + { + uint tmp = state[i]; + state[i] = state[j] ^ buffer[i] ^ state[RATE_WORDS + i]; + state[j] ^= tmp ^ buffer[j] ^ state[RATE_WORDS + (j & CAP_MASK)]; + } + + SparkleOpt(state, SPARKLE_STEPS_BIG); + } + + private void Reset(bool clearMac) + { + if (clearMac) + { + tag = null; + } + + Arrays.Clear(m_buf); + m_bufPos = 0; + encrypted = false; + + switch (m_state) + { + case State.DecInit: + case State.EncInit: + break; + case State.DecAad: + case State.DecData: + case State.DecFinal: + m_state = State.DecInit; + break; + case State.EncAad: + case State.EncData: + case State.EncFinal: + m_state = State.EncFinal; + return; + default: + throw new InvalidOperationException(AlgorithmName + " needs to be initialized"); + } + + // The Initialize function loads nonce and key into the state and executes the + // SPARKLE permutation with the big number of steps. + // load nonce into the rate-part of the state + Array.Copy(npub, 0, state, 0, RATE_WORDS); + // load key into the capacity-part of the sate + Array.Copy(k, 0, state, RATE_WORDS, KEY_WORDS); + + SparkleOpt(state, SPARKLE_STEPS_BIG); + + if (initialAssociatedText != null) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ProcessAadBytes(initialAssociatedText); +#else + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); +#endif + } + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static void ArxBox(uint rc, ref uint s00, ref uint s01) + { + s00 += Integers.RotateRight(s01, 31); + s01 ^= Integers.RotateRight(s00, 24); + s00 ^= rc; + s00 += Integers.RotateRight(s01, 17); + s01 ^= Integers.RotateRight(s00, 17); + s00 ^= rc; + s00 += s01; + s01 ^= Integers.RotateRight(s00, 31); + s00 ^= rc; + s00 += Integers.RotateRight(s01, 24); + s01 ^= Integers.RotateRight(s00, 16); + s00 ^= rc; + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static uint ELL(uint x) + { + return Integers.RotateRight(x, 16) ^ (x & 0xFFFFU); + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static void SparkleOpt(uint[] state, int steps) + { + switch (state.Length) + { + case 8: SparkleOpt8 (state, steps); break; + case 12: SparkleOpt12(state, steps); break; + case 16: SparkleOpt16(state, steps); break; + default: throw new InvalidOperationException(); + } + } + + internal static void SparkleOpt8(uint[] state, int steps) + { + uint s00 = state[0]; + uint s01 = state[1]; + uint s02 = state[2]; + uint s03 = state[3]; + uint s04 = state[4]; + uint s05 = state[5]; + uint s06 = state[6]; + uint s07 = state[7]; + + for (int step = 0; step < steps; ++step) + { + // Add round ant + + s01 ^= RCON[step & 7]; + s03 ^= (uint)step; + + // ARXBOX layer + + ArxBox(RCON[0], ref s00, ref s01); + ArxBox(RCON[1], ref s02, ref s03); + ArxBox(RCON[2], ref s04, ref s05); + ArxBox(RCON[3], ref s06, ref s07); + + // Linear layer + + uint t02 = ELL(s00 ^ s02); + uint t13 = ELL(s01 ^ s03); + + uint u00 = s00 ^ s04; + uint u01 = s01 ^ s05; + uint u02 = s02 ^ s06; + uint u03 = s03 ^ s07; + + s04 = s00; + s05 = s01; + s06 = s02; + s07 = s03; + + s00 = u02 ^ t13; + s01 = u03 ^ t02; + s02 = u00 ^ t13; + s03 = u01 ^ t02; + } + + state[0] = s00; + state[1] = s01; + state[2] = s02; + state[3] = s03; + state[4] = s04; + state[5] = s05; + state[6] = s06; + state[7] = s07; + } + + internal static void SparkleOpt12(uint[] state, int steps) + { + uint s00 = state[0]; + uint s01 = state[1]; + uint s02 = state[2]; + uint s03 = state[3]; + uint s04 = state[4]; + uint s05 = state[5]; + uint s06 = state[6]; + uint s07 = state[7]; + uint s08 = state[8]; + uint s09 = state[9]; + uint s10 = state[10]; + uint s11 = state[11]; + + for (int step = 0; step < steps; ++step) + { + // Add round ant + + s01 ^= RCON[step & 7]; + s03 ^= (uint)step; + + // ARXBOX layer + + ArxBox(RCON[0], ref s00, ref s01); + ArxBox(RCON[1], ref s02, ref s03); + ArxBox(RCON[2], ref s04, ref s05); + ArxBox(RCON[3], ref s06, ref s07); + ArxBox(RCON[4], ref s08, ref s09); + ArxBox(RCON[5], ref s10, ref s11); + + // Linear layer + + uint t024 = ELL(s00 ^ s02 ^ s04); + uint t135 = ELL(s01 ^ s03 ^ s05); + + uint u00 = s00 ^ s06; + uint u01 = s01 ^ s07; + uint u02 = s02 ^ s08; + uint u03 = s03 ^ s09; + uint u04 = s04 ^ s10; + uint u05 = s05 ^ s11; + + s06 = s00; + s07 = s01; + s08 = s02; + s09 = s03; + s10 = s04; + s11 = s05; + + s00 = u02 ^ t135; + s01 = u03 ^ t024; + s02 = u04 ^ t135; + s03 = u05 ^ t024; + s04 = u00 ^ t135; + s05 = u01 ^ t024; + } + + state[0] = s00; + state[1] = s01; + state[2] = s02; + state[3] = s03; + state[4] = s04; + state[5] = s05; + state[6] = s06; + state[7] = s07; + state[8] = s08; + state[9] = s09; + state[10] = s10; + state[11] = s11; + } + + internal static void SparkleOpt16(uint[] state, int steps) + { + Debug.Assert((steps & 1) == 0); + +#if NETCOREAPP3_0_OR_GREATER + if (Sse2.IsSupported) + { + var s0246 = Vector128.Create(state[0], state[2], state[4], state[6]); + var s1357 = Vector128.Create(state[1], state[3], state[5], state[7]); + var s8ACE = Vector128.Create(state[8], state[10], state[12], state[14]); + var s9BDF = Vector128.Create(state[9], state[11], state[13], state[15]); + + var RC03 = Load128(RCON.AsSpan(0)); + var RC47 = Load128(RCON.AsSpan(4)); + + for (int step = 0; step < steps; ++step) + { + // Add round ant + + s1357 = Sse2.Xor(s1357, Vector128.Create(RCON[step & 7], (uint)step, 0U, 0U)); + + // ARXBOX layer + + ArxBox(RC03, ref s0246, ref s1357); + ArxBox(RC47, ref s8ACE, ref s9BDF); + + // Linear layer + + var t0246 = ELL(HorizontalXor(s0246)); + var t1357 = ELL(HorizontalXor(s1357)); + + var u0246 = Sse2.Xor(s0246, s8ACE); + var u1357 = Sse2.Xor(s1357, s9BDF); + + s8ACE = s0246; + s9BDF = s1357; + + s0246 = Sse2.Xor(t1357, Sse2.Shuffle(u0246, 0x39)); + s1357 = Sse2.Xor(t0246, Sse2.Shuffle(u1357, 0x39)); + } + + Store128(Sse2.UnpackLow (s0246, s1357), state.AsSpan(0)); + Store128(Sse2.UnpackHigh(s0246, s1357), state.AsSpan(4)); + Store128(Sse2.UnpackLow (s8ACE, s9BDF), state.AsSpan(8)); + Store128(Sse2.UnpackHigh(s8ACE, s9BDF), state.AsSpan(12)); + } + else +#endif + { + uint s00 = state[0]; + uint s01 = state[1]; + uint s02 = state[2]; + uint s03 = state[3]; + uint s04 = state[4]; + uint s05 = state[5]; + uint s06 = state[6]; + uint s07 = state[7]; + uint s08 = state[8]; + uint s09 = state[9]; + uint s10 = state[10]; + uint s11 = state[11]; + uint s12 = state[12]; + uint s13 = state[13]; + uint s14 = state[14]; + uint s15 = state[15]; + + int step = 0; + while (step < steps) + { + // STEP 1 + + // Add round ant + + s01 ^= RCON[step & 7]; + s03 ^= (uint)(step++); + + // ARXBOX layer + + ArxBox(RCON[0], ref s00, ref s01); + ArxBox(RCON[1], ref s02, ref s03); + ArxBox(RCON[2], ref s04, ref s05); + ArxBox(RCON[3], ref s06, ref s07); + ArxBox(RCON[4], ref s08, ref s09); + ArxBox(RCON[5], ref s10, ref s11); + ArxBox(RCON[6], ref s12, ref s13); + ArxBox(RCON[7], ref s14, ref s15); + + // Linear layer + + uint t0246 = ELL(s00 ^ s02 ^ s04 ^ s06); + uint t1357 = ELL(s01 ^ s03 ^ s05 ^ s07); + + uint u08 = s08; + uint u09 = s09; + + s08 = s02 ^ s10 ^ t1357; + s09 = s03 ^ s11 ^ t0246; + s10 = s04 ^ s12 ^ t1357; + s11 = s05 ^ s13 ^ t0246; + s12 = s06 ^ s14 ^ t1357; + s13 = s07 ^ s15 ^ t0246; + s14 = s00 ^ u08 ^ t1357; + s15 = s01 ^ u09 ^ t0246; + + // STEP 2 + + // Add round ant + + s09 ^= RCON[step & 7]; + s11 ^= (uint)(step++); + + // ARXBOX layer + + ArxBox(RCON[0], ref s08, ref s09); + ArxBox(RCON[1], ref s10, ref s11); + ArxBox(RCON[2], ref s12, ref s13); + ArxBox(RCON[3], ref s14, ref s15); + ArxBox(RCON[4], ref s00, ref s01); + ArxBox(RCON[5], ref s02, ref s03); + ArxBox(RCON[6], ref s04, ref s05); + ArxBox(RCON[7], ref s06, ref s07); + + // Linear layer + + uint t8ACE = ELL(s08 ^ s10 ^ s12 ^ s14); + uint t9BDF = ELL(s09 ^ s11 ^ s13 ^ s15); + + uint u00 = s00; + uint u01 = s01; + + s00 = s02 ^ s10 ^ t9BDF; + s01 = s03 ^ s11 ^ t8ACE; + s02 = s04 ^ s12 ^ t9BDF; + s03 = s05 ^ s13 ^ t8ACE; + s04 = s06 ^ s14 ^ t9BDF; + s05 = s07 ^ s15 ^ t8ACE; + s06 = u00 ^ s08 ^ t9BDF; + s07 = u01 ^ s09 ^ t8ACE; + } + + state[0] = s00; + state[1] = s01; + state[2] = s02; + state[3] = s03; + state[4] = s04; + state[5] = s05; + state[6] = s06; + state[7] = s07; + state[8] = s08; + state[9] = s09; + state[10] = s10; + state[11] = s11; + state[12] = s12; + state[13] = s13; + state[14] = s14; + state[15] = s15; + } + } + +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ArxBox(Vector128<uint> rc, ref Vector128<uint> s00, ref Vector128<uint> s01) + { + s00 = Sse2.Add(s00, Sse2.ShiftRightLogical(s01, 31)); + s00 = Sse2.Add(s00, Sse2.ShiftLeftLogical(s01, 1)); + + s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 24)); + s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 8)); + + s00 = Sse2.Xor(s00, rc); + + s00 = Sse2.Add(s00, Sse2.ShiftRightLogical(s01, 17)); + s00 = Sse2.Add(s00, Sse2.ShiftLeftLogical(s01, 15)); + + s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 17)); + s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 15)); + + s00 = Sse2.Xor(s00, rc); + + s00 = Sse2.Add(s00, s01); + + s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 31)); + s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 1)); + + s00 = Sse2.Xor(s00, rc); + + s00 = Sse2.Add(s00, Sse2.ShiftRightLogical(s01, 24)); + s00 = Sse2.Add(s00, Sse2.ShiftLeftLogical(s01, 8)); + + s01 = Sse2.Xor(s01, Sse2.ShiftRightLogical(s00, 16)); + s01 = Sse2.Xor(s01, Sse2.ShiftLeftLogical(s00, 16)); + + s00 = Sse2.Xor(s00, rc); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128<uint> ELL(Vector128<uint> x) + { + var t = Sse2.ShiftLeftLogical(x, 16); + var u = Sse2.Xor(x, t); + return Sse2.Xor(t, Sse2.ShiftRightLogical(u, 16)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128<uint> HorizontalXor(Vector128<uint> x) + { + var t = Sse2.Xor(x, Sse2.Shuffle(x, 0x1B)); + return Sse2.Xor(t, Sse2.Shuffle(t, 0xB1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128<uint> Load128(ReadOnlySpan<uint> t) + { + if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16) + return MemoryMarshal.Read<Vector128<uint>>(MemoryMarshal.AsBytes(t)); + + return Vector128.Create(t[0], t[1], t[2], t[3]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Store128(Vector128<uint> s, Span<uint> t) + { + var b = MemoryMarshal.AsBytes(t); + if (BitConverter.IsLittleEndian && Unsafe.SizeOf<Vector128<uint>>() == 16) + { + MemoryMarshal.Write(b, ref s); + return; + } + + var u = s.AsUInt64(); + BinaryPrimitives.WriteUInt64LittleEndian(b[..8], u.GetElement(0)); + BinaryPrimitives.WriteUInt64LittleEndian(b[8..], u.GetElement(1)); + } +#endif + } +} diff --git a/crypto/test/src/crypto/test/SparkleTest.cs b/crypto/test/src/crypto/test/SparkleTest.cs index 584b90a52..49d40aa15 100644 --- a/crypto/test/src/crypto/test/SparkleTest.cs +++ b/crypto/test/src/crypto/test/SparkleTest.cs @@ -5,6 +5,8 @@ using System.IO; using NUnit.Framework; using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; using Org.BouncyCastle.Utilities.Test; @@ -15,46 +17,207 @@ namespace Org.BouncyCastle.Crypto.Tests public class SparkleTest { [Test, Explicit] - public void BenchDigest256() + public void BenchDigest_ESCH256() { ImplBenchDigest(SparkleDigest.SparkleParameters.ESCH256); } [Test, Explicit] - public void BenchDigest384() + public void BenchDigest_ESCH384() { ImplBenchDigest(SparkleDigest.SparkleParameters.ESCH384); } + [Test, Explicit] + public void BenchEngineAuth_SCHWAEMM128_128() + { + ImplBenchEngineAuth(SparkleEngine.SparkleParameters.SCHWAEMM128_128); + } + + [Test, Explicit] + public void BenchEngineAuth_SCHWAEMM192_192() + { + ImplBenchEngineAuth(SparkleEngine.SparkleParameters.SCHWAEMM192_192); + } + + [Test, Explicit] + public void BenchEngineAuth_SCHWAEMM256_128() + { + ImplBenchEngineAuth(SparkleEngine.SparkleParameters.SCHWAEMM256_128); + } + + [Test, Explicit] + public void BenchEngineAuth_SCHWAEMM256_256() + { + ImplBenchEngineAuth(SparkleEngine.SparkleParameters.SCHWAEMM256_256); + } + + [Test, Explicit] + public void BenchEngineDecrypt_SCHWAEMM128_128() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM128_128, false); + } + + [Test, Explicit] + public void BenchEngineDecrypt_SCHWAEMM192_192() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM192_192, false); + } + + [Test, Explicit] + public void BenchEngineDecrypt_SCHWAEMM256_128() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM256_128, false); + } + + [Test, Explicit] + public void BenchEngineDecrypt_SCHWAEMM256_256() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM256_256, false); + } + + [Test, Explicit] + public void BenchEngineEncrypt_SCHWAEMM128_128() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM128_128, true); + } + + [Test, Explicit] + public void BenchEngineEncrypt_SCHWAEMM192_192() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM192_192, true); + } + + [Test, Explicit] + public void BenchEngineEncrypt_SCHWAEMM256_128() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM256_128, true); + } + + [Test, Explicit] + public void BenchEngineEncrypt_SCHWAEMM256_256() + { + ImplBenchEngineCrypt(SparkleEngine.SparkleParameters.SCHWAEMM256_256, true); + } + + [Test] + public void TestExceptionsDigest_ESCH256() + { + ImplTestExceptionsDigest(SparkleDigest.SparkleParameters.ESCH256); + } + + [Test] + public void TestExceptionsDigest_ESCH384() + { + ImplTestExceptionsDigest(SparkleDigest.SparkleParameters.ESCH384); + } + + [Test] + public void TestExceptionsEngine_SCHWAEMM128_128() + { + ImplTestExceptionsEngine(SparkleEngine.SparkleParameters.SCHWAEMM128_128); + } + + [Test] + public void TestExceptionsEngine_SCHWAEMM192_192() + { + ImplTestExceptionsEngine(SparkleEngine.SparkleParameters.SCHWAEMM192_192); + } + + [Test] + public void TestExceptionsEngine_SCHWAEMM256_128() + { + ImplTestExceptionsEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_128); + } + + [Test] + public void TestExceptionsEngine_SCHWAEMM256_256() + { + ImplTestExceptionsEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_256); + } + + [Test] + public void TestParametersDigest_ESCH256() + { + ImplTestParametersDigest(SparkleDigest.SparkleParameters.ESCH256, 32); + } + [Test] - public void TestExceptionsDigest256() + public void TestParametersDigest_ESCH384() { - ImplTestExceptionsDigest(SparkleDigest.SparkleParameters.ESCH256, 32); + ImplTestParametersDigest(SparkleDigest.SparkleParameters.ESCH384, 48); } [Test] - public void TestExceptionsDigest384() + public void TestParametersEngine_SCHWAEMM128_128() { - ImplTestExceptionsDigest(SparkleDigest.SparkleParameters.ESCH384, 48); + ImplTestParametersEngine(SparkleEngine.SparkleParameters.SCHWAEMM128_128, 16, 16, 16); } [Test] - public void TestVectorsDigest256() + public void TestParametersEngine_SCHWAEMM192_192() + { + ImplTestParametersEngine(SparkleEngine.SparkleParameters.SCHWAEMM192_192, 24, 24, 24); + } + + [Test] + public void TestParametersEngine_SCHWAEMM256_128() + { + ImplTestParametersEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_128, 16, 32, 16); + } + + [Test] + public void TestParametersEngine_SCHWAEMM256_256() + { + ImplTestParametersEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_256, 32, 32, 32); + } + + [Test] + public void TestVectorsDigest_ESCH256() { ImplTestVectorsDigest(SparkleDigest.SparkleParameters.ESCH256, "256"); } [Test] - public void TestVectorsDigest384() + public void TestVectorsDigest_ESCH384() { ImplTestVectorsDigest(SparkleDigest.SparkleParameters.ESCH384, "384"); } - private static IDigest CreateDigest(SparkleDigest.SparkleParameters sparkleParameters) + [Test] + public void TestVectorsEngine_SCHWAEMM128_128() + { + ImplTestVectorsEngine(SparkleEngine.SparkleParameters.SCHWAEMM128_128, "128_128"); + } + + [Test] + public void TestVectorsEngine_SCHWAEMM192_192() + { + ImplTestVectorsEngine(SparkleEngine.SparkleParameters.SCHWAEMM192_192, "192_192"); + } + + [Test] + public void TestVectorsEngine_SCHWAEMM256_128() + { + ImplTestVectorsEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_128, "128_256"); + } + + [Test] + public void TestVectorsEngine_SCHWAEMM256_256() + { + ImplTestVectorsEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_256, "256_256"); + } + + private static SparkleDigest CreateDigest(SparkleDigest.SparkleParameters sparkleParameters) { return new SparkleDigest(sparkleParameters); } + private static SparkleEngine CreateEngine(SparkleEngine.SparkleParameters sparkleParameters) + { + return new SparkleEngine(sparkleParameters); + } + private static void ImplBenchDigest(SparkleDigest.SparkleParameters sparkleParameters) { var sparkle = CreateDigest(sparkleParameters); @@ -83,11 +246,437 @@ namespace Org.BouncyCastle.Crypto.Tests } } + private static void ImplBenchEngineAuth(SparkleEngine.SparkleParameters sparkleParameters) + { + var sparkle = CreateEngine(sparkleParameters); + InitEngine(sparkle, true); + + byte[] data = new byte[1024]; + for (int i = 0; i < 1024 * 1024; ++i) + { + // NOTE: .NET Core 3.1 has Span<T>, but is tested against our .NET Standard 2.0 assembly. +//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + sparkle.ProcessAadBytes(data); +#else + sparkle.ProcessAadBytes(data, 0, 1024); +#endif + } + } + + private static void ImplBenchEngineCrypt(SparkleEngine.SparkleParameters sparkleParameters, bool forEncryption) + { + var sparkle = CreateEngine(sparkleParameters); + InitEngine(sparkle, forEncryption); + + byte[] data = new byte[1024 + 64]; + for (int i = 0; i < 1024 * 1024; ++i) + { + // NOTE: .NET Core 3.1 has Span<T>, but is tested against our .NET Standard 2.0 assembly. +//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + sparkle.ProcessBytes(data.AsSpan(0, 1024), data); +#else + sparkle.ProcessBytes(data, 0, 1024, data, 0); +#endif + } + } + + private static void ImplTestExceptionsDigest(SparkleDigest.SparkleParameters sparkleParameters) + { + var sparkle = new SparkleDigest(sparkleParameters); + + try + { + sparkle.BlockUpdate(new byte[1], 1, 1); + Assert.Fail(sparkle.AlgorithmName + ": input for BlockUpdate is too short"); + } + catch (DataLengthException) + { + //expected + } + + try + { + sparkle.DoFinal(new byte[sparkle.GetDigestSize() - 1], 2); + Assert.Fail(sparkle.AlgorithmName + ": output for Dofinal is too short"); + } + catch (DataLengthException) + { + //expected + } + } + + private void ImplTestExceptionsEngine(SparkleEngine.SparkleParameters sparkleParameters) + { + var sparkle = new SparkleEngine(sparkleParameters); + + int keysize = sparkle.GetKeyBytesSize(), ivsize = sparkle.GetIVBytesSize(); + int offset; + byte[] k = new byte[keysize]; + byte[] iv = new byte[ivsize]; + byte[] m = Array.Empty<byte>(); + var param = new ParametersWithIV(new KeyParameter(k), iv); + try + { + sparkle.ProcessBytes(m, 0, m.Length, null, 0); + Assert.Fail(sparkle.AlgorithmName + " needs to be initialized before ProcessBytes"); + } + catch (InvalidOperationException) + { + //expected + } + + try + { + sparkle.ProcessByte(0x00, null, 0); + Assert.Fail(sparkle.AlgorithmName + " needs to be initialized before ProcessByte"); + } + catch (InvalidOperationException) + { + //expected + } + + try + { + sparkle.Reset(); + Assert.Fail(sparkle.AlgorithmName + " needs to be initialized before Reset"); + } + catch (InvalidOperationException) + { + //expected + } + + try + { + sparkle.DoFinal(null, m.Length); + Assert.Fail(sparkle.AlgorithmName + " needs to be initialized before DoFinal"); + } + catch (InvalidOperationException) + { + //expected + } + + try + { + sparkle.GetMac(); + sparkle.GetOutputSize(0); + sparkle.GetUpdateOutputSize(0); + } + catch (InvalidOperationException) + { + //expected + Assert.Fail(sparkle.AlgorithmName + " functions can be called before initialization"); + } + + Random rand = new Random(); + int randomNum; + while ((randomNum = rand.Next(100)) == keysize) ; + byte[] k1 = new byte[randomNum]; + while ((randomNum = rand.Next(100)) == ivsize) ; + byte[] iv1 = new byte[randomNum]; + try + { + sparkle.Init(true, new ParametersWithIV(new KeyParameter(k1), iv)); + Assert.Fail(sparkle.AlgorithmName + " k size does not match"); + } + catch (ArgumentException) + { + //expected + } + try + { + sparkle.Init(true, new ParametersWithIV(new KeyParameter(k), iv1)); + Assert.Fail(sparkle.AlgorithmName + "iv size does not match"); + } + catch (ArgumentException) + { + //expected + } + + sparkle.Init(true, param); + byte[] c1 = new byte[sparkle.GetOutputSize(m.Length)]; + try + { + sparkle.DoFinal(c1, m.Length); + } + catch (Exception) + { + Assert.Fail(sparkle.AlgorithmName + " allows no input for AAD and plaintext"); + } + byte[] mac2 = sparkle.GetMac(); + if (mac2 == null) + { + Assert.Fail("mac should not be empty after DoFinal"); + } + if (!Arrays.AreEqual(mac2, c1)) + { + Assert.Fail("mac should be equal when calling DoFinal and GetMac"); + } + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessAadByte((byte)0); + byte[] mac1 = new byte[sparkle.GetOutputSize(0)]; + sparkle.DoFinal(mac1, 0); + if (Arrays.AreEqual(mac1, mac2)) + { + Assert.Fail("mac should not match"); + } + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessByte(0, null, 0); + try + { + sparkle.ProcessAadByte(0x00); + Assert.Fail("ProcessAadByte cannot be called after encryption/decryption"); + } + catch (InvalidOperationException) + { + //expected + } + try + { + sparkle.ProcessAadBytes(new byte[1], 0, 1); + Assert.Fail("ProcessAadBytes cannot be called after encryption/decryption"); + } + catch (InvalidOperationException) + { + //expected + } + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + try + { + sparkle.ProcessAadBytes(new byte[1], 1, 1); + Assert.Fail("input for ProcessAadBytes is too short"); + } + catch (DataLengthException) + { + //expected + } + try + { + sparkle.ProcessBytes(new byte[1], 1, 1, c1, 0); + Assert.Fail("input for ProcessBytes is too short"); + } + catch (DataLengthException) + { + //expected + } + try + { + sparkle.DoFinal(new byte[2], 2); + Assert.Fail("output for DoFinal is too short"); + } + catch (OutputLengthException) + { + //expected + } + + ImplTestExceptionsGetUpdateOutputSize(sparkle, false, param, 100); + ImplTestExceptionsGetUpdateOutputSize(sparkle, true, param, 100); + + mac1 = new byte[sparkle.GetOutputSize(0)]; + mac2 = new byte[sparkle.GetOutputSize(0)]; + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessAadBytes(new byte[2], 0, 2); + sparkle.DoFinal(mac1, 0); + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessAadByte(0x00); + sparkle.ProcessAadByte(0x00); + sparkle.DoFinal(mac2, 0); + + if (!Arrays.AreEqual(mac1, mac2)) + { + Assert.Fail("mac should match for the same AAD with different ways of inputting"); + } + + byte[] aad2 = { 0, 1, 2, 3, 4 }; + byte[] aad3 = { 0, 0, 1, 2, 3, 4, 5 }; + byte[] m2 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + byte[] m3 = { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + byte[] m4 = new byte[m2.Length]; + byte[] c2 = new byte[sparkle.GetOutputSize(m2.Length)]; + byte[] c3 = new byte[sparkle.GetOutputSize(m3.Length)]; + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessAadBytes(aad2, 0, aad2.Length); + offset = sparkle.ProcessBytes(m2, 0, m2.Length, c2, 0); + sparkle.DoFinal(c2, offset); + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessAadBytes(aad3, 1, aad2.Length); + offset = sparkle.ProcessBytes(m3, 1, m2.Length, c3, 1); + sparkle.DoFinal(c3, offset + 1); + + byte[] c3_partial = new byte[c2.Length]; + Array.Copy(c3, 1, c3_partial, 0, c2.Length); + if (!Arrays.AreEqual(c2, c3_partial)) + { + Assert.Fail("mac should match for the same AAD and message with different offset for both input and output"); + } + + sparkle.Init(false, param); + sparkle.ProcessAadBytes(aad2, 0, aad2.Length); + offset = sparkle.ProcessBytes(c2, 0, c2.Length, m4, 0); + sparkle.DoFinal(m4, offset); + if (!Arrays.AreEqual(m2, m4)) + { + Assert.Fail("The encryption and decryption does not recover the plaintext"); + } + + c2[c2.Length - 1] ^= 1; + + sparkle.ProcessAadBytes(aad2, 0, aad2.Length); + offset = sparkle.ProcessBytes(c2, 0, c2.Length, m4, 0); + try + { + sparkle.DoFinal(m4, offset); + Assert.Fail("The decryption should fail"); + } + catch (InvalidCipherTextException) + { + //expected; + } + c2[c2.Length - 1] ^= 1; + + byte[] m7 = new byte[32 + rand.Next(32)]; + rand.NextBytes(m7); + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + byte[] c7 = new byte[sparkle.GetOutputSize(m7.Length)]; + byte[] c8 = new byte[c7.Length]; + byte[] c9 = new byte[c7.Length]; + sparkle.Init(true, param); + sparkle.ProcessAadBytes(aad2, 0, aad2.Length); + offset = sparkle.ProcessBytes(m7, 0, m7.Length, c7, 0); + sparkle.DoFinal(c7, offset); + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + sparkle.ProcessAadBytes(aad2, 0, aad2.Length); + offset = sparkle.ProcessBytes(m7, 0, m7.Length / 2, c8, 0); + offset += sparkle.ProcessBytes(m7, m7.Length / 2, m7.Length - m7.Length / 2, c8, offset); + offset += sparkle.DoFinal(c8, offset); + + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + int split = rand.Next(1, m7.Length); + sparkle.ProcessAadBytes(aad2, 0, aad2.Length); + offset = sparkle.ProcessBytes(m7, 0, split, c9, 0); + offset += sparkle.ProcessBytes(m7, split, m7.Length - split, c9, offset); + offset += sparkle.DoFinal(c9, offset); + if (!Arrays.AreEqual(c7, c8) || !Arrays.AreEqual(c7, c9)) + { + Assert.Fail("Splitting input of plaintext should output the same ciphertext"); + } + + // NOTE: .NET Core 3.1 has Span<T>, but is tested against our .NET Standard 2.0 assembly. +//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + // TODO Maybe use a different IV for this + sparkle.Init(true, param); + Span<byte> c4 = new byte[sparkle.GetOutputSize(m2.Length)]; + sparkle.ProcessAadBytes(aad2); + offset = sparkle.ProcessBytes(m2, c4); + offset += sparkle.DoFinal(c4[offset..]); + if (!c4[..offset].SequenceEqual(c2)) + { + Assert.Fail("Encryption should match for the same AAD and message with/without Span-based API"); + } + + sparkle.Init(false, param); + Span<byte> m6 = new byte[m2.Length]; + sparkle.ProcessAadBytes(aad2); + offset = sparkle.ProcessBytes(c2, m6); + offset += sparkle.DoFinal(m6[offset..]); + if (!m6[..offset].SequenceEqual(m2)) + { + Assert.Fail("Decryption should match for the same AAD and message with/without Span-based API"); + } +#endif + } + + private static void ImplTestExceptionsGetUpdateOutputSize(SparkleEngine sparkle, bool forEncryption, + ICipherParameters parameters, int maxInputSize) + { + // TODO Maybe use a different IV for this + sparkle.Init(forEncryption, parameters); + + int maxOutputSize = sparkle.GetUpdateOutputSize(maxInputSize); + + byte[] input = new byte[maxInputSize]; + byte[] output = new byte[maxOutputSize]; + + for (int inputSize = 0; inputSize <= maxInputSize; ++inputSize) + { + // TODO Maybe use a different IV for this + sparkle.Init(forEncryption, parameters); + + int outputSize = sparkle.GetUpdateOutputSize(inputSize); + if (outputSize > 0) + { + try + { + sparkle.ProcessBytes(input, 0, inputSize, output, maxOutputSize - outputSize + 1); + Assert.Fail("output for ProcessBytes is too short"); + } + catch (OutputLengthException) + { + //expected + } + } + else + { + sparkle.ProcessBytes(input, 0, inputSize, null, 0); + } + } + } + + private static void ImplTestParametersDigest(SparkleDigest.SparkleParameters sparkleParameters, int digestSize) + { + var sparkle = CreateDigest(sparkleParameters); + + Assert.AreEqual(digestSize, sparkle.GetDigestSize(), + sparkle.AlgorithmName + ": GetDigestSize() is not correct"); + } + + private static void ImplTestParametersEngine(SparkleEngine.SparkleParameters sparkleParameters, int keySize, + int ivSize, int macSize) + { + var sparkle = CreateEngine(sparkleParameters); + + Assert.AreEqual(keySize, sparkle.GetKeyBytesSize(), + "key bytes of " + sparkle.AlgorithmName + " is not correct"); + Assert.AreEqual(ivSize, sparkle.GetIVBytesSize(), + "iv bytes of " + sparkle.AlgorithmName + " is not correct"); + + var parameters = new ParametersWithIV(new KeyParameter(new byte[keySize]), new byte[ivSize]); + + sparkle.Init(true, parameters); + Assert.AreEqual(macSize, sparkle.GetOutputSize(0), + "GetOutputSize of " + sparkle.AlgorithmName + " is incorrect for encryption"); + + sparkle.Init(false, parameters); + Assert.AreEqual(0, sparkle.GetOutputSize(macSize), + "GetOutputSize of " + sparkle.AlgorithmName + " is incorrect for decryption"); + } + private static void ImplTestVectorsDigest(SparkleDigest.SparkleParameters sparkleParameters, string filename) { Random random = new Random(); var sparkle = CreateDigest(sparkleParameters); - var map = new Dictionary<string, string>(); + var buf = new Dictionary<string, string>(); using (var src = new StreamReader( SimpleTest.GetTestDataAsStream("crypto.sparkle.LWC_HASH_KAT_" + filename + ".txt"))) { @@ -97,9 +686,9 @@ namespace Org.BouncyCastle.Crypto.Tests int a = line.IndexOf('='); if (a < 0) { - byte[] ptByte = Hex.Decode(map["Msg"]); - byte[] expected = Hex.Decode(map["MD"]); - map.Clear(); + byte[] ptByte = Hex.Decode(buf["Msg"]); + byte[] expected = Hex.Decode(buf["MD"]); + buf.Clear(); byte[] hash = new byte[sparkle.GetDigestSize()]; @@ -118,37 +707,87 @@ namespace Org.BouncyCastle.Crypto.Tests } else { - map[line.Substring(0, a).Trim()] = line.Substring(a + 1).Trim(); + buf[line.Substring(0, a).Trim()] = line.Substring(a + 1).Trim(); } } } } - private static void ImplTestExceptionsDigest(SparkleDigest.SparkleParameters sparkleParameters, int digestSize) + private static void ImplTestVectorsEngine(SparkleEngine.SparkleParameters sparkleParameters, string filename) { - var sparkle = new SparkleDigest(sparkleParameters); + Random random = new Random(); + var sparkle = CreateEngine(sparkleParameters); + var buf = new Dictionary<string, string>(); + using (var src = new StreamReader( + SimpleTest.GetTestDataAsStream("crypto.sparkle.LWC_AEAD_KAT_" + filename + ".txt"))) + { + string line; + while ((line = src.ReadLine()) != null) + { + var data = line.Split(' '); + if (data.Length == 1) + { + byte[] key = Hex.Decode(buf["Key"]); + byte[] nonce = Hex.Decode(buf["Nonce"]); + byte[] ad = Hex.Decode(buf["AD"]); + byte[] pt = Hex.Decode(buf["PT"]); + byte[] ct = Hex.Decode(buf["CT"]); + buf.Clear(); - Assert.AreEqual(digestSize, sparkle.GetDigestSize(), - sparkle.AlgorithmName + ": GetDigestSize() is not correct"); + var parameters = new ParametersWithIV(new KeyParameter(key), nonce); - try - { - sparkle.BlockUpdate(new byte[1], 1, 1); - Assert.Fail(sparkle.AlgorithmName + ": input for BlockUpdate is too short"); - } - catch (DataLengthException) - { - //expected - } - try - { - sparkle.DoFinal(new byte[digestSize - 1], 2); - Assert.Fail(sparkle.AlgorithmName + ": output for Dofinal is too short"); - } - catch (DataLengthException) - { - //expected + // Encrypt + { + sparkle.Init(true, parameters); + + var rv = new byte[sparkle.GetOutputSize(pt.Length)]; + random.NextBytes(rv); // should overwrite any existing data + + sparkle.ProcessAadBytes(ad, 0, ad.Length); + int len = sparkle.ProcessBytes(pt, 0, pt.Length, rv, 0); + len += sparkle.DoFinal(rv, len); + + Assert.True(Arrays.AreEqual(rv, 0, len, ct, 0, ct.Length)); + } + + // Decrypt + { + sparkle.Init(false, parameters); + + var rv = new byte[sparkle.GetOutputSize(ct.Length)]; + random.NextBytes(rv); // should overwrite any existing data + + sparkle.ProcessAadBytes(ad, 0, ad.Length); + int len = sparkle.ProcessBytes(ct, 0, ct.Length, rv, 0); + len += sparkle.DoFinal(rv, len); + + Assert.True(Arrays.AreEqual(rv, 0, len, pt, 0, pt.Length)); + } + } + else + { + if (data.Length >= 3) + { + buf[data[0].Trim()] = data[2].Trim(); + } + else + { + buf[data[0].Trim()] = ""; + } + + } + } } } + + private static void InitEngine(SparkleEngine sparkle, bool forEncryption) + { + int keySize = sparkle.GetKeyBytesSize(); + int ivSize = sparkle.GetIVBytesSize(); + int macSize = keySize * 8; + + var parameters = new AeadParameters(new KeyParameter(new byte[keySize]), macSize, new byte[ivSize], null); + sparkle.Init(forEncryption, parameters); + } } } |