diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs
index aa7eb98afa..92d2b6f255 100644
--- a/src/Neo/Cryptography/Murmur32.cs
+++ b/src/Neo/Cryptography/Murmur32.cs
@@ -11,6 +11,7 @@
using System;
using System.Buffers.Binary;
+using System.IO.Hashing;
using System.Runtime.CompilerServices;
namespace Neo.Cryptography
@@ -19,7 +20,7 @@ namespace Neo.Cryptography
/// Computes the murmur hash for the input data.
/// Murmur32 is a non-cryptographic hash function.
///
- public sealed class Murmur32
+ public sealed class Murmur32 : NonCryptographicHashAlgorithm
{
private const uint c1 = 0xcc9e2d51;
private const uint c2 = 0x1b873593;
@@ -32,6 +33,9 @@ public sealed class Murmur32
private uint _hash;
private int _length;
+ private uint _tail;
+ private int _tailLength;
+
public const int HashSizeInBits = 32;
[Obsolete("Use HashSizeInBits")]
@@ -41,46 +45,73 @@ public sealed class Murmur32
/// Initializes a new instance of the class with the specified seed.
///
/// The seed to be used.
- public Murmur32(uint seed)
+ public Murmur32(uint seed) : base(HashSizeInBits / 8)
{
_seed = seed;
+ Reset();
}
- ///
- /// Append data to murmur computation
- ///
- /// Source
- private void Append(ReadOnlySpan source)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Reset()
+ {
+ _hash = _seed;
+ _length = 0;
+ _tailLength = 0;
+ _tail = 0;
+ }
+
+ ///
+ public override void Append(ReadOnlySpan source)
{
_length += source.Length;
+ if (_tailLength > 0)
+ {
+ var remaining = Math.Min(4 - _tailLength, source.Length);
+ _tail ^= ReadUInt32(source[..remaining]) << (_tailLength * 8);
+ _tailLength += remaining;
+ if (_tailLength == 4)
+ {
+ Mix(_tail);
+ _tailLength = 0;
+ _tail = 0;
+ }
+ source = source[remaining..];
+ }
+#if NET7_0_OR_GREATER
+ for (; source.Length >= 16; source = source[16..])
+ {
+ var k = BinaryPrimitives.ReadUInt128LittleEndian(source);
+ Mix((uint)k);
+ Mix((uint)(k >> 32));
+ Mix((uint)(k >> 64));
+ Mix((uint)(k >> 96));
+ }
+#endif
+
for (; source.Length >= 4; source = source[4..])
{
- var k = BinaryPrimitives.ReadUInt32LittleEndian(source);
- k *= c1;
- k = Helper.RotateLeft(k, r1);
- k *= c2;
- _hash ^= k;
- _hash = Helper.RotateLeft(_hash, r2);
- _hash = _hash * m + n;
+ Mix(BinaryPrimitives.ReadUInt32LittleEndian(source));
}
+
if (source.Length > 0)
{
- uint remainingBytes = 0;
- switch (source.Length)
- {
- case 3: remainingBytes ^= (uint)source[2] << 16; goto case 2;
- case 2: remainingBytes ^= (uint)source[1] << 8; goto case 1;
- case 1: remainingBytes ^= source[0]; break;
- }
- remainingBytes *= c1;
- remainingBytes = Helper.RotateLeft(remainingBytes, r1);
- remainingBytes *= c2;
- _hash ^= remainingBytes;
+ _tail = ReadUInt32(source);
+ _tailLength = source.Length;
}
}
- private uint GetCurrentHashUInt32()
+ ///
+ protected override void GetCurrentHashCore(Span destination)
+ {
+ BinaryPrimitives.WriteUInt32LittleEndian(destination, GetCurrentHashUInt32());
+ }
+
+ internal uint GetCurrentHashUInt32()
{
+ if (_tailLength > 0)
+ _hash ^= Helper.RotateLeft(_tail * c1, r1) * c2;
+
var state = _hash ^ (uint)_length;
state ^= state >> 16;
state *= 0x85ebca6b;
@@ -91,10 +122,27 @@ private uint GetCurrentHashUInt32()
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Initialize()
+ private void Mix(uint k)
{
- _hash = _seed;
- _length = 0;
+ k *= c1;
+ k = Helper.RotateLeft(k, r1);
+ k *= c2;
+ _hash ^= k;
+ _hash = Helper.RotateLeft(_hash, r2);
+ _hash = _hash * m + n;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint ReadUInt32(ReadOnlySpan source)
+ {
+ uint value = 0;
+ switch (source.Length)
+ {
+ case 3: value ^= (uint)source[2] << 16; goto case 2;
+ case 2: value ^= (uint)source[1] << 8; goto case 1;
+ case 1: value ^= source[0]; break;
+ }
+ return value;
}
///
@@ -110,14 +158,14 @@ public byte[] ComputeHash(ReadOnlySpan data)
}
///
- /// Computes the murmur hash for the input data and resets the state.
+ /// Resets the state and computes the murmur hash for the input data.
///
/// The input to compute the hash code for.
/// The computed hash code in uint.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ComputeHashUInt32(ReadOnlySpan data)
{
- Initialize();
+ Reset();
Append(data);
return GetCurrentHashUInt32();
}
diff --git a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs
index 11797218fb..241a5845b8 100644
--- a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs
+++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs
@@ -11,6 +11,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Cryptography;
+using System;
using System.Buffers.Binary;
namespace Neo.UnitTests.Cryptography
@@ -47,6 +48,51 @@ public void TestComputeHashUInt32()
var murmur3 = new Murmur32(10u);
var hash = murmur3.ComputeHashUInt32("hello worldhello world"u8.ToArray());
Assert.AreEqual(60539726u, hash);
+
+ hash = murmur3.ComputeHashUInt32("he"u8.ToArray());
+ Assert.AreEqual(972873329u, hash);
+ }
+
+ [TestMethod]
+ public void TestAppend()
+ {
+ var murmur3 = new Murmur32(10u);
+ murmur3.Append("h"u8.ToArray());
+ murmur3.Append("e"u8.ToArray());
+ Assert.AreEqual(972873329u, murmur3.GetCurrentHashUInt32());
+
+ murmur3.Reset();
+ murmur3.Append("hello world"u8.ToArray());
+ murmur3.Append("hello world"u8.ToArray());
+ Assert.AreEqual(60539726u, murmur3.GetCurrentHashUInt32());
+
+ murmur3.Reset();
+ murmur3.Append("hello worldh"u8.ToArray());
+ murmur3.Append("ello world"u8.ToArray());
+ Assert.AreEqual(60539726u, murmur3.GetCurrentHashUInt32());
+
+ murmur3.Reset();
+ murmur3.Append("hello worldhello world"u8.ToArray());
+ murmur3.Append(""u8.ToArray());
+ Assert.AreEqual(60539726u, murmur3.GetCurrentHashUInt32());
+
+ murmur3.Reset();
+ murmur3.Append(""u8.ToArray());
+ murmur3.Append("hello worldhello world"u8.ToArray());
+ Assert.AreEqual(60539726u, murmur3.GetCurrentHashUInt32());
+
+ // random data, random split
+ var random = new Random();
+ var data = new byte[random.Next(1, 2048)];
+ random.NextBytes(data);
+ for (int i = 0; i < 32; i++)
+ {
+ var split = random.Next(1, data.Length - 1);
+ murmur3.Reset();
+ murmur3.Append(data.AsSpan(0, split));
+ murmur3.Append(data.AsSpan(split));
+ Assert.AreEqual(data.Murmur32(10u), murmur3.GetCurrentHashUInt32());
+ }
}
}
}