From 04ab81f386efff3a44bd458574297279c2004c68 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 18:06:55 +0100 Subject: [PATCH 01/13] Optimize ModExp --- .../Extensions/ByteArrayExtensions.cs | 15 +- .../Nethermind.Core/Extensions/Bytes.cs | 2 +- .../ModExpPrecompile.cs | 213 ++++++++++++++---- .../ModExpPrecompilePreEip2565.cs | 7 +- .../Nethermind.Evm.Test/Eip2565Tests.cs | 7 +- .../Nethermind.Evm.Test/Eip7823Tests.cs | 2 +- .../EvmInstructions.Math2Param.cs | 2 +- 7 files changed, 192 insertions(+), 56 deletions(-) diff --git a/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs index 92bda49291a..94c1c12f86a 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs @@ -56,18 +56,23 @@ public static byte[] SliceWithZeroPaddingEmptyOnError(this byte[] bytes, int sta return slice; } - public static byte[] SliceWithZeroPaddingEmptyOnError(this ReadOnlySpan bytes, int startIndex, int length) + public static ReadOnlySpan SliceWithZeroPaddingEmptyOnError(this ReadOnlySpan bytes, int startIndex, int length) { int copiedFragmentLength = Math.Min(bytes.Length - startIndex, length); if (copiedFragmentLength <= 0) { - return []; + return default; } - byte[] slice = new byte[length]; + ReadOnlySpan sliced = bytes.Slice(startIndex, copiedFragmentLength); + if (copiedFragmentLength < length) + { + byte[] extended = new byte[length]; + sliced.CopyTo(extended); + sliced = extended; + } - bytes.Slice(startIndex, copiedFragmentLength).CopyTo(slice.AsSpan(0, copiedFragmentLength)); - return slice; + return sliced; } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 0f78e509169..39158a6c10b 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -182,7 +182,7 @@ public static bool IsZero(this ReadOnlySpan bytes) return bytes.IndexOfAnyExcept((byte)0) < 0; } - public static int LeadingZerosCount(this Span bytes, int startIndex = 0) + public static int LeadingZerosCount(this ReadOnlySpan bytes, int startIndex = 0) { int nonZeroIndex = bytes[startIndex..].IndexOfAnyExcept((byte)0); return nonZeroIndex < 0 ? bytes.Length - startIndex : nonZeroIndex; diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 803b6a791ef..888c5722433 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -2,19 +2,22 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers.Binary; +using System.Diagnostics; using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.Evm.Precompiles; using Nethermind.GmpBindings; using Nethermind.Int256; namespace Nethermind.Evm.Precompiles; /// -/// https://github.com/ethereum/EIPs/blob/vbuterin-patch-2/EIPS/bigint_modexp.md +/// https://github.com/ethereum/EIPs/blob/vbuterin-patch-2/EIPS/bigint_modexp.md /// public class ModExpPrecompile : IPrecompile { @@ -26,6 +29,11 @@ public class ModExpPrecompile : IPrecompile /// public const int ModExpMaxInputSizeEip7823 = 1024; + private const int LengthSize = 32; + private const int StartBaseLength = 0; + private const int StartExpLength = 32; + private const int StartModLength = 64; + private ModExpPrecompile() { } @@ -57,9 +65,10 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec try { - return inputData.Length >= 96 - ? DataGasCostInternal(inputData.Span.Slice(0, 96), inputData, releaseSpec) - : DataGasCostInternal(inputData, releaseSpec); + ReadOnlySpan span = inputData.Span; + return span.Length >= 96 + ? DataGasCostInternal(span, releaseSpec) + : DataGasCostShortInternal(span, releaseSpec); } catch (OverflowException) { @@ -67,32 +76,32 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec } } - private static long DataGasCostInternal(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + [MethodImpl(MethodImplOptions.NoInlining)] + private static long DataGasCostShortInternal(ReadOnlySpan inputData, IReleaseSpec releaseSpec) { + Debug.Assert(inputData.Length < 96); + Span extendedInput = stackalloc byte[96]; - inputData[..Math.Min(96, inputData.Length)].Span - .CopyTo(extendedInput[..Math.Min(96, inputData.Length)]); + inputData.CopyTo(extendedInput); - return DataGasCostInternal(extendedInput, inputData, releaseSpec); + return DataGasCostInternal(extendedInput, releaseSpec); } - private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSpec releaseSpec) { - UInt256 baseLength = new(extendedInput[..32], true); - UInt256 expLength = new(extendedInput.Slice(32, 32), true); - UInt256 modulusLength = new(extendedInput.Slice(64, 32), true); - + (uint baseLength, uint expLength, uint modulusLength) = GetInputLengths(inputData); if (ExceedsMaxInputSize(releaseSpec, baseLength, expLength, modulusLength)) { return long.MaxValue; } - UInt256 complexity = MultComplexity(baseLength, modulusLength, releaseSpec.IsEip7883Enabled); + ulong complexity = MultComplexity(baseLength, modulusLength, releaseSpec.IsEip7883Enabled); - UInt256 expLengthUpTo32 = UInt256.Min(32, expLength); - UInt256 startIndex = 96 + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? - UInt256 exp = new(inputData.Span.SliceWithZeroPaddingEmptyOnError((int)startIndex, (int)expLengthUpTo32), true); + uint expLengthUpTo32 = Math.Min(32, expLength); + uint startIndex = 96 + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? + UInt256 exp = new(inputData.SliceWithZeroPaddingEmptyOnError((int)startIndex, (int)expLengthUpTo32), true); UInt256 iterationCount = CalculateIterationCount(expLength, exp, releaseSpec.IsEip7883Enabled); + bool overflow = UInt256.MultiplyOverflow(complexity, iterationCount, out UInt256 result); result /= 3; return result > long.MaxValue || overflow @@ -100,31 +109,129 @@ private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOn : Math.Max(releaseSpec.IsEip7883Enabled ? GasCostOf.MinModExpEip7883 : GasCostOf.MinModExpEip2565, (long)result); } - private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, UInt256 baseLength, UInt256 expLength, UInt256 modulusLength) - => releaseSpec.IsEip7823Enabled && - (baseLength > ModExpMaxInputSizeEip7823 || expLength > ModExpMaxInputSizeEip7823 || modulusLength > ModExpMaxInputSizeEip7823); + private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLength, uint expLength, uint modulusLength) + { + return releaseSpec.IsEip7823Enabled + ? baseLength > ModExpMaxInputSizeEip7823 || expLength > ModExpMaxInputSizeEip7823 || modulusLength > ModExpMaxInputSizeEip7823 + : baseLength == int.MaxValue || modulusLength == int.MaxValue; + } - private static (int, int, int) GetInputLengths(ReadOnlyMemory inputData) + [MethodImpl(MethodImplOptions.NoInlining)] + private (uint baseLength, uint expLength, uint modulusLength) GetInputLengthsShort(ReadOnlySpan inputData) { + Debug.Assert(inputData.Length < 96); + Span extendedInput = stackalloc byte[96]; - inputData[..Math.Min(96, inputData.Length)].Span - .CopyTo(extendedInput[..Math.Min(96, inputData.Length)]); + inputData.CopyTo(extendedInput); - int baseLength = (int)new UInt256(extendedInput[..32], true); - UInt256 expLengthUint256 = new(extendedInput.Slice(32, 32), true); - int expLength = expLengthUint256 > Array.MaxLength ? Array.MaxLength : (int)expLengthUint256; - int modulusLength = (int)new UInt256(extendedInput.Slice(64, 32), true); + return GetInputLengths(extendedInput); + } + private static (uint baseLength, uint expLength, uint modulusLength) GetInputLengths(ReadOnlySpan inputData) + { + // Test if too high + if (Vector256.IsSupported) + { + ref var firstByte = ref MemoryMarshal.GetReference(inputData); + Vector256 mask = ~Vector256.Create(0, 0, 0, 0, 0, 0, 0, uint.MaxValue).AsByte(); + if (Vector256.BitwiseAnd( + Vector256.BitwiseOr( + Vector256.BitwiseOr( + Unsafe.ReadUnaligned>(ref firstByte), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartExpLength))), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, 64))), + mask) != Vector256.Zero) + { + return GetInputLengthsMayOverflow(inputData); + } + } + else if (Vector128.IsSupported) + { + ref var firstByte = ref MemoryMarshal.GetReference(inputData); + Vector128 mask = ~Vector128.Create(0, 0, 0, uint.MaxValue).AsByte(); + if (Vector128.BitwiseOr( + Vector128.BitwiseOr( + Vector128.BitwiseOr( + Unsafe.ReadUnaligned>(ref firstByte), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartExpLength))), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, 64))), + Vector128.BitwiseAnd( + Vector128.BitwiseOr( + Vector128.BitwiseOr( + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, 16)), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartExpLength + 16))), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartModLength + 16))), + mask) + ) != Vector128.Zero) + { + return GetInputLengthsMayOverflow(inputData); + } + } + else if (inputData.Slice(StartBaseLength, LengthSize - sizeof(uint)).IndexOfAnyExcept((byte)0) >= 0 || + inputData.Slice(StartExpLength, LengthSize - sizeof(uint)).IndexOfAnyExcept((byte)0) >= 0 || + inputData.Slice(StartModLength, LengthSize - sizeof(uint)).IndexOfAnyExcept((byte)0) >= 0) + { + return GetInputLengthsMayOverflow(inputData); + } + + uint baseLength = BinaryPrimitives.ReadUInt32BigEndian(inputData.Slice(32 - sizeof(uint), sizeof(uint))); + uint expLength = BinaryPrimitives.ReadUInt32BigEndian(inputData.Slice(64 - sizeof(uint), sizeof(uint))); + uint modulusLength = BinaryPrimitives.ReadUInt32BigEndian(inputData.Slice(96 - sizeof(uint), sizeof(uint))); return (baseLength, expLength, modulusLength); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static (uint baseLength, uint expLength, uint modulusLength) GetInputLengthsMayOverflow(ReadOnlySpan inputData) + { + // Only valid if baseLength and modulusLength are zero; when expLength doesn't matter + if (Vector256.IsSupported) + { + ref var firstByte = ref MemoryMarshal.GetReference(inputData); + if (Vector256.BitwiseOr( + Unsafe.ReadUnaligned>(ref firstByte), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartModLength))) + != Vector256.Zero) + { + // Overflow + return (uint.MaxValue, uint.MaxValue, uint.MaxValue); + } + } + else if (Vector128.IsSupported) + { + ref var firstByte = ref MemoryMarshal.GetReference(inputData); + if (Vector128.BitwiseOr( + Vector128.BitwiseOr( + Unsafe.ReadUnaligned>(ref firstByte), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, 16))), + Vector128.BitwiseOr( + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartModLength)), + Unsafe.ReadUnaligned>(ref Unsafe.Add(ref firstByte, StartModLength + 16))) + ) != Vector128.Zero) + { + // Overflow + return (uint.MaxValue, uint.MaxValue, uint.MaxValue); + } + } + else if (inputData.Slice(StartBaseLength, LengthSize).IndexOfAnyExcept((byte)0) >= 0 || + inputData.Slice(StartModLength, LengthSize).IndexOfAnyExcept((byte)0) >= 0) + { + // Overflow + return (uint.MaxValue, uint.MaxValue, uint.MaxValue); + } + + return (0, uint.MaxValue, 0); + } + public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.ModExpPrecompile++; - (int baseLength, int expLength, int modulusLength) = GetInputLengths(inputData); + ReadOnlySpan inputSpan = inputData.Span; + (uint baseLength, uint expLength, uint modulusLength) = inputSpan.Length >= 96 + ? GetInputLengths(inputSpan) + : GetInputLengthsShort(inputSpan); - if (ExceedsMaxInputSize(releaseSpec, (uint)baseLength, (uint)expLength, (uint)modulusLength)) + if (ExceedsMaxInputSize(releaseSpec, baseLength, expLength, modulusLength)) return IPrecompile.Failure; // if both are 0, then expLength can be huge, which leads to a potential buffer too big exception @@ -133,7 +240,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re using var modulusInt = mpz_t.Create(); - fixed (byte* modulusData = inputData.Span.SliceWithZeroPaddingEmptyOnError(96 + baseLength + expLength, modulusLength)) + fixed (byte* modulusData = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength + (int)expLength, (int)modulusLength)) { if (modulusData is not null) Gmp.mpz_import(modulusInt, (nuint)modulusLength, 1, 1, 1, nuint.Zero, (nint)modulusData); @@ -146,8 +253,8 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re using var expInt = mpz_t.Create(); using var powmResult = mpz_t.Create(); - fixed (byte* baseData = inputData.Span.SliceWithZeroPaddingEmptyOnError(96, baseLength)) - fixed (byte* expData = inputData.Span.SliceWithZeroPaddingEmptyOnError(96 + baseLength, expLength)) + fixed (byte* baseData = inputSpan.SliceWithZeroPaddingEmptyOnError(96, (int)baseLength)) + fixed (byte* expData = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength, (int)expLength)) { if (baseData is not null) Gmp.mpz_import(baseInt, (nuint)baseLength, 1, 1, 1, nuint.Zero, (nint)baseData); @@ -173,7 +280,7 @@ public static (byte[], bool) OldRun(byte[] inputData) { Metrics.ModExpPrecompile++; - (int baseLength, int expLength, int modulusLength) = GetInputLengths(inputData); + (int baseLength, int expLength, int modulusLength) = OldGetInputLengths(inputData); BigInteger modulusInt = inputData .SliceWithZeroPaddingEmptyOnError(96 + baseLength + expLength, modulusLength).ToUnsignedBigInteger(); @@ -189,6 +296,20 @@ public static (byte[], bool) OldRun(byte[] inputData) return (BigInteger.ModPow(baseInt, expInt, modulusInt).ToBigEndianByteArray(modulusLength), true); } + private static (int baseLength, int expLength, int modulusLength) OldGetInputLengths(ReadOnlySpan inputData) + { + Span extendedInput = stackalloc byte[96]; + inputData[..Math.Min(96, inputData.Length)] + .CopyTo(extendedInput[..Math.Min(96, inputData.Length)]); + + int baseLength = (int)new UInt256(extendedInput[..32], true); + UInt256 expLengthUint256 = new(extendedInput.Slice(32, 32), true); + int expLength = expLengthUint256 > Array.MaxLength ? Array.MaxLength : (int)expLengthUint256; + int modulusLength = (int)new UInt256(extendedInput.Slice(64, 32), true); + + return (baseLength, expLength, modulusLength); + } + /// /// def calculate_multiplication_complexity(base_length, modulus_length): /// max_length = max(base_length, modulus_length) @@ -196,18 +317,28 @@ public static (byte[], bool) OldRun(byte[] inputData) /// return words**2 /// /// - private static UInt256 MultComplexity(in UInt256 baseLength, in UInt256 modulusLength, bool isEip7883Enabled) + private static ulong MultComplexity(uint baseLength, uint modulusLength, bool isEip7883Enabled) { - UInt256 maxLength = UInt256.Max(baseLength, modulusLength); - UInt256.Mod(maxLength, 8, out UInt256 mod8); - UInt256 words = (maxLength / 8) + (mod8.IsZero ? UInt256.Zero : UInt256.One); + // Pick the larger of the two + uint max = baseLength > modulusLength ? baseLength : modulusLength; + + // Compute ceil(max/8) via a single add + shift + // (max + 7) >> 3 == (max + 7) / 8, rounding up + ulong words = ((ulong)max + 7u) >> 3; + + // Square it once + ulong sq = words * words; + // If EIP-7883 => small-case = 16, else 2*sq when max>32 if (isEip7883Enabled) { - return maxLength > 32 ? 2 * words * words : 16; + return max > 32 + ? (sq << 1) // 2 * words * words + : 16UL; // constant floor } - return words * words; + // Otherwise plain square + return sq; } static readonly UInt256 IterationCountMultiplierEip2565 = 8; diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs index c1186c809e8..c646b8b40a6 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using Nethermind.Int256; namespace Nethermind.Evm.Precompiles; @@ -62,7 +61,7 @@ private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOn UInt256 complexity = MultComplexity(UInt256.Max(baseLength, modulusLength)); - byte[] expSignificantBytes = + ReadOnlySpan expSignificantBytes = inputData.Span.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength, (int)UInt256.Min(expLength, 32)); UInt256 lengthOver32 = expLength <= 32 ? 0 : expLength - 32; @@ -109,13 +108,13 @@ private static UInt256 MultComplexity(in UInt256 adjustedExponentLength) return adjustedExponentLength * adjustedExponentLength / 16 + 480 * adjustedExponentLength - 199680; } - private static UInt256 AdjustedExponentLength(in UInt256 lengthOver32, byte[] exponent) + private static UInt256 AdjustedExponentLength(in UInt256 lengthOver32, ReadOnlySpan exponent) { bool overflow = false; bool underflow = false; UInt256 result; - int leadingZeros = exponent.AsSpan().LeadingZerosCount(); + int leadingZeros = exponent.LeadingZerosCount(); if (leadingZeros == exponent.Length) { overflow |= UInt256.MultiplyOverflow(lengthOver32, Eight, out result); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs index afc87008b9a..9d9fdad93af 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs @@ -25,13 +25,14 @@ public void Simple_routine([Random(int.MinValue, int.MaxValue, 100)] int seed) string randomInput = string.Format("{0}{0}{0}{1}", Length64, data.ToHexString()); Prepare input = Prepare.EvmCode.FromCode(randomInput); + byte[] inputData = input.Done.ToArray(); - (ReadOnlyMemory, bool) gmpPair = ModExpPrecompile.Instance.Run(input.Done.ToArray(), Berlin.Instance); + (ReadOnlyMemory, bool) gmpPair = ModExpPrecompile.Instance.Run(inputData, Berlin.Instance); #pragma warning disable 618 - (ReadOnlyMemory, bool) bigIntPair = ModExpPrecompile.OldRun(input.Done.ToArray()); + (ReadOnlyMemory, bool) bigIntPair = ModExpPrecompile.OldRun(inputData); #pragma warning restore 618 - Assert.That(bigIntPair.Item1.ToArray(), Is.EqualTo(gmpPair.Item1.ToArray())); + Assert.That(gmpPair.Item1.ToArray(), Is.EqualTo(bigIntPair.Item1.ToArray())); } [Test] diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs index a7331ba8dd8..98ca3d46b46 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs @@ -61,7 +61,7 @@ public void TestInvalid(string inputHex) byte[] input = Bytes.FromHexString(inputHex); Assert.Throws(() => TestSuccess(input, specDisabled)); - Assert.Throws(() => TestSuccess(input, specEnabled)); + Assert.That(TestSuccess(input, specEnabled), Is.EqualTo(false)); Assert.That(TestGas(input, specDisabled), Is.EqualTo(long.MaxValue)); Assert.That(TestGas(input, specEnabled), Is.EqualTo(long.MaxValue)); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs index 20dfa34d688..3cf253b5243 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs @@ -269,7 +269,7 @@ public static EvmExceptionType InstructionExp(VirtualMachine vm, r goto StackUnderflow; // Pop the exponent as a 256-bit word. - Span bytes = stack.PopWord256(); + ReadOnlySpan bytes = stack.PopWord256(); // Determine the effective byte-length of the exponent. int leadingZeros = bytes.LeadingZerosCount(); From fead7aefda32d22421031d7a2f8a17374e2bf659 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 19:18:57 +0100 Subject: [PATCH 02/13] Faaster --- .../ModExpPrecompile.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 888c5722433..6b290f78476 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -240,9 +240,10 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re using var modulusInt = mpz_t.Create(); - fixed (byte* modulusData = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength + (int)expLength, (int)modulusLength)) + ReadOnlySpan modulusDataSpan = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength + (int)expLength, (int)modulusLength); + if (modulusDataSpan.Length > 0) { - if (modulusData is not null) + fixed (byte* modulusData = &MemoryMarshal.GetReference(modulusDataSpan)) Gmp.mpz_import(modulusInt, (nuint)modulusLength, 1, 1, 1, nuint.Zero, (nint)modulusData); } @@ -253,23 +254,27 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re using var expInt = mpz_t.Create(); using var powmResult = mpz_t.Create(); - fixed (byte* baseData = inputSpan.SliceWithZeroPaddingEmptyOnError(96, (int)baseLength)) - fixed (byte* expData = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength, (int)expLength)) + ReadOnlySpan baseDataSpan = inputSpan.SliceWithZeroPaddingEmptyOnError(96, (int)baseLength); + if (baseDataSpan.Length > 0) { - if (baseData is not null) + fixed (byte* baseData = &MemoryMarshal.GetReference(baseDataSpan)) Gmp.mpz_import(baseInt, (nuint)baseLength, 1, 1, 1, nuint.Zero, (nint)baseData); + } - if (expData is not null) + ReadOnlySpan expDataSpan = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength, (int)expLength); + if (expDataSpan.Length > 0) + { + fixed (byte* expData = &MemoryMarshal.GetReference(expDataSpan)) Gmp.mpz_import(expInt, (nuint)expLength, 1, 1, 1, nuint.Zero, (nint)expData); } Gmp.mpz_powm(powmResult, baseInt, expInt, modulusInt); - var powmResultLen = (int)(Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; - var offset = modulusLength - powmResultLen; - byte[] result = new byte[modulusLength]; + uint powmResultLen = (uint)(Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; + uint offset = modulusLength - powmResultLen; - fixed (byte* ptr = result) + byte[] result = new byte[modulusLength]; + fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(result)) Gmp.mpz_export((nint)(ptr + offset), out _, 1, 1, 1, nuint.Zero, powmResult); return (result, true); From 53f9244616675b7dcc100f6936d419d471cb2117 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 19:58:33 +0100 Subject: [PATCH 03/13] Moar --- .../ModExpPrecompile.cs | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 6b290f78476..4092712560d 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -99,7 +99,7 @@ private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSp uint expLengthUpTo32 = Math.Min(32, expLength); uint startIndex = 96 + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? - UInt256 exp = new(inputData.SliceWithZeroPaddingEmptyOnError((int)startIndex, (int)expLengthUpTo32), true); + UInt256 exp = new(inputData.SliceWithZeroPaddingEmptyOnError((int)startIndex, (int)expLengthUpTo32), isBigEndian: true); UInt256 iterationCount = CalculateIterationCount(expLength, exp, releaseSpec.IsEip7883Enabled); bool overflow = UInt256.MultiplyOverflow(complexity, iterationCount, out UInt256 result); @@ -346,9 +346,9 @@ private static ulong MultComplexity(uint baseLength, uint modulusLength, bool is return sq; } - static readonly UInt256 IterationCountMultiplierEip2565 = 8; + static readonly ulong IterationCountMultiplierEip2565 = 8; - static readonly UInt256 IterationCountMultiplierEip7883 = 16; + static readonly ulong IterationCountMultiplierEip7883 = 16; /// /// def calculate_iteration_count(exponent_length, exponent): @@ -362,38 +362,30 @@ private static ulong MultComplexity(uint baseLength, uint modulusLength, bool is /// /// /// - private static UInt256 CalculateIterationCount(UInt256 exponentLength, UInt256 exponent, bool isEip7883Enabled) + private static UInt256 CalculateIterationCount(uint exponentLength, UInt256 exponent, bool isEip7883Enabled) { - try + ulong iterationCount; + if (exponentLength <= 32) + { + iterationCount = exponent.IsZero ? 0 : (uint)(exponent.BitLen - 1); + } + else { - UInt256 iterationCount; - if (exponentLength <= 32) + uint bitLength = (uint)exponent.BitLen; + if (bitLength > 0) { - iterationCount = exponent.IsZero ? UInt256.Zero : (UInt256)(exponent.BitLen - 1); + bitLength--; } - else + + ulong multiplicationResult = (exponentLength - 32) * (isEip7883Enabled ? IterationCountMultiplierEip7883 : IterationCountMultiplierEip2565); + iterationCount = multiplicationResult + bitLength; + if (iterationCount < multiplicationResult) { - int bitLength = (exponent & UInt256.MaxValue).BitLen; - if (bitLength > 0) - { - bitLength--; - } - - bool overflow = UInt256.MultiplyOverflow(exponentLength - 32, - isEip7883Enabled ? IterationCountMultiplierEip7883 : IterationCountMultiplierEip2565, - out UInt256 multiplicationResult); - overflow |= UInt256.AddOverflow(multiplicationResult, (UInt256)bitLength, out iterationCount); - if (overflow) - { - return UInt256.MaxValue; - } + // Overflowed + return new UInt256(iterationCount, 1, 0, 0); } - - return UInt256.Max(iterationCount, UInt256.One); - } - catch (OverflowException) - { - return UInt256.MaxValue; } + + return iterationCount > 1 ? new UInt256(iterationCount) : UInt256.One; } } From 29a5738d23460e7b9e4e99c6500e99d5ab756f31 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 20:22:37 +0100 Subject: [PATCH 04/13] work as nuint --- src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 4092712560d..22344b72fdd 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -270,8 +270,8 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re Gmp.mpz_powm(powmResult, baseInt, expInt, modulusInt); - uint powmResultLen = (uint)(Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; - uint offset = modulusLength - powmResultLen; + nuint powmResultLen = (Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; + nuint offset = modulusLength - powmResultLen; byte[] result = new byte[modulusLength]; fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(result)) From 6fba82b0066c1f1033270c5e96d44e485007301d Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 20:46:56 +0100 Subject: [PATCH 05/13] use nint --- src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 22344b72fdd..edc19454f77 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -270,8 +270,8 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re Gmp.mpz_powm(powmResult, baseInt, expInt, modulusInt); - nuint powmResultLen = (Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; - nuint offset = modulusLength - powmResultLen; + nint powmResultLen = (nint)(Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; + nint offset = (nint)modulusLength - powmResultLen; byte[] result = new byte[modulusLength]; fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(result)) From df048be80a020998311696cf35365cee95acc067 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 20:49:51 +0100 Subject: [PATCH 06/13] cast via int --- src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index edc19454f77..7384ac57d2c 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -271,7 +271,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re Gmp.mpz_powm(powmResult, baseInt, expInt, modulusInt); nint powmResultLen = (nint)(Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; - nint offset = (nint)modulusLength - powmResultLen; + nint offset = (nint)(int)modulusLength - powmResultLen; byte[] result = new byte[modulusLength]; fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(result)) From 28e8cdb3a320a37c297b218b0a2602cfc325b088 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 20:53:09 +0100 Subject: [PATCH 07/13] Remove unneeded casts --- .../Nethermind.Evm.Precompiles/ModExpPrecompile.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 7384ac57d2c..59e575a7db0 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -244,7 +244,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re if (modulusDataSpan.Length > 0) { fixed (byte* modulusData = &MemoryMarshal.GetReference(modulusDataSpan)) - Gmp.mpz_import(modulusInt, (nuint)modulusLength, 1, 1, 1, nuint.Zero, (nint)modulusData); + Gmp.mpz_import(modulusInt, modulusLength, 1, 1, 1, nuint.Zero, (nint)modulusData); } if (Gmp.mpz_sgn(modulusInt) == 0) @@ -258,20 +258,20 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re if (baseDataSpan.Length > 0) { fixed (byte* baseData = &MemoryMarshal.GetReference(baseDataSpan)) - Gmp.mpz_import(baseInt, (nuint)baseLength, 1, 1, 1, nuint.Zero, (nint)baseData); + Gmp.mpz_import(baseInt, baseLength, 1, 1, 1, nuint.Zero, (nint)baseData); } ReadOnlySpan expDataSpan = inputSpan.SliceWithZeroPaddingEmptyOnError(96 + (int)baseLength, (int)expLength); if (expDataSpan.Length > 0) { fixed (byte* expData = &MemoryMarshal.GetReference(expDataSpan)) - Gmp.mpz_import(expInt, (nuint)expLength, 1, 1, 1, nuint.Zero, (nint)expData); + Gmp.mpz_import(expInt, expLength, 1, 1, 1, nuint.Zero, (nint)expData); } Gmp.mpz_powm(powmResult, baseInt, expInt, modulusInt); nint powmResultLen = (nint)(Gmp.mpz_sizeinbase(powmResult, 2) + 7) / 8; - nint offset = (nint)(int)modulusLength - powmResultLen; + nint offset = (int)modulusLength - powmResultLen; byte[] result = new byte[modulusLength]; fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(result)) From edbd0c113f3c906cbf7cdb000f19d2a1583dab6c Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 20:59:56 +0100 Subject: [PATCH 08/13] Move OldRuns to tests --- .../ModExpPrecompile.cs | 36 ------------------ .../Nethermind.Evm.Test/Eip2565Tests.cs | 38 +++++++++++++++++-- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 59e575a7db0..c2ba884ecd0 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -4,7 +4,6 @@ using System; using System.Buffers.Binary; using System.Diagnostics; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -280,41 +279,6 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re return (result, true); } - [Obsolete("This is a previous implementation using BigInteger instead of GMP")] - public static (byte[], bool) OldRun(byte[] inputData) - { - Metrics.ModExpPrecompile++; - - (int baseLength, int expLength, int modulusLength) = OldGetInputLengths(inputData); - - BigInteger modulusInt = inputData - .SliceWithZeroPaddingEmptyOnError(96 + baseLength + expLength, modulusLength).ToUnsignedBigInteger(); - - if (modulusInt.IsZero) - { - return (new byte[modulusLength], true); - } - - BigInteger baseInt = inputData.SliceWithZeroPaddingEmptyOnError(96, baseLength).ToUnsignedBigInteger(); - BigInteger expInt = inputData.SliceWithZeroPaddingEmptyOnError(96 + baseLength, expLength) - .ToUnsignedBigInteger(); - return (BigInteger.ModPow(baseInt, expInt, modulusInt).ToBigEndianByteArray(modulusLength), true); - } - - private static (int baseLength, int expLength, int modulusLength) OldGetInputLengths(ReadOnlySpan inputData) - { - Span extendedInput = stackalloc byte[96]; - inputData[..Math.Min(96, inputData.Length)] - .CopyTo(extendedInput[..Math.Min(96, inputData.Length)]); - - int baseLength = (int)new UInt256(extendedInput[..32], true); - UInt256 expLengthUint256 = new(extendedInput.Slice(32, 32), true); - int expLength = expLengthUint256 > Array.MaxLength ? Array.MaxLength : (int)expLengthUint256; - int modulusLength = (int)new UInt256(extendedInput.Slice(64, 32), true); - - return (baseLength, expLength, modulusLength); - } - /// /// def calculate_multiplication_complexity(base_length, modulus_length): /// max_length = max(base_length, modulus_length) diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs index 9d9fdad93af..14e715eb990 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs @@ -3,10 +3,12 @@ using System; using System.Linq; +using System.Numerics; using FluentAssertions; using MathNet.Numerics.Random; using Nethermind.Core.Extensions; using Nethermind.Evm.Precompiles; +using Nethermind.Int256; using Nethermind.Specs.Forks; using NUnit.Framework; @@ -28,9 +30,7 @@ public void Simple_routine([Random(int.MinValue, int.MaxValue, 100)] int seed) byte[] inputData = input.Done.ToArray(); (ReadOnlyMemory, bool) gmpPair = ModExpPrecompile.Instance.Run(inputData, Berlin.Instance); -#pragma warning disable 618 - (ReadOnlyMemory, bool) bigIntPair = ModExpPrecompile.OldRun(inputData); -#pragma warning restore 618 + (ReadOnlyMemory, bool) bigIntPair = BigIntegerModExp(inputData); Assert.That(gmpPair.Item1.ToArray(), Is.EqualTo(bigIntPair.Item1.ToArray())); } @@ -52,5 +52,37 @@ public void ModExp_run_should_not_throw_exception(string inputStr) long gas = ModExpPrecompile.Instance.DataGasCost(input.Done, London.Instance); gas.Should().Be(200); } + + private static (byte[], bool) BigIntegerModExp(byte[] inputData) + { + (int baseLength, int expLength, int modulusLength) = GetInputLengths(inputData); + + BigInteger modulusInt = inputData + .SliceWithZeroPaddingEmptyOnError(96 + baseLength + expLength, modulusLength).ToUnsignedBigInteger(); + + if (modulusInt.IsZero) + { + return (new byte[modulusLength], true); + } + + BigInteger baseInt = inputData.SliceWithZeroPaddingEmptyOnError(96, baseLength).ToUnsignedBigInteger(); + BigInteger expInt = inputData.SliceWithZeroPaddingEmptyOnError(96 + baseLength, expLength) + .ToUnsignedBigInteger(); + return (BigInteger.ModPow(baseInt, expInt, modulusInt).ToBigEndianByteArray(modulusLength), true); + } + + private static (int baseLength, int expLength, int modulusLength) GetInputLengths(ReadOnlySpan inputData) + { + Span extendedInput = stackalloc byte[96]; + inputData[..Math.Min(96, inputData.Length)] + .CopyTo(extendedInput[..Math.Min(96, inputData.Length)]); + + int baseLength = (int)new UInt256(extendedInput[..32], true); + UInt256 expLengthUint256 = new(extendedInput.Slice(32, 32), true); + int expLength = expLengthUint256 > Array.MaxLength ? Array.MaxLength : (int)expLengthUint256; + int modulusLength = (int)new UInt256(extendedInput.Slice(64, 32), true); + + return (baseLength, expLength, modulusLength); + } } } From 3a68660f3220f0c0b511ffbc90e6112821b21beb Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 21:10:55 +0100 Subject: [PATCH 09/13] fix benchmarks --- src/Nethermind/Benchmarks.slnx | 1 + .../ModExpBenchmark.cs | 20 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Nethermind/Benchmarks.slnx b/src/Nethermind/Benchmarks.slnx index 7ba0c11e37f..1466a214d66 100644 --- a/src/Nethermind/Benchmarks.slnx +++ b/src/Nethermind/Benchmarks.slnx @@ -14,6 +14,7 @@ + diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs index e8d5934b828..1a7f8a3ad00 100644 --- a/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs @@ -3,22 +3,12 @@ using System; using System.Collections.Generic; -using BenchmarkDotNet.Attributes; using Nethermind.Evm.Precompiles; -namespace Nethermind.Precompiles.Benchmark -{ - public class ModExpBenchmark : PrecompileBenchmarkBase - { - protected override IEnumerable Precompiles => new[] { ModExpPrecompile.Instance }; - protected override string InputsDirectory => "modexp"; +namespace Nethermind.Precompiles.Benchmark; - [Benchmark] - public (ReadOnlyMemory, bool) BigInt() - { -#pragma warning disable CS0618 // Type or member is obsolete - return ModExpPrecompile.OldRun(Input.Bytes); -#pragma warning restore CS0618 // Type or member is obsolete - } - } +public class ModExpBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] { ModExpPrecompile.Instance }; + protected override string InputsDirectory => "modexp"; } From 61ef7409a75f9f9ccebbb1705e48387d63e6ce86 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 21:28:15 +0100 Subject: [PATCH 10/13] Feedback --- .../ModExpPrecompile.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index c2ba884ecd0..a9e47b74c6b 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -32,6 +32,7 @@ public class ModExpPrecompile : IPrecompile private const int StartBaseLength = 0; private const int StartExpLength = 32; private const int StartModLength = 64; + private const int LengthsLengths = StartModLength + LengthSize; private ModExpPrecompile() { @@ -65,7 +66,7 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec try { ReadOnlySpan span = inputData.Span; - return span.Length >= 96 + return span.Length >= LengthsLengths ? DataGasCostInternal(span, releaseSpec) : DataGasCostShortInternal(span, releaseSpec); } @@ -78,9 +79,9 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec [MethodImpl(MethodImplOptions.NoInlining)] private static long DataGasCostShortInternal(ReadOnlySpan inputData, IReleaseSpec releaseSpec) { - Debug.Assert(inputData.Length < 96); + Debug.Assert(inputData.Length < LengthsLengths); - Span extendedInput = stackalloc byte[96]; + Span extendedInput = stackalloc byte[LengthsLengths]; inputData.CopyTo(extendedInput); return DataGasCostInternal(extendedInput, releaseSpec); @@ -96,8 +97,8 @@ private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSp ulong complexity = MultComplexity(baseLength, modulusLength, releaseSpec.IsEip7883Enabled); - uint expLengthUpTo32 = Math.Min(32, expLength); - uint startIndex = 96 + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? + uint expLengthUpTo32 = Math.Min(LengthSize, expLength); + uint startIndex = LengthsLengths + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? UInt256 exp = new(inputData.SliceWithZeroPaddingEmptyOnError((int)startIndex, (int)expLengthUpTo32), isBigEndian: true); UInt256 iterationCount = CalculateIterationCount(expLength, exp, releaseSpec.IsEip7883Enabled); @@ -118,9 +119,9 @@ private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLengt [MethodImpl(MethodImplOptions.NoInlining)] private (uint baseLength, uint expLength, uint modulusLength) GetInputLengthsShort(ReadOnlySpan inputData) { - Debug.Assert(inputData.Length < 96); + Debug.Assert(inputData.Length < LengthsLengths); - Span extendedInput = stackalloc byte[96]; + Span extendedInput = stackalloc byte[LengthsLengths]; inputData.CopyTo(extendedInput); return GetInputLengths(extendedInput); @@ -226,7 +227,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re Metrics.ModExpPrecompile++; ReadOnlySpan inputSpan = inputData.Span; - (uint baseLength, uint expLength, uint modulusLength) = inputSpan.Length >= 96 + (uint baseLength, uint expLength, uint modulusLength) = inputSpan.Length >= LengthsLengths ? GetInputLengths(inputSpan) : GetInputLengthsShort(inputSpan); @@ -301,7 +302,7 @@ private static ulong MultComplexity(uint baseLength, uint modulusLength, bool is // If EIP-7883 => small-case = 16, else 2*sq when max>32 if (isEip7883Enabled) { - return max > 32 + return max > LengthSize ? (sq << 1) // 2 * words * words : 16UL; // constant floor } @@ -329,9 +330,10 @@ private static ulong MultComplexity(uint baseLength, uint modulusLength, bool is private static UInt256 CalculateIterationCount(uint exponentLength, UInt256 exponent, bool isEip7883Enabled) { ulong iterationCount; - if (exponentLength <= 32) + uint overflow = 0; + if (exponentLength <= LengthSize) { - iterationCount = exponent.IsZero ? 0 : (uint)(exponent.BitLen - 1); + iterationCount = (uint)Math.Max(1, exponent.BitLen - 1); } else { @@ -341,15 +343,20 @@ private static UInt256 CalculateIterationCount(uint exponentLength, UInt256 expo bitLength--; } - ulong multiplicationResult = (exponentLength - 32) * (isEip7883Enabled ? IterationCountMultiplierEip7883 : IterationCountMultiplierEip2565); + ulong multiplicationResult = (exponentLength - LengthSize) * (isEip7883Enabled ? IterationCountMultiplierEip7883 : IterationCountMultiplierEip2565); iterationCount = multiplicationResult + bitLength; if (iterationCount < multiplicationResult) { // Overflowed - return new UInt256(iterationCount, 1, 0, 0); + overflow = 1; + } + else if (iterationCount < 1) + { + // Min 1 iteration + iterationCount = 1; } } - return iterationCount > 1 ? new UInt256(iterationCount) : UInt256.One; + return new UInt256(iterationCount, overflow); } } From ebfcc6d7d471851b47dcb6980a1eac2cdfd08db7 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 21:32:32 +0100 Subject: [PATCH 11/13] Less branches --- src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index a9e47b74c6b..63edc8712c2 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -112,8 +112,8 @@ private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSp private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLength, uint expLength, uint modulusLength) { return releaseSpec.IsEip7823Enabled - ? baseLength > ModExpMaxInputSizeEip7823 || expLength > ModExpMaxInputSizeEip7823 || modulusLength > ModExpMaxInputSizeEip7823 - : baseLength == int.MaxValue || modulusLength == int.MaxValue; + ? baseLength > ModExpMaxInputSizeEip7823 | expLength > ModExpMaxInputSizeEip7823 | modulusLength > ModExpMaxInputSizeEip7823 + : baseLength > int.MaxValue | modulusLength > int.MaxValue; } [MethodImpl(MethodImplOptions.NoInlining)] From c57b52a95bdb387d7fd3b13b19c1b27745a7fa08 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 21:37:16 +0100 Subject: [PATCH 12/13] Less branches --- .../Nethermind.Evm.Precompiles/ModExpPrecompile.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 63edc8712c2..010a6418329 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -26,7 +26,7 @@ public class ModExpPrecompile : IPrecompile /// This constant defines the upper limit for the size of the input data that can be processed. /// For more details, see: https://eips.ethereum.org/EIPS/eip-7823 /// - public const int ModExpMaxInputSizeEip7823 = 1024; + public const uint ModExpMaxInputSizeEip7823 = 1024; private const int LengthSize = 32; private const int StartBaseLength = 0; @@ -112,8 +112,8 @@ private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSp private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLength, uint expLength, uint modulusLength) { return releaseSpec.IsEip7823Enabled - ? baseLength > ModExpMaxInputSizeEip7823 | expLength > ModExpMaxInputSizeEip7823 | modulusLength > ModExpMaxInputSizeEip7823 - : baseLength > int.MaxValue | modulusLength > int.MaxValue; + ? (baseLength > ModExpMaxInputSizeEip7823 | expLength > ModExpMaxInputSizeEip7823 | modulusLength > ModExpMaxInputSizeEip7823) + : (baseLength | modulusLength) > int.MaxValue; } [MethodImpl(MethodImplOptions.NoInlining)] From 1b3cdca88bc7db77a6ec73c3352e4b4e54622bc5 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 18 Jul 2025 21:41:44 +0100 Subject: [PATCH 13/13] fixed invalid --- src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs index 98ca3d46b46..578e214fbbf 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs @@ -60,7 +60,7 @@ public void TestInvalid(string inputHex) byte[] input = Bytes.FromHexString(inputHex); - Assert.Throws(() => TestSuccess(input, specDisabled)); + Assert.That(TestSuccess(input, specDisabled), Is.EqualTo(false)); Assert.That(TestSuccess(input, specEnabled), Is.EqualTo(false)); Assert.That(TestGas(input, specDisabled), Is.EqualTo(long.MaxValue));