Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crypto/ecies/ecies.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b
if prv.PublicKey.Curve != pub.Curve {
return nil, ErrInvalidCurve
}
if pub.X == nil || pub.Y == nil || !pub.Curve.IsOnCurve(pub.X, pub.Y) {
return nil, ErrInvalidPublicKey
}
if skLen+macLen > MaxSharedKeyLength(pub) {
return nil, ErrSharedKeyTooBig
}
Expand Down
6 changes: 5 additions & 1 deletion crypto/secp256k1/curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func (BitCurve *BitCurve) Params() *elliptic.CurveParams {

// IsOnCurve returns true if the given (x,y) lies on the BitCurve.
func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
if x.Cmp(BitCurve.P) >= 0 || y.Cmp(BitCurve.P) >= 0 {
return false
}
Comment thread
Thegaram marked this conversation as resolved.

// y² = x³ + b
y2 := new(big.Int).Mul(y, y) //y²
y2.Mod(y2, BitCurve.P) //y²%P
Expand All @@ -105,7 +109,7 @@ func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
return x3.Cmp(y2) == 0
}

//TODO: double check if the function is okay
// TODO: double check if the function is okay
// affineFromJacobian reverses the Jacobian transform. See the comment at the
// top of the file.
func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
Expand Down
5 changes: 3 additions & 2 deletions crypto/secp256k1/ext.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, unsigned char *point,
ARG_CHECK(scalar != NULL);
(void)ctx;

secp256k1_fe_set_b32(&feX, point);
secp256k1_fe_set_b32(&feY, point+32);
if (!secp256k1_fe_set_b32(&feX, point) || !secp256k1_fe_set_b32(&feY, point+32)) {
return 0;
}
secp256k1_ge_set_xy(&ge, &feX, &feY);
secp256k1_scalar_set_b32(&s, scalar, &overflow);
if (overflow || secp256k1_scalar_is_zero(&s)) {
Expand Down
170 changes: 170 additions & 0 deletions p2p/rlpx/rlpx_oracle_poc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package rlpx

import (
"crypto/ecdsa"
"crypto/rand"
"math/big"
"testing"

"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/ecies"
)

// TestHandshakeECIESInvalidCurveOracle verifies that ECIES decryption rejects
// ciphertexts containing an invalid-curve ephemeral public key with
// ErrInvalidPublicKey, not a MAC error. A MAC error would indicate the ECDH
// succeeded with the invalid point, enabling an oracle attack to extract
// bits of the receiver's private key (CVE-2026-26315).
func TestHandshakeECIESInvalidCurveOracle(t *testing.T) {
// Generate receiver key (the node under attack).
receiverKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
receiverECIES := ecies.ImportECDSAPublic(&receiverKey.PublicKey)

// Create a valid ECIES ciphertext.
plaintext := []byte("hello handshake")
ct, err := ecies.Encrypt(rand.Reader, receiverECIES, plaintext, nil, nil)
if err != nil {
t.Fatal(err)
}

// The ciphertext starts with an uncompressed EC point (0x04 || X || Y),
// each coordinate is 32 bytes for secp256k1.
curve := crypto.S256()
byteLen := (curve.Params().BitSize + 7) / 8 // 32

// Tamper: replace the ephemeral public key with a point NOT on secp256k1.
// Use coordinates that satisfy y² != x³ + 7 (mod P).
invalidX := new(big.Int).SetInt64(1)
invalidY := new(big.Int).SetInt64(1)

// Sanity: confirm the point is NOT on the curve.
if curve.IsOnCurve(invalidX, invalidY) {
t.Fatal("expected (1,1) to be off the secp256k1 curve")
}

// Build the tampered ciphertext.
tampered := make([]byte, len(ct))
copy(tampered, ct)
tampered[0] = 0x04 // uncompressed point prefix
xBytes := invalidX.Bytes()
yBytes := invalidY.Bytes()
// Zero-pad and copy X.
copy(tampered[1+byteLen-len(xBytes):1+byteLen], xBytes)
// Zero the leading bytes.
for i := 1; i < 1+byteLen-len(xBytes); i++ {
tampered[i] = 0
}
// Zero-pad and copy Y.
copy(tampered[1+2*byteLen-len(yBytes):1+2*byteLen], yBytes)
for i := 1 + byteLen; i < 1+2*byteLen-len(yBytes); i++ {
tampered[i] = 0
}

// Decrypt with the tampered ciphertext.
receiverECIESPriv := ecies.ImportECDSA(receiverKey)
_, err = receiverECIESPriv.Decrypt(tampered, nil, nil)
if err == nil {
t.Fatal("expected decryption to fail with invalid-curve point")
}
if err != ecies.ErrInvalidPublicKey {
t.Fatalf("expected ErrInvalidPublicKey, got: %v", err)
}
}

// TestHandshakeECIESOutOfRangeCoordinates verifies that coordinates >= P are
// rejected by IsOnCurve, preventing a crash in the C secp256k1 library
// (CVE-2026-26314).
func TestHandshakeECIESOutOfRangeCoordinates(t *testing.T) {
curve := crypto.S256()
p := curve.Params().P

// A point on the curve.
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
t.Fatal(err)
}
if !curve.IsOnCurve(key.PublicKey.X, key.PublicKey.Y) {
t.Fatal("generated key not on curve")
}

// Shift X by +P: mathematically equivalent mod P, but should be rejected
// because the coordinate is out of the valid range [0, P).
outOfRangeX := new(big.Int).Add(key.PublicKey.X, p)
if curve.IsOnCurve(outOfRangeX, key.PublicKey.Y) {
t.Fatal("IsOnCurve should reject x >= P")
}

// Same for Y.
outOfRangeY := new(big.Int).Add(key.PublicKey.Y, p)
if curve.IsOnCurve(key.PublicKey.X, outOfRangeY) {
t.Fatal("IsOnCurve should reject y >= P")
}

// Negative coordinates.
if curve.IsOnCurve(big.NewInt(-1), key.PublicKey.Y) {
t.Fatal("IsOnCurve should reject negative x")
}
}
Comment thread
Thegaram marked this conversation as resolved.

// TestECIESGenerateSharedNilCoordinates verifies GenerateShared rejects a
// public key with nil coordinates.
func TestECIESGenerateSharedNilCoordinates(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
prv := ecies.ImportECDSA(key)

badPub := &ecies.PublicKey{
Curve: crypto.S256(),
}
_, err = prv.GenerateShared(badPub, 16, 16)
if err != ecies.ErrInvalidPublicKey {
t.Fatalf("expected ErrInvalidPublicKey for nil coords, got: %v", err)
}
}

// importPublicKeyForTest is a helper to convert public key bytes.
func importPublicKeyForTest(pubKeyBytes []byte) (*ecies.PublicKey, error) {
pub, err := crypto.UnmarshalPubkey(append([]byte{0x04}, pubKeyBytes...))
if err != nil {
return nil, err
}
return ecies.ImportECDSAPublic(pub), nil
}

// TestImportPublicKeyInvalidCurvePoint verifies that importPublicKey rejects
// points not on the curve via the underlying UnmarshalPubkey path.
func TestImportPublicKeyInvalidCurvePoint(t *testing.T) {
curve := crypto.S256()
byteLen := (curve.Params().BitSize + 7) / 8

// Build a 64-byte public key with (1, 1) — not on secp256k1.
pubBytes := make([]byte, 2*byteLen)
pubBytes[byteLen-1] = 1 // X = 1
pubBytes[2*byteLen-1] = 1 // Y = 1

_, err := importPublicKeyForTest(pubBytes)
if err == nil {
t.Fatal("expected importPublicKey to reject invalid curve point")
}
}
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 10 // Minor version component of the current release
VersionPatch = 5 // Patch version component of the current release
VersionPatch = 6 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

Expand Down
Loading