-
Notifications
You must be signed in to change notification settings - Fork 519
feat: add EVM precompiles #488
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
19135fb
feat: add ECRec, ECAdd, ECMul, ECPair EVM precompiles
ivokub cff8e5a
fix: inut check that input slice elements are canonical in NewElement
ivokub 8c26769
test: skip full tests
ivokub 8886844
docs: fix typo
ivokub c214616
fix: ECPair performs pairing check
ivokub 6d83f5d
fix: ECPair takes multiple input pairs
ivokub 59bb100
test: generate passing ecpair test
ivokub 76ecf39
test: use random points in ECPair test
yelhousni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/consensys/gnark/frontend" | ||
| "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" | ||
| "github.com/consensys/gnark/std/math/bits" | ||
| "github.com/consensys/gnark/std/math/emulated" | ||
| ) | ||
|
|
||
| // ECRecover implements [ECRECOVER] precompile contract at address 0x01. | ||
| // | ||
| // [ECRECOVER]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/ecrecover/index.html | ||
| func ECRecover(api frontend.API, msg emulated.Element[emulated.Secp256k1Fr], | ||
| v frontend.Variable, r, s emulated.Element[emulated.Secp256k1Fr]) *sw_emulated.AffinePoint[emulated.Secp256k1Fp] { | ||
| // EVM uses v \in {27, 28}, but everyone else v >= 0. Convert back | ||
| v = api.Sub(v, 27) | ||
| var emfp emulated.Secp256k1Fp | ||
| var emfr emulated.Secp256k1Fr | ||
| fpField, err := emulated.NewField[emulated.Secp256k1Fp](api) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("new field: %v", err)) | ||
| } | ||
| frField, err := emulated.NewField[emulated.Secp256k1Fr](api) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("new field: %v", err)) | ||
| } | ||
| // with the encoding we may have that r,s < 2*Fr (i.e. not r,s < Fr). Apply more thorough checks. | ||
| frField.AssertIsLessOrEqual(&r, frField.Modulus()) | ||
| frField.AssertIsLessOrEqual(&s, frField.Modulus()) | ||
| curve, err := sw_emulated.New[emulated.Secp256k1Fp, emulated.Secp256k1Fr](api, sw_emulated.GetSecp256k1Params()) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("new curve: %v", err)) | ||
| } | ||
| // we cannot directly use the field emulation hint calling wrappers as we work between two fields. | ||
| Rlimbs, err := api.Compiler().NewHint(recoverPointHint, 2*int(emfp.NbLimbs()), recoverPointHintArgs(v, r)...) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("point hint: %v", err)) | ||
| } | ||
| R := sw_emulated.AffinePoint[emulated.Secp256k1Fp]{ | ||
| X: *fpField.NewElement(Rlimbs[0:emfp.NbLimbs()]), | ||
| Y: *fpField.NewElement(Rlimbs[emfp.NbLimbs() : 2*emfp.NbLimbs()]), | ||
| } | ||
| // we cannot directly use the field emulation hint calling wrappers as we work between two fields. | ||
| Plimbs, err := api.Compiler().NewHint(recoverPublicKeyHint, 2*int(emfp.NbLimbs()), recoverPublicKeyHintArgs(msg, v, r, s)...) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("point hint: %v", err)) | ||
| } | ||
| P := sw_emulated.AffinePoint[emulated.Secp256k1Fp]{ | ||
| X: *fpField.NewElement(Plimbs[0:emfp.NbLimbs()]), | ||
| Y: *fpField.NewElement(Plimbs[emfp.NbLimbs() : 2*emfp.NbLimbs()]), | ||
| } | ||
| // check that len(v) = 2 | ||
| vbits := bits.ToBinary(api, v, bits.WithNbDigits(2)) | ||
| // check that Rx is correct: x = r+v[1]*fr | ||
| tmp := fpField.Select(vbits[1], fpField.NewElement(emfr.Modulus()), fpField.NewElement(0)) | ||
| rbits := frField.ToBits(&r) | ||
| rfp := fpField.FromBits(rbits...) | ||
| tmp = fpField.Add(rfp, tmp) | ||
| fpField.AssertIsEqual(tmp, &R.X) | ||
| // check that Ry is correct: highbit(y) = v[0] | ||
| Rynormal := fpField.Reduce(&R.Y) | ||
| Rybits := fpField.ToBits(Rynormal) | ||
| api.AssertIsEqual(vbits[0], Rybits[emfp.Modulus().BitLen()-1]) | ||
| // compute rinv = r^{-1} mod fr | ||
| rinv := frField.Inverse(&r) | ||
| // compute u1 = -msg * rinv | ||
| u1 := frField.MulMod(&msg, rinv) | ||
| u1 = frField.Neg(u1) | ||
| // compute u2 = s * rinv | ||
| u2 := frField.MulMod(&s, rinv) | ||
| // check u1 * G + u2 R == P | ||
| A := curve.ScalarMulBase(u1) | ||
| B := curve.ScalarMul(&R, u2) | ||
| C := curve.Add(A, B) | ||
| curve.AssertIsEqual(C, &P) | ||
| return &P | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "crypto/rand" | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/consensys/gnark-crypto/ecc" | ||
| "github.com/consensys/gnark-crypto/ecc/secp256k1/ecdsa" | ||
| "github.com/consensys/gnark-crypto/ecc/secp256k1/fr" | ||
| "github.com/consensys/gnark/backend" | ||
| "github.com/consensys/gnark/frontend" | ||
| "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" | ||
| "github.com/consensys/gnark/std/math/emulated" | ||
| "github.com/consensys/gnark/test" | ||
| ) | ||
|
|
||
| func TestSignForRecoverCorrectness(t *testing.T) { | ||
| sk, err := ecdsa.GenerateKey(rand.Reader) | ||
| if err != nil { | ||
| t.Fatal("generate", err) | ||
| } | ||
| pk := sk.PublicKey | ||
| msg := []byte("test") | ||
| _, r, s, err := sk.SignForRecover(msg, nil) | ||
| if err != nil { | ||
| t.Fatal("sign", err) | ||
| } | ||
| var sig ecdsa.Signature | ||
| r.FillBytes(sig.R[:fr.Bytes]) | ||
| s.FillBytes(sig.S[:fr.Bytes]) | ||
| sigM := sig.Bytes() | ||
| ok, err := pk.Verify(sigM, msg, nil) | ||
| if err != nil { | ||
| t.Fatal("verify", err) | ||
| } | ||
| if !ok { | ||
| t.Fatal("not verified") | ||
| } | ||
| } | ||
|
|
||
| type ecrecoverCircuit struct { | ||
| Message emulated.Element[emulated.Secp256k1Fr] | ||
| V frontend.Variable | ||
| R emulated.Element[emulated.Secp256k1Fr] | ||
| S emulated.Element[emulated.Secp256k1Fr] | ||
| Expected sw_emulated.AffinePoint[emulated.Secp256k1Fp] | ||
| } | ||
|
|
||
| func (c *ecrecoverCircuit) Define(api frontend.API) error { | ||
| curve, err := sw_emulated.New[emulated.Secp256k1Fp, emulated.Secp256k1Fr](api, sw_emulated.GetSecp256k1Params()) | ||
| if err != nil { | ||
| return fmt.Errorf("new curve: %w", err) | ||
| } | ||
| res := ECRecover(api, c.Message, c.V, c.R, c.S) | ||
| curve.AssertIsEqual(&c.Expected, res) | ||
| return nil | ||
| } | ||
|
|
||
| func testRoutineECRecover(t *testing.T) (circ, wit frontend.Circuit) { | ||
| sk, err := ecdsa.GenerateKey(rand.Reader) | ||
| if err != nil { | ||
| t.Fatal("generate", err) | ||
| } | ||
| pk := sk.PublicKey | ||
| msg := []byte("test") | ||
| v, r, s, err := sk.SignForRecover(msg, nil) | ||
| if err != nil { | ||
| t.Fatal("sign", err) | ||
| } | ||
| circuit := ecrecoverCircuit{} | ||
| witness := ecrecoverCircuit{ | ||
| Message: emulated.ValueOf[emulated.Secp256k1Fr](ecdsa.HashToInt(msg)), | ||
| V: v + 27, // EVM constant | ||
| R: emulated.ValueOf[emulated.Secp256k1Fr](r), | ||
| S: emulated.ValueOf[emulated.Secp256k1Fr](s), | ||
| Expected: sw_emulated.AffinePoint[emulated.Secp256k1Fp]{ | ||
| X: emulated.ValueOf[emulated.Secp256k1Fp](pk.A.X), | ||
| Y: emulated.ValueOf[emulated.Secp256k1Fp](pk.A.Y), | ||
| }, | ||
| } | ||
| return &circuit, &witness | ||
| } | ||
|
|
||
| func TestECRecoverCircuitShort(t *testing.T) { | ||
| assert := test.NewAssert(t) | ||
| circuit, witness := testRoutineECRecover(t) | ||
| err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()) | ||
| assert.NoError(err) | ||
| } | ||
|
|
||
| func TestECRecoverCircuitFull(t *testing.T) { | ||
| t.Skip("skipping very long test") | ||
| assert := test.NewAssert(t) | ||
| circuit, witness := testRoutineECRecover(t) | ||
| assert.ProverSucceeded(circuit, witness, | ||
| test.NoFuzzing(), test.NoSerialization(), | ||
| test.WithBackends(backend.GROTH16, backend.PLONK), test.WithCurves(ecc.BN254), | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package evmprecompiles | ||
|
|
||
| // will implement later |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package evmprecompiles | ||
|
|
||
| // not going to implement. It is trivial. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| package evmprecompiles |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "github.com/consensys/gnark/frontend" | ||
| "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" | ||
| "github.com/consensys/gnark/std/math/emulated" | ||
| ) | ||
|
|
||
| // ECAdd implements [ALT_BN128_ADD] precompile contract at address 0x06. | ||
| // | ||
| // [ALT_BN128_ADD]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/alt_bn128/index.html#alt-bn128-add | ||
| func ECAdd(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BN254Fp]) *sw_emulated.AffinePoint[emulated.BN254Fp] { | ||
| curve, err := sw_emulated.New[emulated.BN254Fp, emulated.BN254Fr](api, sw_emulated.GetBN254Params()) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| res := curve.Add(P, Q) | ||
| return res | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "github.com/consensys/gnark/frontend" | ||
| "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" | ||
| "github.com/consensys/gnark/std/math/emulated" | ||
| ) | ||
|
|
||
| // ECMul implements [ALT_BN128_MUL] precompile contract at address 0x07. | ||
| // | ||
| // [ALT_BN128_MUL]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/alt_bn128/index.html#alt-bn128-mul | ||
| func ECMul(api frontend.API, P *sw_emulated.AffinePoint[emulated.BN254Fp], u *emulated.Element[emulated.BN254Fr]) *sw_emulated.AffinePoint[emulated.BN254Fp] { | ||
| curve, err := sw_emulated.New[emulated.BN254Fp, emulated.BN254Fr](api, sw_emulated.GetBN254Params()) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| res := curve.ScalarMul(P, u) | ||
| return res | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "github.com/consensys/gnark/frontend" | ||
| "github.com/consensys/gnark/std/algebra/emulated/sw_bn254" | ||
| ) | ||
|
|
||
| // ECPair implements [ALT_BN128_PAIRING_CHECK] precompile contract at address 0x08. | ||
| // | ||
| // [ALT_BN128_PAIRING_CHECK]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/alt_bn128/index.html#alt-bn128-pairing-check | ||
| func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { | ||
| pair, err := sw_bn254.NewPairing(api) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| if err := pair.PairingCheck(P, Q); err != nil { | ||
| panic(err) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.