diff --git a/src/Nethermind/Chains/op-sepolia.json b/src/Nethermind/Chains/op-sepolia.json
index f90ba4e5a259..ab7158b12611 100644
--- a/src/Nethermind/Chains/op-sepolia.json
+++ b/src/Nethermind/Chains/op-sepolia.json
@@ -61,7 +61,7 @@
"eip4844TransitionTimestamp": "0x65D62C10",
"eip5656TransitionTimestamp": "0x65D62C10",
"eip6780TransitionTimestamp": "0x65D62C10",
- "eip7212TransitionTimestamp": "0x66575100",
+ "rip7212TransitionTimestamp": "0x66575100",
"terminalTotalDifficulty": "0"
},
"genesis": {
diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
index c34e6933f531..de7e33ea06d2 100644
--- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
@@ -279,6 +279,11 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec
///
bool IsEip6780Enabled { get; }
+ ///
+ /// Secp256r1 precompile
+ ///
+ bool IsRip7212Enabled { get; }
+
///
/// Should transactions be validated against chainId.
///
diff --git a/src/Nethermind/Nethermind.Evm.Test/Secp256r1PrecompilePrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/Secp256r1PrecompilePrecompileTests.cs
new file mode 100644
index 000000000000..b123d2074b9e
--- /dev/null
+++ b/src/Nethermind/Nethermind.Evm.Test/Secp256r1PrecompilePrecompileTests.cs
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using FluentAssertions;
+using Nethermind.Core.Extensions;
+using Nethermind.Evm.Precompiles;
+using Nethermind.Specs.Forks;
+using NUnit.Framework;
+
+namespace Nethermind.Evm.Test
+{
+ [TestFixture]
+ public class Secp256r1PrecompilePrecompileTests : VirtualMachineTestsBase
+ {
+ private static readonly byte[] ValidAnswer = Bytes.FromHexString(
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ );
+
+ [Test] // https://github.com/paradigmxyz/alphanet/blob/main/crates/precompile/src/secp256r1.rs#L137
+ [TestCase(
+ "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
+ true
+ )]
+ [TestCase(
+ "3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5",
+ true
+ )]
+ [TestCase(
+ "e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a",
+ true
+ )]
+ [TestCase(
+ "b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1",
+ true
+ )]
+ [TestCase(
+ "858b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2",
+ true
+ )]
+ [TestCase(
+ "3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
+ false
+ )]
+ [TestCase(
+ "afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5",
+ false
+ )]
+ [TestCase(
+ "f775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a",
+ false
+ )]
+ [TestCase(
+ "c5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d262144475b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fccf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1",
+ false
+ )]
+ [TestCase(
+ "958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf31b7c691f5ce665f8aae0bda895c23595c834fecc2390a5bcc203b04afcacbb4280713287a2d0c37e23f7513fab898f2c1fefa00ec09a924c335d9b629f1d4fb71901c3e59611afbfea354d101324e894c788d1c01f00b3c251b2",
+ false
+ )]
+ public void Produces_Correct_Outputs(string input, bool isValid)
+ {
+ var bytes = Bytes.FromHexString(input);
+ (ReadOnlyMemory output, bool success) = Secp256r1Precompile.Instance.Run(bytes, Prague.Instance);
+ success.Should().BeTrue();
+ output.ToArray().Should().BeEquivalentTo(isValid ? ValidAnswer : []);
+ }
+
+ [Test]
+ [TestCase(
+ ""
+ )]
+ [TestCase(
+ "4cee90eb86eaa050036147a12d49004b6a"
+ )]
+ [TestCase(
+ "4cee90eb86eaa050036147a12d49004b6a958b991cfd78f16537fe6d1f4afd10273384db08bdfc843562a22b0626766686f6aec8247599f40bfe01bec0e0ecf17b4319559022d4d9bf007fe929943004eb4866760dedf319"
+ )]
+ public void Produces_Empty_Output_On_Invalid_Input(string input)
+ {
+ var bytes = Bytes.FromHexString(input);
+ (ReadOnlyMemory output, bool success) = Secp256r1Precompile.Instance.Run(bytes, Prague.Instance);
+ success.Should().BeTrue();
+ output.Should().Be(ReadOnlyMemory.Empty);
+ }
+ }
+}
diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
index 48b67ddd52d0..57eb47d499de 100644
--- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
+++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
@@ -89,6 +89,8 @@ private static FrozenDictionary InitializePrecompiledCon
[MapToG2Precompile.Address] = new(MapToG2Precompile.Instance),
[PointEvaluationPrecompile.Address] = new(PointEvaluationPrecompile.Instance),
+
+ [Secp256r1Precompile.Address] = new(Secp256r1Precompile.Instance),
}.ToFrozenDictionary();
}
diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs
index 8c3286facc9a..c1072cafe204 100644
--- a/src/Nethermind/Nethermind.Evm/Metrics.cs
+++ b/src/Nethermind/Nethermind.Evm/Metrics.cs
@@ -113,6 +113,9 @@ public static long SstoreOpcode
[Description("Number of SHA256 precompile calls.")]
public static long Sha256Precompile { get; set; }
+ [Description("Number of Secp256r1 precompile calls.")]
+ public static long Secp256r1Precompile { get; set; }
+
[Description("Number of Point Evaluation precompile calls.")]
public static long PointEvaluationPrecompile { get; set; }
diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/AddressExtensions.cs b/src/Nethermind/Nethermind.Evm/Precompiles/AddressExtensions.cs
index 0f4fe5ebb6be..11ba1f00ae55 100644
--- a/src/Nethermind/Nethermind.Evm/Precompiles/AddressExtensions.cs
+++ b/src/Nethermind/Nethermind.Evm/Precompiles/AddressExtensions.cs
@@ -13,29 +13,38 @@ public static class AddressExtensions
public static bool IsPrecompile(this Address address, IReleaseSpec releaseSpec)
{
Span data = MemoryMarshal.Cast(address.Bytes.AsSpan());
- return (data[4] & 0x00ffffff) == 0
+ return (data[4] & 0x0000ffff) == 0
&& data[3] == 0 && data[2] == 0 && data[1] == 0 && data[0] == 0
- && (data[4] >>> 24) switch
+ && ((data[4] >>> 16) & 0xff) switch
{
- 0x01 => true,
- 0x02 => true,
- 0x03 => true,
- 0x04 => true,
- 0x05 => releaseSpec.ModExpEnabled,
- 0x06 => releaseSpec.Bn128Enabled,
- 0x07 => releaseSpec.Bn128Enabled,
- 0x08 => releaseSpec.Bn128Enabled,
- 0x09 => releaseSpec.BlakeEnabled,
- 0x0a => releaseSpec.IsEip4844Enabled,
- 0x0b => releaseSpec.Bls381Enabled,
- 0x0c => releaseSpec.Bls381Enabled,
- 0x0d => releaseSpec.Bls381Enabled,
- 0x0e => releaseSpec.Bls381Enabled,
- 0x0f => releaseSpec.Bls381Enabled,
- 0x10 => releaseSpec.Bls381Enabled,
- 0x11 => releaseSpec.Bls381Enabled,
- 0x12 => releaseSpec.Bls381Enabled,
- 0x13 => releaseSpec.Bls381Enabled,
+ 0x00 => (data[4] >>> 24) switch
+ {
+ 0x01 => true,
+ 0x02 => true,
+ 0x03 => true,
+ 0x04 => true,
+ 0x05 => releaseSpec.ModExpEnabled,
+ 0x06 => releaseSpec.Bn128Enabled,
+ 0x07 => releaseSpec.Bn128Enabled,
+ 0x08 => releaseSpec.Bn128Enabled,
+ 0x09 => releaseSpec.BlakeEnabled,
+ 0x0a => releaseSpec.IsEip4844Enabled,
+ 0x0b => releaseSpec.Bls381Enabled,
+ 0x0c => releaseSpec.Bls381Enabled,
+ 0x0d => releaseSpec.Bls381Enabled,
+ 0x0e => releaseSpec.Bls381Enabled,
+ 0x0f => releaseSpec.Bls381Enabled,
+ 0x10 => releaseSpec.Bls381Enabled,
+ 0x11 => releaseSpec.Bls381Enabled,
+ 0x12 => releaseSpec.Bls381Enabled,
+ 0x13 => releaseSpec.Bls381Enabled,
+ _ => false
+ },
+ 0x01 => (data[4] >>> 24) switch
+ {
+ 0x00 => releaseSpec.IsRip7212Enabled,
+ _ => false
+ },
_ => false
};
}
diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Secp256r1Precompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Secp256r1Precompile.cs
new file mode 100644
index 000000000000..ed00e14a7898
--- /dev/null
+++ b/src/Nethermind/Nethermind.Evm/Precompiles/Secp256r1Precompile.cs
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Security.Cryptography;
+using Nethermind.Core;
+using Nethermind.Core.Extensions;
+using Nethermind.Core.Specs;
+
+namespace Nethermind.Evm.Precompiles;
+
+public class Secp256r1Precompile : IPrecompile
+{
+ private static readonly byte[] ValidResult = new byte[] { 1 }.PadLeft(32);
+
+ public static readonly Secp256r1Precompile Instance = new();
+ public static Address Address { get; } = Address.FromNumber(0x100);
+
+ public long BaseGasCost(IReleaseSpec releaseSpec) => 3450L;
+ public long DataGasCost(in ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L;
+
+ // TODO can be optimized - Go implementation is 2-6 times faster depending on the platform. Options:
+ // - Try to replicate Go version in C#
+ // - Compile Go code into a library and call it via P/Invoke
+ public (ReadOnlyMemory, bool) Run(in ReadOnlyMemory inputData, IReleaseSpec releaseSpec)
+ {
+ if (inputData.Length != 160)
+ return (null, true);
+
+ ReadOnlySpan bytes = inputData.Span;
+ ReadOnlySpan hash = bytes[..32], sig = bytes[32..96];
+ ReadOnlySpan x = bytes[96..128], y = bytes[128..160];
+
+ using var ecdsa = ECDsa.Create(new ECParameters
+ {
+ Curve = ECCurve.NamedCurves.nistP256,
+ Q = new() { X = x.ToArray(), Y = y.ToArray() }
+ });
+ var isValid = ecdsa.VerifyHash(hash, sig);
+
+ Metrics.Secp256r1Precompile++;
+
+ return (isValid ? ValidResult : null, true);
+ }
+}
diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
index f6d4bacf1a69..5ed238c70b8c 100644
--- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
@@ -111,6 +111,7 @@ public OverridableReleaseSpec(IReleaseSpec spec)
public bool IsEip3541Enabled => _spec.IsEip3541Enabled;
public bool IsEip4844Enabled => _spec.IsEip4844Enabled;
+ public bool IsRip7212Enabled => _spec.IsRip7212Enabled;
public bool IsEip3607Enabled { get; set; }
public bool IsEip158IgnoredAccount(Address address)
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
index 41fa983d646a..d3f5c9705706 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
@@ -119,6 +119,7 @@ public class ChainParameters
public Address Eip4788ContractAddress { get; set; }
public ulong? Eip2935TransitionTimestamp { get; set; }
public Address Eip2935ContractAddress { get; set; }
+ public ulong? Rip7212TransitionTimestamp { get; set; }
#region EIP-4844 parameters
///
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
index 860e48b88149..d0b3805a52fb 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
@@ -245,6 +245,7 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt
releaseSpec.WithdrawalTimestamp = chainSpec.Parameters.Eip4895TransitionTimestamp ?? ulong.MaxValue;
releaseSpec.IsEip4844Enabled = (chainSpec.Parameters.Eip4844TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
+ releaseSpec.IsRip7212Enabled = (chainSpec.Parameters.Rip7212TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
releaseSpec.Eip4844TransitionTimestamp = chainSpec.Parameters.Eip4844TransitionTimestamp ?? ulong.MaxValue;
releaseSpec.IsEip5656Enabled = (chainSpec.Parameters.Eip5656TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
releaseSpec.IsEip6780Enabled = (chainSpec.Parameters.Eip6780TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
index 769a9858633f..068c4d193650 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
@@ -141,6 +141,7 @@ bool GetForInnerPathExistence(KeyValuePair o) =>
Eip2537TransitionTimestamp = chainSpecJson.Params.Eip2537TransitionTimestamp,
Eip5656TransitionTimestamp = chainSpecJson.Params.Eip5656TransitionTimestamp,
Eip6780TransitionTimestamp = chainSpecJson.Params.Eip6780TransitionTimestamp,
+ Rip7212TransitionTimestamp = chainSpecJson.Params.Rip7212TransitionTimestamp,
Eip4788TransitionTimestamp = chainSpecJson.Params.Eip4788TransitionTimestamp,
Eip4788ContractAddress = chainSpecJson.Params.Eip4788ContractAddress ?? Eip4788Constants.BeaconRootsAddress,
Eip2935TransitionTimestamp = chainSpecJson.Params.Eip2935TransitionTimestamp,
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
index 283f755d9f07..499022ca0c62 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
@@ -147,4 +147,5 @@ internal class ChainSpecParamsJson
public ulong? Eip4844MaxBlobGasPerBlock { get; set; }
public UInt256? Eip4844MinBlobGasPrice { get; set; }
public ulong? Eip4844TargetBlobGasPerBlock { get; set; }
+ public ulong? Rip7212TransitionTimestamp { get; set; }
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs b/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs
index 11036fa09462..928e61cc372e 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs
@@ -16,6 +16,7 @@ protected Prague()
Name = "Prague";
IsEip2537Enabled = true;
IsEip2935Enabled = true;
+ IsRip7212Enabled = true;
Eip2935ContractAddress = Eip2935Constants.BlockHashHistoryAddress;
}
diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
index c3615a05a13a..67cdfdd7a14f 100644
--- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
@@ -80,6 +80,7 @@ public ReleaseSpec Clone()
public bool IsEip3860Enabled { get; set; }
public bool IsEip4895Enabled { get; set; }
public bool IsEip4844Enabled { get; set; }
+ public bool IsRip7212Enabled { get; set; }
public bool IsEip5656Enabled { get; set; }
public bool IsEip6780Enabled { get; set; }
public bool IsEip4788Enabled { get; set; }
diff --git a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs
index cc9f87ffa7ab..9ab42d14e212 100644
--- a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs
@@ -16,6 +16,7 @@ public SystemTransactionReleaseSpec(IReleaseSpec spec)
_spec = spec;
}
public bool IsEip4844Enabled => _spec.IsEip4844Enabled;
+ public bool IsRip7212Enabled => _spec.IsRip7212Enabled;
public string Name => "System";