Skip to content

Conversation

@yelhousni
Copy link
Contributor

@yelhousni yelhousni commented Nov 13, 2025

Description

This PR implements a circuit corresponding to https://eips.ethereum.org/EIPS/eip-7951 alongside test against Consensys/gnark-crypto#767 and Wycheproof test vectors (https://eips.ethereum.org/assets/eip-7951/test-vectors.json).

Needs Consensys/gnark-crypto#767 to be merged first.

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

Tests against gnark-crypto pass but against Wycheproof some edge cases fail because they are checked at the arithmetization level not the gnark circuit level. Currently, p256verify_vectors_clean.json contains some data that passes gnark circuit test and p256verify_vectors.json is the entire data.

How has this been benchmarked?

In a BN254 circuit:

  • SCS: 563 926
  • R1CS: 153 757

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • I did not modify files generated from templates
  • golangci-lint does not output errors locally
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Note

Adds a P-256 ECDSA verification circuit (EIP-7951) and comprehensive tests using gnark-crypto and Wycheproof vectors.

  • evmprecompiles:
    • std/evmprecompiles/256-p256verify.go: Introduces P256Verify circuit implementing EIP-7951 P-256 ECDSA verification using gnark (ecdsa.PublicKey.IsValid).
  • Tests:
    • std/evmprecompiles/256-p256verify_test.go: Adds circuit tests with generated keys and message, plus Wycheproof vector-driven tests; includes helpers for parsing 160-byte inputs and expected flags.
  • Test Vectors:
    • std/evmprecompiles/test_vectors/p256verify_vectors.json: Adds full Wycheproof/EIP vectors.
    • std/evmprecompiles/test_vectors/p256verify_vectors_clean.json: Adds reduced set of vectors that pass the circuit-level checks.

Written by Cursor Bugbot for commit 72ad80d. This will update automatically on new commits. Configure here.

@yelhousni yelhousni marked this pull request as draft November 13, 2025 00:47
@yelhousni yelhousni requested review from Copilot and ivokub November 14, 2025 16:26
@yelhousni yelhousni added the dep: linea Issues affecting Linea downstream label Nov 14, 2025
@yelhousni yelhousni added this to the v0.14.N milestone Nov 14, 2025
@yelhousni yelhousni marked this pull request as ready for review November 14, 2025 16:27
@yelhousni yelhousni changed the title feat: eip-7951 ecdsa p256 feat: EIP-7951 for ECDSA on P-256 curve Nov 14, 2025
Copilot finished reviewing on behalf of yelhousni November 14, 2025 16:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements the P256Verify precompile (EIP-7951) for ECDSA signature verification over the secp256r1 (P-256) elliptic curve. The implementation provides a gnark circuit for verifying ECDSA signatures at the EVM precompile address 0x100.

Key changes:

  • Adds P256Verify function implementing EIP-7951 using gnark's emulated P256 curve and ECDSA signature verification
  • Includes comprehensive test coverage with both basic functional tests and Wycheproof test vectors
  • Provides 60+ test vectors from the Wycheproof project covering edge cases and malleability scenarios

Reviewed Changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.

File Description
std/evmprecompiles/256-p256verify.go Core implementation of P256Verify precompile using emulated ECDSA signature verification
std/evmprecompiles/256-p256verify_test.go Test suite with basic circuit tests and EIP vector validation
std/evmprecompiles/test_vectors/p256verify_vectors_clean.json Wycheproof test vectors for comprehensive edge case coverage

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The io/ioutil package is deprecated since Go 1.16. Replace ioutil.ReadFile with os.ReadFile and update the import to use "os" instead of "io/ioutil".

Suggested change
"io/ioutil"

Copilot uses AI. Check for mistakes.
panic(err)
}
if len(raw) != 160 {
return nil, nil, nil, nil, nil
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function splitInput160 silently returns nil values when the input length is not 160 bytes. This could lead to nil pointer dereferences on line 97-101 where the returned values are dereferenced. Consider returning an error or panicking with a descriptive message if the length check fails.

Suggested change
return nil, nil, nil, nil, nil
panic(fmt.Sprintf("splitInput160: input length is %d bytes, expected 160", len(raw)))

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
// 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
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments reference "arithmetization" and "ECDATA" which are zkEVM-specific implementation details. For a general-purpose gnark circuit, these validations are not performed by external systems. The comments should clarify that when used outside a zkEVM context, the caller must ensure: (1) input length is 160 bytes, (2) 0 < r < n and 0 < s < n, (3) 0 ≤ qx < p and 0 ≤ qy < p, (4) (qx, qy) is on the P-256 curve, and (5) (qx, qy) ≠ (0,0). Alternatively, consider adding explicit validation within the circuit for general-purpose use.

Suggested change
// 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
// The following checks are performed by external systems in the zkEVM context (arithmetization/ECDATA).
// For general-purpose use outside zkEVM, the caller must ensure:
// 1. input length is 160 bytes,
// 2. 0 < r < n and 0 < s < n,
// 3. 0 ≤ qx < p and 0 ≤ qy < p,
// 4. (qx, qy) is a valid point on the P-256 curve,
// 5. (qx, qy) ≠ (0,0).

Copilot uses AI. Check for mistakes.
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)
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Assert Context Breaks Subtest Isolation

The assert helper is created with the outer test context but used inside t.Run subtests. When assert.NoError(err) fails, it reports to the parent test rather than the subtest, causing the entire test function to stop instead of just failing the specific subtest. This prevents other test vectors from running and makes it difficult to identify which specific vector failed. The assertion should use the subtest's t parameter instead.

Fix in Cursor Fix in Web

var expected frontend.Variable
if verified {
expected = 1
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Default nil causes incorrect values.

When verified is false, expected remains nil (the zero value of interface{}), not 0. This causes incorrect witness values when testing invalid signatures. The variable needs explicit initialization to 0 before the conditional assignment.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dep: linea Issues affecting Linea downstream

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants