From 57a24e92b618198fff079bfcef6f2acdf7c0c085 Mon Sep 17 00:00:00 2001 From: Alex Bespalov Date: Thu, 13 Jun 2024 15:14:25 +0300 Subject: [PATCH] EIP/RIP-7212 (#7135) --- src/Nethermind/Chains/op-sepolia.json | 2 +- .../Nethermind.Core/Specs/IReleaseSpec.cs | 5 ++ .../Secp256r1PrecompilePrecompileTests.cs | 87 +++++++++++++++++++ .../Nethermind.Evm/CodeInfoRepository.cs | 2 + src/Nethermind/Nethermind.Evm/Metrics.cs | 3 + .../Precompiles/AddressExtensions.cs | 51 ++++++----- .../Precompiles/Secp256r1Precompile.cs | 45 ++++++++++ .../OverridableReleaseSpec.cs | 1 + .../ChainSpecStyle/ChainParameters.cs | 1 + .../ChainSpecBasedSpecProvider.cs | 1 + .../ChainSpecStyle/ChainSpecLoader.cs | 1 + .../Json/ChainSpecParamsJson.cs | 1 + .../Nethermind.Specs/Forks/18_Prague.cs | 1 + .../Nethermind.Specs/ReleaseSpec.cs | 1 + .../SystemTransactionReleaseSpec.cs | 1 + 15 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 src/Nethermind/Nethermind.Evm.Test/Secp256r1PrecompilePrecompileTests.cs create mode 100644 src/Nethermind/Nethermind.Evm/Precompiles/Secp256r1Precompile.cs diff --git a/src/Nethermind/Chains/op-sepolia.json b/src/Nethermind/Chains/op-sepolia.json index f90ba4e5a25..ab7158b1261 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 c34e6933f53..de7e33ea06d 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 00000000000..b123d2074b9 --- /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 e5fc48d677b..d75e375c93a 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -91,6 +91,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 8c3286facc9..c1072cafe20 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 0f4fe5ebb6b..11ba1f00ae5 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 00000000000..ed00e14a789 --- /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 f6d4bacf1a6..5ed238c70b8 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 41fa983d646..d3f5c970570 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 860e48b8814..d0b3805a52f 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 769a9858633..068c4d19365 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 283f755d9f0..499022ca0c6 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 11036fa0946..928e61cc372 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 c3615a05a13..67cdfdd7a14 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 cc9f87ffa7a..9ab42d14e21 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";