diff --git a/crypto/src/crypto/digests/AsconDigest.cs b/crypto/src/crypto/digests/AsconDigest.cs
index 20d39ce77..84c9299d3 100644
--- a/crypto/src/crypto/digests/AsconDigest.cs
+++ b/crypto/src/crypto/digests/AsconDigest.cs
@@ -20,8 +20,6 @@ namespace Org.BouncyCastle.Crypto.Digests
{
AsconHash,
AsconHashA,
- AsconXof,
- AsconXofA,
}
private readonly AsconParameters m_asconParameters;
@@ -47,12 +45,6 @@ namespace Org.BouncyCastle.Crypto.Digests
case AsconParameters.AsconHashA:
ASCON_PB_ROUNDS = 8;
break;
- case AsconParameters.AsconXof:
- ASCON_PB_ROUNDS = 12;
- break;
- case AsconParameters.AsconXofA:
- ASCON_PB_ROUNDS = 8;
- break;
default:
throw new ArgumentException("Invalid parameter settings for Ascon Hash");
}
@@ -67,8 +59,6 @@ namespace Org.BouncyCastle.Crypto.Digests
{
case AsconParameters.AsconHash: return "Ascon-Hash";
case AsconParameters.AsconHashA: return "Ascon-HashA";
- case AsconParameters.AsconXof: return "Ascon-Xof";
- case AsconParameters.AsconXofA: return "Ascon-XofA";
default: throw new InvalidOperationException();
}
}
@@ -167,10 +157,8 @@ namespace Org.BouncyCastle.Crypto.Digests
#else
Check.OutputLength(output, outOff, 32, "output buffer is too short");
- m_buf[m_bufPos] = 0x80;
- x0 ^= Pack.BE_To_UInt64(m_buf, 0) & (ulong.MaxValue << (56 - (m_bufPos << 3)));
+ FinishAbsorbing();
- P(12);
Pack.UInt64_To_BE(x0, output, outOff);
for (int i = 0; i < 3; ++i)
@@ -191,10 +179,8 @@ namespace Org.BouncyCastle.Crypto.Digests
{
Check.OutputLength(output, 32, "output buffer is too short");
- m_buf[m_bufPos] = 0x80;
- x0 ^= Pack.BE_To_UInt64(m_buf) & (ulong.MaxValue << (56 - (m_bufPos << 3)));
+ FinishAbsorbing();
- P(12);
Pack.UInt64_To_BE(x0, output);
for (int i = 0; i < 3; ++i)
@@ -231,25 +217,19 @@ namespace Org.BouncyCastle.Crypto.Digests
x3 = 4834782570098516968UL;
x4 = 3787428097924915520UL;
break;
- case AsconParameters.AsconXof:
- x0 = 13077933504456348694UL;
- x1 = 3121280575360345120UL;
- x2 = 7395939140700676632UL;
- x3 = 6533890155656471820UL;
- x4 = 5710016986865767350UL;
- break;
- case AsconParameters.AsconXofA:
- x0 = 4940560291654768690UL;
- x1 = 14811614245468591410UL;
- x2 = 17849209150987444521UL;
- x3 = 2623493988082852443UL;
- x4 = 12162917349548726079UL;
- break;
default:
throw new InvalidOperationException();
}
}
+ private void FinishAbsorbing()
+ {
+ m_buf[m_bufPos] = 0x80;
+ x0 ^= Pack.BE_To_UInt64(m_buf, 0) & (ulong.MaxValue << (56 - (m_bufPos << 3)));
+
+ P(12);
+ }
+
private void P(int nr)
{
//if (nr >= 8)
diff --git a/crypto/src/crypto/digests/AsconXof.cs b/crypto/src/crypto/digests/AsconXof.cs
new file mode 100644
index 000000000..04af0b7e3
--- /dev/null
+++ b/crypto/src/crypto/digests/AsconXof.cs
@@ -0,0 +1,399 @@
+using System;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
+
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+ /// <summary>ASCON v1.2 XOF, https://ascon.iaik.tugraz.at/ .</summary>
+ /// <remarks>
+ /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/ascon-spec-final.pdf<br/>
+ /// ASCON v1.2 XOF with reference to C Reference Impl from: https://github.com/ascon/ascon-c .
+ /// </remarks>
+ public sealed class AsconXof
+ : IXof
+ {
+ public enum AsconParameters
+ {
+ AsconXof,
+ AsconXofA,
+ }
+
+ private readonly AsconParameters m_asconParameters;
+ private readonly int ASCON_PB_ROUNDS;
+
+ private ulong x0;
+ private ulong x1;
+ private ulong x2;
+ private ulong x3;
+ private ulong x4;
+
+ private readonly byte[] m_buf = new byte[8];
+ private int m_bufPos = 0;
+ private bool m_squeezing = false;
+
+ public AsconXof(AsconParameters parameters)
+ {
+ m_asconParameters = parameters;
+ switch (parameters)
+ {
+ case AsconParameters.AsconXof:
+ ASCON_PB_ROUNDS = 12;
+ break;
+ case AsconParameters.AsconXofA:
+ ASCON_PB_ROUNDS = 8;
+ break;
+ default:
+ throw new ArgumentException("Invalid parameter settings for Ascon XOF");
+ }
+ Reset();
+ }
+
+ public string AlgorithmName
+ {
+ get
+ {
+ switch (m_asconParameters)
+ {
+ case AsconParameters.AsconXof: return "Ascon-Xof";
+ case AsconParameters.AsconXofA: return "Ascon-XofA";
+ default: throw new InvalidOperationException();
+ }
+ }
+ }
+
+ public int GetDigestSize() => 32;
+
+ public int GetByteLength() => 8;
+
+ public void Update(byte input)
+ {
+ if (m_squeezing)
+ throw new InvalidOperationException("attempt to absorb while squeezing");
+
+ m_buf[m_bufPos] = input;
+ if (++m_bufPos == 8)
+ {
+ x0 ^= Pack.BE_To_UInt64(m_buf, 0);
+ P(ASCON_PB_ROUNDS);
+ m_bufPos = 0;
+ }
+ }
+
+ public void BlockUpdate(byte[] input, int inOff, int inLen)
+ {
+ Check.DataLength(input, inOff, inLen, "input buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ BlockUpdate(input.AsSpan(inOff, inLen));
+#else
+ if (m_squeezing)
+ throw new InvalidOperationException("attempt to absorb while squeezing");
+
+ if (inLen < 1)
+ return;
+
+ int available = 8 - m_bufPos;
+ if (inLen < available)
+ {
+ Array.Copy(input, inOff, m_buf, m_bufPos, inLen);
+ m_bufPos += inLen;
+ return;
+ }
+
+ int inPos = 0;
+ if (m_bufPos > 0)
+ {
+ Array.Copy(input, inOff, m_buf, m_bufPos, available);
+ inPos += available;
+ x0 ^= Pack.BE_To_UInt64(m_buf, 0);
+ P(ASCON_PB_ROUNDS);
+ }
+
+ int remaining;
+ while ((remaining = inLen - inPos) >= 8)
+ {
+ x0 ^= Pack.BE_To_UInt64(input, inOff + inPos);
+ P(ASCON_PB_ROUNDS);
+ inPos += 8;
+ }
+
+ Array.Copy(input, inOff + inPos, m_buf, 0, remaining);
+ m_bufPos = remaining;
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public void BlockUpdate(ReadOnlySpan<byte> input)
+ {
+ if (m_squeezing)
+ throw new InvalidOperationException("attempt to absorb while squeezing");
+
+ int available = 8 - m_bufPos;
+ if (input.Length < available)
+ {
+ input.CopyTo(m_buf.AsSpan(m_bufPos));
+ m_bufPos += input.Length;
+ return;
+ }
+
+ if (m_bufPos > 0)
+ {
+ input[..available].CopyTo(m_buf.AsSpan(m_bufPos));
+ x0 ^= Pack.BE_To_UInt64(m_buf);
+ P(ASCON_PB_ROUNDS);
+ input = input[available..];
+ }
+
+ while (input.Length >= 8)
+ {
+ x0 ^= Pack.BE_To_UInt64(input);
+ P(ASCON_PB_ROUNDS);
+ input = input[8..];
+ }
+
+ input.CopyTo(m_buf);
+ m_bufPos = input.Length;
+ }
+#endif
+
+ public int DoFinal(byte[] output, int outOff)
+ {
+ return OutputFinal(output, outOff, GetDigestSize());
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public int DoFinal(Span<byte> output)
+ {
+ int digestSize = GetDigestSize();
+
+ Check.OutputLength(output, digestSize, "output buffer is too short");
+
+ return OutputFinal(output[..digestSize]);
+ }
+#endif
+
+ public int OutputFinal(byte[] output, int outOff, int outLen)
+ {
+ Check.OutputLength(output, outOff, outLen, "output buffer is too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return OutputFinal(output.AsSpan(outOff, outLen));
+#else
+ int length = Output(output, outOff, outLen);
+
+ Reset();
+
+ return length;
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public int OutputFinal(Span<byte> output)
+ {
+ int length = Output(output);
+
+ Reset();
+
+ return length;
+ }
+#endif
+
+ public int Output(byte[] output, int outOff, int outLen)
+ {
+ Check.OutputLength(output, outOff, outLen, "output buffer is too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return Output(output.AsSpan(outOff, outLen));
+#else
+ int result = outLen;
+
+ if (!m_squeezing)
+ {
+ FinishAbsorbing();
+
+ if (outLen >= 8)
+ {
+ Pack.UInt64_To_BE(x0, output, outOff);
+ outOff += 8;
+ outLen -= 8;
+ }
+ else
+ {
+ Pack.UInt64_To_BE(x0, m_buf);
+ m_bufPos = 0;
+ }
+ }
+
+ if (m_bufPos < 8)
+ {
+ int available = 8 - m_bufPos;
+ if (outLen <= available)
+ {
+ Array.Copy(m_buf, m_bufPos, output, outOff, outLen);
+ m_bufPos += outLen;
+ return result;
+ }
+
+ Array.Copy(m_buf, m_bufPos, output, outOff, available);
+ outOff += available;
+ outLen -= available;
+ //m_bufPos = 8;
+ }
+
+ while (outLen >= 8)
+ {
+ P(ASCON_PB_ROUNDS);
+ Pack.UInt64_To_BE(x0, output, outOff);
+ outOff += 8;
+ outLen -= 8;
+ }
+
+ if (outLen > 0)
+ {
+ P(ASCON_PB_ROUNDS);
+ Pack.UInt64_To_BE(x0, m_buf);
+ Array.Copy(m_buf, 0, output, outOff, outLen);
+ }
+
+ m_bufPos = outLen;
+ return result;
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public int Output(Span<byte> output)
+ {
+ int result = output.Length;
+
+ if (!m_squeezing)
+ {
+ FinishAbsorbing();
+
+ if (output.Length >= 8)
+ {
+ Pack.UInt64_To_BE(x0, output);
+ output = output[8..];
+ }
+ else
+ {
+ Pack.UInt64_To_BE(x0, m_buf);
+ m_bufPos = 0;
+ }
+ }
+
+ if (m_bufPos < 8)
+ {
+ int available = 8 - m_bufPos;
+ if (output.Length <= available)
+ {
+ output.CopyFrom(m_buf.AsSpan(m_bufPos));
+ m_bufPos += output.Length;
+ return result;
+ }
+
+ output[..available].CopyFrom(m_buf.AsSpan(m_bufPos));
+ output = output[available..];
+ //m_bufPos = 8;
+ }
+
+ while (output.Length >= 8)
+ {
+ P(ASCON_PB_ROUNDS);
+ Pack.UInt64_To_BE(x0, output);
+ output = output[8..];
+ }
+
+ if (!output.IsEmpty)
+ {
+ P(ASCON_PB_ROUNDS);
+ Pack.UInt64_To_BE(x0, m_buf);
+ output.CopyFrom(m_buf);
+ }
+
+ m_bufPos = output.Length;
+ return result;
+ }
+#endif
+
+ public void Reset()
+ {
+ Array.Clear(m_buf, 0, m_buf.Length);
+ m_bufPos = 0;
+ m_squeezing = false;
+
+ switch (m_asconParameters)
+ {
+ case AsconParameters.AsconXof:
+ x0 = 13077933504456348694UL;
+ x1 = 3121280575360345120UL;
+ x2 = 7395939140700676632UL;
+ x3 = 6533890155656471820UL;
+ x4 = 5710016986865767350UL;
+ break;
+ case AsconParameters.AsconXofA:
+ x0 = 4940560291654768690UL;
+ x1 = 14811614245468591410UL;
+ x2 = 17849209150987444521UL;
+ x3 = 2623493988082852443UL;
+ x4 = 12162917349548726079UL;
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ private void FinishAbsorbing()
+ {
+ m_buf[m_bufPos] = 0x80;
+ x0 ^= Pack.BE_To_UInt64(m_buf, 0) & (ulong.MaxValue << (56 - (m_bufPos << 3)));
+
+ P(12);
+
+ m_bufPos = 8;
+ m_squeezing = true;
+ }
+
+ private void P(int nr)
+ {
+ //if (nr >= 8)
+ {
+ if (nr == 12)
+ {
+ ROUND(0xf0UL);
+ ROUND(0xe1UL);
+ ROUND(0xd2UL);
+ ROUND(0xc3UL);
+ }
+ ROUND(0xb4UL);
+ ROUND(0xa5UL);
+ }
+ ROUND(0x96UL);
+ ROUND(0x87UL);
+ ROUND(0x78UL);
+ ROUND(0x69UL);
+ ROUND(0x5aUL);
+ ROUND(0x4bUL);
+ }
+
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ private void ROUND(ulong c)
+ {
+ ulong t0 = x0 ^ x1 ^ x2 ^ x3 ^ c ^ (x1 & (x0 ^ x2 ^ x4 ^ c));
+ ulong t1 = x0 ^ x2 ^ x3 ^ x4 ^ c ^ ((x1 ^ x2 ^ c) & (x1 ^ x3));
+ ulong t2 = x1 ^ x2 ^ x4 ^ c ^ (x3 & x4);
+ ulong t3 = x0 ^ x1 ^ x2 ^ c ^ (~x0 & (x3 ^ x4));
+ ulong t4 = x1 ^ x3 ^ x4 ^ ((x0 ^ x4) & x1);
+ x0 = t0 ^ Longs.RotateRight(t0, 19) ^ Longs.RotateRight(t0, 28);
+ x1 = t1 ^ Longs.RotateRight(t1, 39) ^ Longs.RotateRight(t1, 61);
+ x2 = ~(t2 ^ Longs.RotateRight(t2, 1) ^ Longs.RotateRight(t2, 6));
+ x3 = t3 ^ Longs.RotateRight(t3, 10) ^ Longs.RotateRight(t3, 17);
+ x4 = t4 ^ Longs.RotateRight(t4, 7) ^ Longs.RotateRight(t4, 41);
+ }
+ }
+}
diff --git a/crypto/test/src/crypto/test/AsconTest.cs b/crypto/test/src/crypto/test/AsconTest.cs
index 95dd9c210..59e5aaa6d 100644
--- a/crypto/test/src/crypto/test/AsconTest.cs
+++ b/crypto/test/src/crypto/test/AsconTest.cs
@@ -24,13 +24,13 @@ namespace Org.BouncyCastle.Crypto.Tests
{
ImplTestVectorsHash(AsconDigest.AsconParameters.AsconHashA, "asconhasha");
ImplTestVectorsHash(AsconDigest.AsconParameters.AsconHash, "asconhash");
- ImplTestVectorsHash(AsconDigest.AsconParameters.AsconXof, "asconxof");
- ImplTestVectorsHash(AsconDigest.AsconParameters.AsconXofA, "asconxofa");
+ ImplTestVectorsXof(AsconXof.AsconParameters.AsconXof, "asconxof");
+ ImplTestVectorsXof(AsconXof.AsconParameters.AsconXofA, "asconxofa");
ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconHashA), 32);
ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconHash), 32);
- ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconXof), 32);
- ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconXofA), 32);
+ ImplTestExceptions(new AsconXof(AsconXof.AsconParameters.AsconXof), 32);
+ ImplTestExceptions(new AsconXof(AsconXof.AsconParameters.AsconXofA), 32);
AsconEngine asconEngine = new AsconEngine(AsconEngine.AsconParameters.ascon80pq);
ImplTestExceptions(asconEngine);
@@ -439,11 +439,12 @@ namespace Org.BouncyCastle.Crypto.Tests
"GetOutputSize of " + asconEngine.AlgorithmName + " is incorrect for decryption");
}
- private void ImplTestVectorsHash(AsconDigest.AsconParameters AsconParameters, string filename)
+ private void ImplTestVectorsHash(AsconDigest.AsconParameters asconParameters, string filename)
{
- AsconDigest Ascon = new AsconDigest(AsconParameters);
+ AsconDigest ascon = new AsconDigest(asconParameters);
var buf = new Dictionary<string, string>();
- using (var src = new StreamReader(SimpleTest.GetTestDataAsStream("crypto.ascon."+filename+"_LWC_HASH_KAT_256.txt")))
+ using (var src = new StreamReader(
+ SimpleTest.GetTestDataAsStream("crypto.ascon." + filename + "_LWC_HASH_KAT_256.txt")))
{
Dictionary<string, string> map = new Dictionary<string, string>();
string line;
@@ -456,11 +457,50 @@ namespace Org.BouncyCastle.Crypto.Tests
byte[] expected = Hex.Decode(map["MD"]);
map.Clear();
- Ascon.BlockUpdate(ptByte, 0, ptByte.Length);
- byte[] hash = new byte[Ascon.GetDigestSize()];
- Ascon.DoFinal(hash, 0);
+ ascon.BlockUpdate(ptByte, 0, ptByte.Length);
+ byte[] hash = new byte[ascon.GetDigestSize()];
+ ascon.DoFinal(hash, 0);
Assert.True(Arrays.AreEqual(expected, hash));
- Ascon.Reset();
+ ascon.Reset();
+ }
+ else
+ {
+ if (data.Length >= 3)
+ {
+ map[data[0].Trim()] = data[2].Trim();
+ }
+ else
+ {
+ map[data[0].Trim()] = "";
+ }
+ }
+ }
+ }
+ }
+
+ private void ImplTestVectorsXof(AsconXof.AsconParameters asconParameters, string filename)
+ {
+ AsconXof ascon = new AsconXof(asconParameters);
+ var buf = new Dictionary<string, string>();
+ using (var src = new StreamReader(
+ SimpleTest.GetTestDataAsStream("crypto.ascon." + filename + "_LWC_HASH_KAT_256.txt")))
+ {
+ Dictionary<string, string> map = new Dictionary<string, string>();
+ string line;
+ while ((line = src.ReadLine()) != null)
+ {
+ var data = line.Split(' ');
+ if (data.Length == 1)
+ {
+ byte[] ptByte = Hex.Decode(map["Msg"]);
+ byte[] expected = Hex.Decode(map["MD"]);
+ map.Clear();
+
+ ascon.BlockUpdate(ptByte, 0, ptByte.Length);
+ byte[] hash = new byte[ascon.GetDigestSize()];
+ ascon.DoFinal(hash, 0);
+ Assert.True(Arrays.AreEqual(expected, hash));
+ ascon.Reset();
}
else
{
@@ -501,5 +541,30 @@ namespace Org.BouncyCastle.Crypto.Tests
//expected
}
}
+
+ private void ImplTestExceptions(AsconXof asconXof, int digestSize)
+ {
+ Assert.AreEqual(digestSize, asconXof.GetDigestSize(),
+ asconXof.AlgorithmName + ": digest size is not correct");
+
+ try
+ {
+ asconXof.BlockUpdate(new byte[1], 1, 1);
+ Assert.Fail(asconXof.AlgorithmName + ": input for BlockUpdate is too short");
+ }
+ catch (DataLengthException)
+ {
+ //expected
+ }
+ try
+ {
+ asconXof.DoFinal(new byte[digestSize - 1], 2);
+ Assert.Fail(asconXof.AlgorithmName + ": output for DoFinal is too short");
+ }
+ catch (OutputLengthException)
+ {
+ //expected
+ }
+ }
}
}
|