From e88a1ea3cdacbd4f9447f408c22c6337db17f45b Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 9 Jan 2025 22:57:51 +0800 Subject: [PATCH 1/6] optimize: Murmur3 should be no cryptographic hash algorithm --- src/Neo/Cryptography/Helper.cs | 9 +-- src/Neo/Cryptography/Murmur32.cs | 75 ++++++++++++------- src/Neo/SmartContract/Native/CryptoLib.cs | 2 +- .../Neo.UnitTests/Cryptography/UT_Murmur32.cs | 18 +++-- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/Neo/Cryptography/Helper.cs b/src/Neo/Cryptography/Helper.cs index c6a3a72ae7..d12ac763fa 100644 --- a/src/Neo/Cryptography/Helper.cs +++ b/src/Neo/Cryptography/Helper.cs @@ -17,7 +17,6 @@ using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; using System; -using System.Buffers.Binary; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -63,8 +62,7 @@ public static byte[] RIPEMD160(this ReadOnlySpan value) /// The computed hash code. public static uint Murmur32(this byte[] value, uint seed) { - using Murmur32 murmur = new(seed); - return BinaryPrimitives.ReadUInt32LittleEndian(murmur.ComputeHash(value)); + return Neo.Cryptography.Murmur32.HashToUInt32(value, seed); } /// @@ -75,10 +73,7 @@ public static uint Murmur32(this byte[] value, uint seed) /// The computed hash code. public static uint Murmur32(this ReadOnlySpan value, uint seed) { - Span buffer = stackalloc byte[sizeof(uint)]; - using Murmur32 murmur = new(seed); - murmur.TryComputeHash(value, buffer, out _); - return BinaryPrimitives.ReadUInt32LittleEndian(buffer); + return Neo.Cryptography.Murmur32.HashToUInt32(value, seed); } /// diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index e3c234f1ad..e92f673752 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -11,13 +11,15 @@ using System; using System.Buffers.Binary; +using System.Runtime.CompilerServices; namespace Neo.Cryptography { /// /// Computes the murmur hash for the input data. + /// Murmur32 is a non-cryptographic hash function. /// - public sealed class Murmur32 : System.Security.Cryptography.HashAlgorithm + public sealed class Murmur32 { private const uint c1 = 0xcc9e2d51; private const uint c2 = 0x1b873593; @@ -31,7 +33,7 @@ public sealed class Murmur32 : System.Security.Cryptography.HashAlgorithm private int length; public const int HashSizeInBits = 32; - public override int HashSize => HashSizeInBits; + public int HashSize => HashSizeInBits; /// /// Initializes a new instance of the class with the specified seed. @@ -40,16 +42,10 @@ public sealed class Murmur32 : System.Security.Cryptography.HashAlgorithm public Murmur32(uint seed) { this.seed = seed; - HashSizeValue = HashSizeInBits; Initialize(); } - protected override void HashCore(byte[] array, int ibStart, int cbSize) - { - HashCore(array.AsSpan(ibStart, cbSize)); - } - - protected override void HashCore(ReadOnlySpan source) + private void HashCore(ReadOnlySpan source) { length += source.Length; for (; source.Length >= 4; source = source[4..]) @@ -78,30 +74,59 @@ protected override void HashCore(ReadOnlySpan source) } } - protected override byte[] HashFinal() + private uint GetCurrentHashUInt32() { - byte[] buffer = new byte[sizeof(uint)]; - TryHashFinal(buffer, out _); - return buffer; + var state = hash ^ (uint)length; + state ^= state >> 16; + state *= 0x85ebca6b; + state ^= state >> 13; + state *= 0xc2b2ae35; + state ^= state >> 16; + return state; } - protected override bool TryHashFinal(Span destination, out int bytesWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Initialize() { - hash ^= (uint)length; - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; + hash = seed; + length = 0; + } - bytesWritten = Math.Min(destination.Length, sizeof(uint)); - return BinaryPrimitives.TryWriteUInt32LittleEndian(destination, hash); + /// + /// Computes the murmur hash for the input data and resets the state. + /// + /// The input to compute the hash code for. + /// The computed hash code in byte[4]. + public byte[] ComputeHash(ReadOnlySpan data) + { + var buffer = new byte[HashSizeInBits / 8]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, ComputeHashUInt32(data)); + return buffer; } - public override void Initialize() + /// + /// Computes the murmur hash for the input data and resets the state. + /// + /// The input to compute the hash code for. + /// The computed hash code in uint. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ComputeHashUInt32(ReadOnlySpan data) { - hash = seed; - length = 0; + HashCore(data); + + var state = GetCurrentHashUInt32(); + Initialize(); + return state; } + + /// + /// Computes the murmur hash for the input data. + /// + /// The input to compute the hash code for. + /// The seed used by the murmur algorithm. + /// The computed hash code in uint. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint HashToUInt32(ReadOnlySpan data, uint seed) + => new Murmur32(seed).ComputeHashUInt32(data); } } diff --git a/src/Neo/SmartContract/Native/CryptoLib.cs b/src/Neo/SmartContract/Native/CryptoLib.cs index 23c76aa158..cc60b1320f 100644 --- a/src/Neo/SmartContract/Native/CryptoLib.cs +++ b/src/Neo/SmartContract/Native/CryptoLib.cs @@ -62,7 +62,7 @@ public static byte[] Sha256(byte[] data) [ContractMethod(CpuFee = 1 << 13)] public static byte[] Murmur32(byte[] data, uint seed) { - using Murmur32 murmur = new(seed); + Murmur32 murmur = new(seed); return murmur.ComputeHash(data); } diff --git a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs index ff7ff295bf..8b61a94fed 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs @@ -27,24 +27,28 @@ public void TestGetHashSize() } [TestMethod] - public void TestHashCore() + public void TestHashToUInt32() { byte[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1 }; array.Murmur32(10u).Should().Be(378574820u); } [TestMethod] - public void TestTryComputeHash() + public void TestComputeHash() { var murmur3 = new Murmur32(10u); - var buffer = new byte[murmur3.HashSize / 8]; var data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1 }; - - var ok = murmur3.TryComputeHash(data, buffer, out _); - ok.Should().BeTrue(); - + var buffer = murmur3.ComputeHash(data); var hash = BinaryPrimitives.ReadUInt32LittleEndian(buffer); hash.Should().Be(378574820u); } + + [TestMethod] + public void TestComputeHashUInt32() + { + var murmur3 = new Murmur32(10u); + var hash = murmur3.ComputeHashUInt32("hello worldhello world"u8.ToArray()); + hash.Should().Be(60539726u); + } } } From 4868dc87e79435b723e8533839fe2868d393049d Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Mon, 13 Jan 2025 15:31:26 +0100 Subject: [PATCH 2/6] Avoid double initialize when only call once ComputeHash --- src/Neo/Cryptography/Murmur32.cs | 15 +++++++-------- tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs | 3 +-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index e92f673752..ce94203876 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -28,11 +28,13 @@ public sealed class Murmur32 private const uint m = 5; private const uint n = 0xe6546b64; - private readonly uint seed; + private readonly uint _seed; private uint hash; private int length; public const int HashSizeInBits = 32; + + [Obsolete("Use HashSizeInBits")] public int HashSize => HashSizeInBits; /// @@ -41,8 +43,7 @@ public sealed class Murmur32 /// The seed to be used. public Murmur32(uint seed) { - this.seed = seed; - Initialize(); + _seed = seed; } private void HashCore(ReadOnlySpan source) @@ -88,7 +89,7 @@ private uint GetCurrentHashUInt32() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Initialize() { - hash = seed; + hash = _seed; length = 0; } @@ -112,11 +113,9 @@ public byte[] ComputeHash(ReadOnlySpan data) [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ComputeHashUInt32(ReadOnlySpan data) { - HashCore(data); - - var state = GetCurrentHashUInt32(); Initialize(); - return state; + HashCore(data); + return GetCurrentHashUInt32(); } /// diff --git a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs index 8b61a94fed..ef260519df 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs @@ -22,8 +22,7 @@ public class UT_Murmur32 [TestMethod] public void TestGetHashSize() { - Murmur32 murmur3 = new Murmur32(1); - murmur3.HashSize.Should().Be(32); + Murmur32.HashSizeInBits.Should().Be(32); } [TestMethod] From c2fc2e52a981a99e0490e43c0ae0488f5d1f31a6 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Mon, 13 Jan 2025 15:32:57 +0100 Subject: [PATCH 3/6] Rename --- src/Neo/Cryptography/Murmur32.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index ce94203876..2e6517893e 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -29,8 +29,8 @@ public sealed class Murmur32 private const uint n = 0xe6546b64; private readonly uint _seed; - private uint hash; - private int length; + private uint _hash; + private int _length; public const int HashSizeInBits = 32; @@ -48,16 +48,16 @@ public Murmur32(uint seed) private void HashCore(ReadOnlySpan source) { - length += source.Length; + _length += source.Length; for (; source.Length >= 4; source = source[4..]) { - uint k = BinaryPrimitives.ReadUInt32LittleEndian(source); + 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; + _hash ^= k; + _hash = Helper.RotateLeft(_hash, r2); + _hash = _hash * m + n; } if (source.Length > 0) { @@ -71,13 +71,13 @@ private void HashCore(ReadOnlySpan source) remainingBytes *= c1; remainingBytes = Helper.RotateLeft(remainingBytes, r1); remainingBytes *= c2; - hash ^= remainingBytes; + _hash ^= remainingBytes; } } private uint GetCurrentHashUInt32() { - var state = hash ^ (uint)length; + var state = _hash ^ (uint)_length; state ^= state >> 16; state *= 0x85ebca6b; state ^= state >> 13; @@ -89,8 +89,8 @@ private uint GetCurrentHashUInt32() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Initialize() { - hash = _seed; - length = 0; + _hash = _seed; + _length = 0; } /// From a8855d2b918709b6d1a5ea93f3cb88f024b1791e Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Mon, 13 Jan 2025 15:35:42 +0100 Subject: [PATCH 4/6] Allow to append --- src/Neo/Cryptography/Murmur32.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index 2e6517893e..b4c61664ad 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -46,7 +46,11 @@ public Murmur32(uint seed) _seed = seed; } - private void HashCore(ReadOnlySpan source) + /// + /// Append data to murmur computation + /// + /// Source + public void Append(ReadOnlySpan source) { _length += source.Length; for (; source.Length >= 4; source = source[4..]) @@ -114,7 +118,7 @@ public byte[] ComputeHash(ReadOnlySpan data) public uint ComputeHashUInt32(ReadOnlySpan data) { Initialize(); - HashCore(data); + Append(data); return GetCurrentHashUInt32(); } From dfbfb956f4a18f883e4a314ef004102ee9e7615a Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 14 Jan 2025 13:51:33 +0800 Subject: [PATCH 5/6] Update src/Neo/Cryptography/Murmur32.cs --- src/Neo/Cryptography/Murmur32.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index b4c61664ad..1d784d8863 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -50,7 +50,7 @@ public Murmur32(uint seed) /// Append data to murmur computation /// /// Source - public void Append(ReadOnlySpan source) + private void Append(ReadOnlySpan source) { _length += source.Length; for (; source.Length >= 4; source = source[4..]) From 4894dfc3cc3917dc827e6f0430be7682fb042fa1 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 14 Jan 2025 15:42:51 +0800 Subject: [PATCH 6/6] Update src/Neo/Cryptography/Murmur32.cs Co-authored-by: Shargon --- src/Neo/Cryptography/Murmur32.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index 1d784d8863..a845ea3fee 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -91,7 +91,7 @@ private uint GetCurrentHashUInt32() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Initialize() + private void Initialize() { _hash = _seed; _length = 0;