-
Notifications
You must be signed in to change notification settings - Fork 479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EIP/RIP-7212 #7135
EIP/RIP-7212 #7135
Conversation
# Conflicts: # src/Nethermind/Nethermind.Evm/VirtualMachine.cs
Current implementation uses built-in .NET ECDsa, which is not the fastest option. Benchmarks comparing different versions: .NETWindows 10
Ubuntu 22
GoWindows 10
Ubuntu 22
|
Which go lib specifically is that? |
@benaadams, built-in ecdsa package. Go code used: import (
"encoding/hex"
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
"fmt"
"C"
)
//export VerifyHex
func VerifyHex(input *C.char) bool {
var h = C.GoString(input)
bytes, err := hex.DecodeString(h)
if err != nil {
return false
}
return VerifyBytes(bytes)
}
//export VerifyBytes
func VerifyBytes(bytes []byte) bool {
if len(bytes) != 160 {
return false
}
var hash = bytes[0:32]
var r, s = new(big.Int).SetBytes(bytes[32:64]), new(big.Int).SetBytes(bytes[64:96])
var x, y = new(big.Int).SetBytes(bytes[96:128]), new(big.Int).SetBytes(bytes[128:160])
var publicKey = &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
return ecdsa.Verify(publicKey, hash, r, s)
} Can be built into a library via go build -ldflags="-s -w" -buildmode=c-shared -o secp256r1.dll main.go |
C# benchmarking code: public class Secp256r1VerifyBenchmarks
{
private const string InputHex = "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e";
private static readonly byte[] Input = Convert.FromHexString(InputHex);
private ECDsa _ecdsaShared;
private X9ECParameters _bouncyCurve;
private ISigner _bouncySigner;
private ECDomainParameters _bouncyDomainParameters;
[GlobalSetup]
public void Setup()
{
_ecdsaShared = ECDsa.Create();
_bouncyCurve = CustomNamedCurves.GetByName("secp256r1");
_bouncySigner = SignerUtilities.GetSigner("NONEWITHPLAIN-ECDSA");
_bouncyDomainParameters = new(_bouncyCurve);
}
[Benchmark]
public bool DotNet_Ecdsa_WithCreate()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
var ecdsa = ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark]
public bool DotNet_Ecdsa_WithImportParameters()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECDsa ecdsa = _ecdsaShared;
ecdsa.ImportParameters(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark] // 211.2 us
public bool BouncyCastle()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECPublicKeyParameters parameters = new(
"ECDSA",
_bouncyCurve.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(1, x), new Org.BouncyCastle.Math.BigInteger(1, y)),
_bouncyDomainParameters
);
_bouncySigner.Init(false, parameters);
_bouncySigner.BlockUpdate(hash);
return _bouncySigner.VerifySignature(sig.ToArray());
}
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyHex(string hash);
[Benchmark]
public bool Go_Ecdsa_MarshallingString() => VerifyHex(InputHex) != 0;
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyBytes(GoSlice hash);
private struct GoSlice(IntPtr data, long len, long cap)
{
public IntPtr Data = data;
public long Len = len, Cap = cap;
}
[Benchmark]
public bool Go_Ecdsa_MarshallingSlice()
{
unsafe
{
fixed (byte* p = Input)
{
var ptr = (IntPtr) p;
var slice = new GoSlice(ptr, Input.Length, Input.Length);
return VerifyBytes(slice) != 0;
}
}
}
// Other options:
// https://github.com/starkbank/ecdsa-dotnet - needs bridge/pinvoke from Rust
// https://github.com/MystenLabs/fastcrypto - very slow actually
} |
Aside; this is different to the |
It's for secP256_r_1 |
Noticed that Go implementation does some caching, at least when converting public key to C version, so updated benchmarks to try to eliminate/reduce any caching benefits. Didn't find any noticeable difference so: public class Secp256r1VerifyBenchmarks
{
public byte[][] Inputs;
public string[] InputsHexes;
private ECDsa _ecdsaShared;
private X9ECParameters _bouncyCurve;
private ISigner _bouncySigner;
private ECDomainParameters _bouncyDomainParameters;
[Params(1, 100, 10_000)]
public int SamplesCount;
[GlobalSetup]
public void Setup()
{
_ecdsaShared = ECDsa.Create();
_bouncyCurve = CustomNamedCurves.GetByName("secp256r1");
_bouncySigner = SignerUtilities.GetSigner("NONEWITHPLAIN-ECDSA");
_bouncyDomainParameters = new(_bouncyCurve);
var rng = RandomNumberGenerator.Create();
Inputs = new byte[SamplesCount][];
InputsHexes = new string[SamplesCount];
for (var i = 0; i < Inputs.Length; i++)
{
var hash = new byte[32];
rng.GetBytes(hash);
var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
ECParameters pub = ecdsa.ExportParameters(false);
byte[] sig = ecdsa.SignHash(hash, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
(byte[] x, byte[] y) = (pub.Q.X, pub.Q.Y);
Inputs[i] = [..hash, ..sig, ..x, ..y];
InputsHexes[i] = Convert.ToHexString(Inputs[i]);
}
}
private int Index => Random.Shared.Next(Inputs.Length);
private byte[] Input => Inputs[Index];
private string InputHex => InputsHexes[Index];
[Benchmark]
public bool DotNet_Ecdsa_WithCreate()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
var ecdsa = ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark]
public bool DotNet_Ecdsa_WithImportParameters()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECDsa ecdsa = _ecdsaShared;
ecdsa.ImportParameters(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new() { X = x.ToArray(), Y = y.ToArray() }
});
return ecdsa.VerifyHash(hash, sig);
}
[Benchmark]
public bool BouncyCastle()
{
ReadOnlySpan<byte> input = Input;
ReadOnlySpan<byte> hash = input[..32];
ReadOnlySpan<byte> sig = input[32..96];
ReadOnlySpan<byte> x = input[96..128];
ReadOnlySpan<byte> y = input[128..160];
ECPublicKeyParameters parameters = new(
"ECDSA",
_bouncyCurve.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(1, x), new Org.BouncyCastle.Math.BigInteger(1, y)),
_bouncyDomainParameters
);
_bouncySigner.Init(false, parameters);
_bouncySigner.BlockUpdate(hash);
return _bouncySigner.VerifySignature(sig.ToArray());
}
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyHex(string hash);
[Benchmark]
public bool Go_Ecdsa_MarshallingString() => VerifyHex(InputHex) != 0;
#if Windows
[DllImport("secp256r1.dll", CallingConvention = CallingConvention.Cdecl)]
#elif Linux
[DllImport("secp256r1.so", CallingConvention = CallingConvention.Cdecl)]
#endif
private static extern byte VerifyBytes(GoSlice hash);
private struct GoSlice(IntPtr data, long len, long cap)
{
public IntPtr Data = data;
public long Len = len, Cap = cap;
}
[Benchmark]
public bool Go_Ecdsa_MarshallingSlice()
{
ReadOnlySpan<byte> input = Input;
unsafe
{
fixed (byte* p = input)
{
var ptr = (IntPtr) p;
var slice = new GoSlice(ptr, input.Length, input.Length);
return VerifyBytes(slice) != 0;
}
}
}
// Other options:
// https://github.com/starkbank/ecdsa-dotnet - needs bridge/pinvoke from Rust
// https://github.com/MystenLabs/fastcrypto - very slow actually
} |
Changes
Adds secp256r1 precompile contract.
For now intended to be used in Optimism, but may be enabled in Ethereum as agreed.
EIP | RIP | Discussion | Geth PR
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Requires explanation in Release Notes