From ff10adac7e44eb7f66f7160be80231da103efaf7 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 1 Jun 2026 17:07:01 -0700 Subject: [PATCH] sign/{ed25519,ed448}: check if public key is identity. When parsing a public key or verifying a signature, check that the public key is not the identity element. --- sign/ed25519/ed25519.go | 2 +- sign/ed25519/ed25519_test.go | 29 +++++++++++++++++++++++++++++ sign/ed25519/point.go | 4 ++++ sign/ed25519/point_test.go | 8 ++++++++ sign/ed25519/signapi.go | 4 ++++ sign/ed448/ed448.go | 2 +- sign/ed448/ed448_test.go | 26 ++++++++++++++++++++++++++ sign/ed448/signapi.go | 5 +++++ sign/sign.go | 4 ++++ 9 files changed, 82 insertions(+), 2 deletions(-) diff --git a/sign/ed25519/ed25519.go b/sign/ed25519/ed25519.go index 2c73c26fb..6fcd105e8 100644 --- a/sign/ed25519/ed25519.go +++ b/sign/ed25519/ed25519.go @@ -327,7 +327,7 @@ func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool } var P pointR1 - if ok := P.FromBytes(public); !ok { + if ok := P.FromBytes(public); !ok || P.IsIdentity() { return false } diff --git a/sign/ed25519/ed25519_test.go b/sign/ed25519/ed25519_test.go index 13977873a..1eeecbb9a 100644 --- a/sign/ed25519/ed25519_test.go +++ b/sign/ed25519/ed25519_test.go @@ -3,6 +3,7 @@ package ed25519_test import ( "testing" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/ed25519" ) @@ -39,6 +40,34 @@ func TestMalleability(t *testing.T) { } } +func TestIdentityPublicKey(t *testing.T) { + // Identity public key encoding: y=1, x=0, sign bit of x is 0. + identityPub := make([]byte, ed25519.PublicKeySize) + identityPub[0] = 0x01 + + // Signature with R=identity, S=0. + identitySig := make([]byte, ed25519.SignatureSize) + identitySig[0] = 0x01 + + msg := []byte("any message") + + if ed25519.Verify(identityPub, msg, identitySig) { + t.Error("identity public key accepted by Verify") + } + if ed25519.VerifyPh(identityPub, msg, identitySig, "") { + t.Error("identity public key accepted by VerifyPh") + } + if ed25519.VerifyWithCtx(identityPub, msg, identitySig, "ctx") { + t.Error("identity public key accepted by VerifyWithCtx") + } + + scheme := ed25519.Scheme() + _, err := scheme.UnmarshalBinaryPublicKey(identityPub) + if err != sign.ErrInvalidPublicKey { + t.Errorf("UnmarshalBinaryPublicKey returned %v, want %v", err, sign.ErrInvalidPublicKey) + } +} + func TestPublic(t *testing.T) { var zero zeroReader pub, priv, err := ed25519.GenerateKey(zero) diff --git a/sign/ed25519/point.go b/sign/ed25519/point.go index d1c3b146b..4c3f7f2d2 100644 --- a/sign/ed25519/point.go +++ b/sign/ed25519/point.go @@ -24,6 +24,10 @@ func (P *pointR1) SetIdentity() { P.tb = fp.Elt{} } +func (P *pointR1) IsIdentity() bool { + return fp.IsZero(&P.x) && !fp.IsZero(&P.y) && !fp.IsZero(&P.z) && P.y == P.z +} + func (P *pointR1) toAffine() { fp.Inv(&P.z, &P.z) fp.Mul(&P.x, &P.x, &P.z) diff --git a/sign/ed25519/point_test.go b/sign/ed25519/point_test.go index c5da133e6..e2b51b54c 100644 --- a/sign/ed25519/point_test.go +++ b/sign/ed25519/point_test.go @@ -29,6 +29,14 @@ func TestPoint(t *testing.T) { test.CheckOk(!invalid.isEqual(&invalid), "invalid point shouldn't match anything", t) }) + t.Run("isIdentity", func(t *testing.T) { + var P pointR1 + P.SetIdentity() + test.CheckOk(P.IsIdentity(), "SetIdentity should produce identity", t) + randomPoint(&P) + test.CheckOk(!P.IsIdentity(), "random point should not be identity", t) + }) + t.Run("add", func(t *testing.T) { var P pointR1 var Q pointR1 diff --git a/sign/ed25519/signapi.go b/sign/ed25519/signapi.go index e4520f520..c77c7615e 100644 --- a/sign/ed25519/signapi.go +++ b/sign/ed25519/signapi.go @@ -74,6 +74,10 @@ func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { } pub := make(PublicKey, PublicKeySize) copy(pub, buf[:PublicKeySize]) + var P pointR1 + if ok := P.FromBytes(pub); !ok || P.IsIdentity() { + return nil, sign.ErrInvalidPublicKey + } return pub, nil } diff --git a/sign/ed448/ed448.go b/sign/ed448/ed448.go index c368b181b..f409f3ff7 100644 --- a/sign/ed448/ed448.go +++ b/sign/ed448/ed448.go @@ -300,7 +300,7 @@ func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool } P, err := goldilocks.FromBytes(public) - if err != nil { + if err != nil || P.IsIdentity() { return false } diff --git a/sign/ed448/ed448_test.go b/sign/ed448/ed448_test.go index 91349609d..01b502fc0 100644 --- a/sign/ed448/ed448_test.go +++ b/sign/ed448/ed448_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/sign" "github.com/cloudflare/circl/sign/ed448" ) @@ -43,6 +44,31 @@ func TestEqual(t *testing.T) { } } +func TestIdentityPublicKey(t *testing.T) { + // Identity public key encoding: y=1, x=0, sign bit of x is 0. + identityPub := make([]byte, ed448.PublicKeySize) + identityPub[0] = 0x01 + + // Signature with R=identity, S=0. + identitySig := make([]byte, ed448.SignatureSize) + identitySig[0] = 0x01 + + msg := []byte("any message") + + if ed448.Verify(identityPub, msg, identitySig, "") { + t.Error("identity public key accepted by Verify") + } + if ed448.VerifyPh(identityPub, msg, identitySig, "") { + t.Error("identity public key accepted by VerifyPh") + } + + scheme := ed448.Scheme() + _, err := scheme.UnmarshalBinaryPublicKey(identityPub) + if err != sign.ErrInvalidPublicKey { + t.Errorf("UnmarshalBinaryPublicKey returned %v, want %v", err, sign.ErrInvalidPublicKey) + } +} + func TestWrongPublicKey(t *testing.T) { wrongPublicKeys := [...][ed448.PublicKeySize]byte{ { // y = p diff --git a/sign/ed448/signapi.go b/sign/ed448/signapi.go index 22da8bc0a..d0fa5a01e 100644 --- a/sign/ed448/signapi.go +++ b/sign/ed448/signapi.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/asn1" + "github.com/cloudflare/circl/ecc/goldilocks" "github.com/cloudflare/circl/sign" ) @@ -74,6 +75,10 @@ func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { } pub := make(PublicKey, PublicKeySize) copy(pub, buf[:PublicKeySize]) + P, err := goldilocks.FromBytes(pub) + if err != nil || P.IsIdentity() { + return nil, sign.ErrInvalidPublicKey + } return pub, nil } diff --git a/sign/sign.go b/sign/sign.go index 1247f1b62..9e6cac41e 100644 --- a/sign/sign.go +++ b/sign/sign.go @@ -110,6 +110,10 @@ var ( // the wrong size. ErrPrivKeySize = errors.New("wrong size for private key") + // ErrInvalidPublicKey is the error used if the provided public key is + // invalid (e.g., it is the identity point). + ErrInvalidPublicKey = errors.New("invalid public key") + // ErrContextNotSupported is the error used if a context is not // supported. ErrContextNotSupported = errors.New("context not supported")