-
Notifications
You must be signed in to change notification settings - Fork 494
feat: EIP-7951 for ECDSA on P-256 curve #1649
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
+6,280
−22
Merged
Changes from 3 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
265d08f
feat: eip-7951 ecdsa p256
yelhousni 847ba74
docs: add comments P256Verify
yelhousni 3bc2819
test: add test vectors
yelhousni 3f6b94b
Merge branch 'master' into feat/eip-7951
ivokub 72ad80d
chore: use non-deprecated method for reading file
ivokub 5459acd
feat: implement unified versions of double, triple and doubleAndAdd
ivokub 465ba29
feat: use generic methods in scalarmul on switch
ivokub 7931f3e
refactor: direct implementation of ecdsa verification precompile
ivokub 2423768
test: implement utility functions for mocking arithmetization
ivokub 259be59
test: mock arithmetization and use full test vector set
ivokub 78a70d1
docs: define assumptions
ivokub dbec56e
test: remove unused test
ivokub 4426258
fix: initialized varaible
ivokub 5fef998
perf: use jointscalarmul
ivokub 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,37 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "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/std/signature/ecdsa" | ||
| ) | ||
|
|
||
| // P256Verify implements [P256Verify] precompile contract at address 0x100. | ||
| // | ||
| // This circuit performs ECDSA signature verification over the secp256r1 | ||
| // elliptic curve (also known as P-256 or prime256v1). | ||
| // | ||
| // [P256Verify]: https://eips.ethereum.org/EIPS/eip-7951 | ||
| func P256Verify(api frontend.API, | ||
| msgHash emulated.Element[emulated.P256Fr], | ||
| r, s emulated.Element[emulated.P256Fr], | ||
| qx, qy emulated.Element[emulated.P256Fp], | ||
| ) frontend.Variable { | ||
| // Input validation: | ||
| // 1. input_length == 160 ==> checked by the arithmetization | ||
| // 2. 0 < r < n and 0 < s < n ==> checked by the arithmetization/ECDATA and enforced in `IsValid()` | ||
| // 3. 0 ≤ qx < p and 0 ≤ qy < p ==> checked by the arithmetization/ECDATA | ||
| // 4. (qx, qy) is a valid point on the curve P256 ==> checked by the arithmetization/ECDATA | ||
| // 5. (qx, qy) is not (0,0) ==> checked by the arithmetization/ECDATA | ||
| pk := ecdsa.PublicKey[emulated.P256Fp, emulated.P256Fr]{ | ||
| X: qx, | ||
| Y: qy, | ||
| } | ||
| sig := ecdsa.Signature[emulated.P256Fr]{ | ||
| R: r, | ||
| S: s, | ||
| } | ||
| verified := pk.IsValid(api, sw_emulated.GetCurveParams[emulated.P256Fp](), &msgHash, &sig) | ||
| return verified | ||
| } | ||
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,145 @@ | ||
| package evmprecompiles | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "crypto/rand" | ||
| "encoding/hex" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io/ioutil" | ||
ivokub marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "math/big" | ||
| "testing" | ||
|
|
||
| "github.com/consensys/gnark-crypto/ecc" | ||
| "github.com/consensys/gnark-crypto/ecc/secp256r1/ecdsa" | ||
| "github.com/consensys/gnark/frontend" | ||
| "github.com/consensys/gnark/std/math/emulated" | ||
| "github.com/consensys/gnark/test" | ||
| ) | ||
|
|
||
| type p256verifyCircuit struct { | ||
| MsgHash emulated.Element[emulated.P256Fr] | ||
| R emulated.Element[emulated.P256Fr] | ||
| S emulated.Element[emulated.P256Fr] | ||
| Qx, Qy emulated.Element[emulated.P256Fp] | ||
| Expected frontend.Variable | ||
| } | ||
|
|
||
| func (c *p256verifyCircuit) Define(api frontend.API) error { | ||
| res := P256Verify(api, c.MsgHash, c.R, c.S, c.Qx, c.Qy) | ||
| api.AssertIsEqual(c.Expected, res) | ||
| return nil | ||
| } | ||
|
|
||
| func TestP256VerifyCircuit(t *testing.T) { | ||
| assert := test.NewAssert(t) | ||
| // key generation | ||
| sk, err := ecdsa.GenerateKey(rand.Reader) | ||
| if err != nil { | ||
| t.Fatal("generate", err) | ||
| } | ||
| pk := sk.PublicKey | ||
| // signing | ||
| msg := []byte("test") | ||
| sigBuf, err := sk.Sign(msg, nil) | ||
| if err != nil { | ||
| t.Fatal("sign", err) | ||
| } | ||
| // verification | ||
| verified, err := sk.PublicKey.Verify(sigBuf, msg, nil) | ||
| if err != nil { | ||
| t.Fatal("verify", err) | ||
| } | ||
| // marshalling | ||
| var sig ecdsa.Signature | ||
| sig.SetBytes(sigBuf[:]) | ||
| var r, s big.Int | ||
| r.SetBytes(sig.R[:]) | ||
| s.SetBytes(sig.S[:]) | ||
| hash := ecdsa.HashToInt(msg) | ||
| var expected frontend.Variable | ||
| if verified { | ||
| expected = 1 | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| circuit := p256verifyCircuit{} | ||
| witness := p256verifyCircuit{ | ||
| MsgHash: emulated.ValueOf[emulated.P256Fr](hash), | ||
| R: emulated.ValueOf[emulated.P256Fr](r), | ||
| S: emulated.ValueOf[emulated.P256Fr](s), | ||
| Qx: emulated.ValueOf[emulated.P256Fp](pk.A.X), | ||
| Qy: emulated.ValueOf[emulated.P256Fp](pk.A.Y), | ||
| Expected: expected, | ||
| } | ||
| err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) | ||
| assert.NoError(err) | ||
| } | ||
|
|
||
| func TestP256VerifyCircuitWithEIPVectors(t *testing.T) { | ||
| assert := test.NewAssert(t) | ||
| data, err := ioutil.ReadFile("test_vectors/p256verify_vectors_clean.json") | ||
| if err != nil { | ||
| t.Fatalf("read vectors.json: %v", err) | ||
| } | ||
|
|
||
| var vecs []vector | ||
| if err := json.Unmarshal(data, &vecs); err != nil { | ||
| t.Fatalf("unmarshal: %v", err) | ||
| } | ||
| for i, v := range vecs { | ||
| h, r, s, qx, qy := splitInput160(v.Input) | ||
| verified := expectedBool(v.Expected) | ||
| expected := frontend.Variable(0) | ||
| if verified { | ||
| expected = 1 | ||
| } | ||
| witness := p256verifyCircuit{ | ||
| MsgHash: emulated.ValueOf[emulated.P256Fr](*h), | ||
| R: emulated.ValueOf[emulated.P256Fr](*r), | ||
| S: emulated.ValueOf[emulated.P256Fr](*s), | ||
| Qx: emulated.ValueOf[emulated.P256Fp](*qx), | ||
| Qy: emulated.ValueOf[emulated.P256Fp](*qy), | ||
| Expected: expected, | ||
| } | ||
|
|
||
| circuit := p256verifyCircuit{} | ||
|
|
||
| t.Run(fmt.Sprintf("vector_%03d_%s", i, v.Name), func(t *testing.T) { | ||
| err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) | ||
| assert.NoError(err) | ||
| }) | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| // --- utils | ||
| type vector struct { | ||
| Name string `json:"Name,omitempty"` | ||
| Input string `json:"Input"` | ||
| Expected string `json:"Expected"` | ||
| } | ||
|
|
||
| func splitInput160(hexInput string) (h, r, s, qx, qy *big.Int) { | ||
| raw, err := hex.DecodeString(hexInput) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| if len(raw) != 160 { | ||
| return nil, nil, nil, nil, nil | ||
ivokub marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| h = new(big.Int).SetBytes(raw[0:32]) | ||
| r = new(big.Int).SetBytes(raw[32:64]) | ||
| s = new(big.Int).SetBytes(raw[64:96]) | ||
| qx = new(big.Int).SetBytes(raw[96:128]) | ||
| qy = new(big.Int).SetBytes(raw[128:160]) | ||
| return | ||
| } | ||
|
|
||
| func expectedBool(s string) bool { | ||
| raw, err := hex.DecodeString(s) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| one := make([]byte, 32) | ||
| one[31] = 1 | ||
| return bytes.Equal(raw, one) | ||
| } | ||
Oops, something went wrong.
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.