Skip to content

Fix P-521 ECDSA verification and add curve coverage tests#1956

Merged
mchain0 merged 4 commits intomainfrom
tejaswi/fix-ecdsa-verify
Apr 13, 2026
Merged

Fix P-521 ECDSA verification and add curve coverage tests#1956
mchain0 merged 4 commits intomainfrom
tejaswi/fix-ecdsa-verify

Conversation

@nadahalli
Copy link
Copy Markdown
Contributor

Summary

  • Fix hashForCurve matching "P-512" instead of "P-521", which made the P-521 code path unreachable
  • Fix verifyECDSASignature using hash length to size signature components; P-521 COSE signatures use 66-byte components (ceil(521/8)), not 64 (SHA-512 digest size). Component size is now derived from the curve order via curveKeySize
  • Add verify_test.go with sign/verify roundtrips for all four NIST curves (P-224, P-256, P-384, P-521), negative tests (wrong key, wrong payload, tampered signature, wrong length, DER encoding rejected), and a targeted regression test proving P-521 key size != hash size

hashForCurve matched "P-512" instead of "P-521", making the P-521 path
unreachable. verifyECDSASignature used hash length (64 for SHA-512) to
determine signature component size, but P-521 COSE signatures use 66-byte
components (ceil(521/8)). Derive component size from the curve order instead.

Add verify_test.go with sign/verify roundtrips for all four NIST curves,
rejection tests (wrong key, wrong payload, tampered sig, wrong length,
DER format), and an explicit test proving P-521 key size != hash size.
@nadahalli nadahalli requested a review from a team as a code owner April 2, 2026 11:48
Copilot AI review requested due to automatic review settings April 2, 2026 11:48
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

👋 nadahalli, thanks for creating this pull request!

To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team.

Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

📊 API Diff Results

No changes detected for module github.com/smartcontractkit/chainlink-common

View full report

Copy link
Copy Markdown
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 fixes ECDSA COSE signature verification for P-521 keys in the Nitro attestation verifier by correctly selecting the P-521 hash path and by sizing COSE signature components based on curve bit size (r||s component width), not hash digest length. It also adds a dedicated test suite to prevent regressions across supported NIST curves.

Changes:

  • Fix hashForCurve to match "P-521" (previously "P-512"), making the P-521 branch reachable.
  • Fix verifyECDSASignature to split COSE signatures using ceil(curveBitSize/8) component sizing (not len(hash)), rejecting incorrect signature lengths early.
  • Add verify_test.go with roundtrip and negative tests across P-224/P-256/P-384/P-521 plus a targeted P-521 regression.

Reviewed changes

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

File Description
pkg/teeattestation/nitro/verify.go Corrects curve/hash selection for P-521 and fixes COSE ECDSA signature component sizing based on curve bit size.
pkg/teeattestation/nitro/verify_test.go Adds comprehensive curve coverage and regression tests for COSE ECDSA verification, including P-521 sizing edge case.

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

Comment thread pkg/teeattestation/nitro/verify_test.go Outdated
r, s, err := ecdsa.Sign(rand.Reader, key, hash)
require.NoError(t, err)

keySize := (key.Curve.Params().BitSize + 7) / 8
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

coseSign re-computes the COSE component size with (key.Curve.Params().BitSize + 7) / 8, duplicating the production helper curveKeySize. To avoid divergence if sizing logic changes again, consider calling curveKeySize(&key.PublicKey) here (or factoring a shared helper used by both prod + tests).

Suggested change
keySize := (key.Curve.Params().BitSize + 7) / 8
keySize := curveKeySize(&key.PublicKey)

Copilot uses AI. Check for mistakes.
Comment thread pkg/teeattestation/nitro/verify_test.go Outdated
Comment on lines +229 to +236
// marshalDER produces a minimal ASN.1 DER encoding of an ECDSA signature.
func marshalDER(r, s *big.Int) []byte {
encodeInt := func(v *big.Int) []byte {
b := v.Bytes()
if len(b) > 0 && b[0]&0x80 != 0 {
b = append([]byte{0x00}, b...)
}
return append([]byte{0x02, byte(len(b))}, b...)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The test builds a DER-encoded signature via a custom marshalDER implementation. Since the Go stdlib already supports DER ECDSA signatures (e.g., ecdsa.SignASN1 / asn1.Marshal), using that would simplify the test and reduce the chance of subtle DER encoding bugs (length handling, INTEGER=0 edge cases, etc.).

Copilot uses AI. Check for mistakes.
Verify the ECDSA signature pipeline (CBOR parse, Sig_structure build,
verifyECDSASignature) against official test vectors from the COSE Working
Group Examples repository (normative suite for RFC 9052/9053):

- ES256: RFC 8152 Appendix C.2.1 (sign-pass-03)
- ES384: ecdsa-sig-02
- ES512: ecdsa-sig-03 (exercises the P-521 fix from previous commit)
- Tampered payload: sign-fail-02
- Modified protected header: sign-fail-06
- Wrong key: valid ES256 vector verified with P-384 key
Comment on lines +223 to +224
// curveKeySize returns the byte length of one ECDSA signature component
// (r or s) for the given curve: ceil(bitSize / 8). This is the correct
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

super nit: maybe do float division -> ciel -> cast to int in case this helper is ever required for other curves with odd bit sizes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

(n + 7) / 8 with integer division is the standard idiom for ceil(n / 8).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yeah nvm, stupid suggestion.

@mchain0 mchain0 added this pull request to the merge queue Apr 13, 2026
Merged via the queue into main with commit 8c59288 Apr 13, 2026
32 checks passed
@mchain0 mchain0 deleted the tejaswi/fix-ecdsa-verify branch April 13, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants