From bd9851b18438b39edfd6a23068d320ea6df8a61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 26 Jun 2025 16:56:22 +0200 Subject: [PATCH] ecdsa: avoid some big.Int conversion --- crypto/internal/asn1.go | 37 +++++++++++++++++++++++++++++++++++++ crypto/p256/public.go | 25 ++++++++++++++++--------- crypto/p384/public.go | 25 ++++++++++++++++--------- crypto/p521/public.go | 25 ++++++++++++++++--------- 4 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 crypto/internal/asn1.go diff --git a/crypto/internal/asn1.go b/crypto/internal/asn1.go new file mode 100644 index 0000000..7f52cc8 --- /dev/null +++ b/crypto/internal/asn1.go @@ -0,0 +1,37 @@ +package helpers + +import ( + "errors" + + "golang.org/x/crypto/cryptobyte" + "golang.org/x/crypto/cryptobyte/asn1" +) + +// Taken from crypto/ecdsa + +func EncodeSignatureToASN1(r, s []byte) ([]byte, error) { + var b cryptobyte.Builder + b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { + addASN1IntBytes(b, r) + addASN1IntBytes(b, s) + }) + return b.Bytes() +} + +// addASN1IntBytes encodes in ASN.1 a positive integer represented as +// a big-endian byte slice with zero or more leading zeroes. +func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) { + for len(bytes) > 0 && bytes[0] == 0 { + bytes = bytes[1:] + } + if len(bytes) == 0 { + b.SetError(errors.New("invalid integer")) + return + } + b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) { + if bytes[0]&0x80 != 0 { + c.AddUint8(0) + } + c.AddBytes(bytes) + }) +} diff --git a/crypto/p256/public.go b/crypto/p256/public.go index e9b894a..8e20e17 100644 --- a/crypto/p256/public.go +++ b/crypto/p256/public.go @@ -34,11 +34,17 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) { } // PublicKeyFromXY converts x and y coordinates into a PublicKey. -func PublicKeyFromXY(x, y *big.Int) (*PublicKey, error) { - if !elliptic.P256().IsOnCurve(x, y) { +func PublicKeyFromXY(x, y []byte) (*PublicKey, error) { + pub := &PublicKey{k: &ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }} + + if !elliptic.P256().IsOnCurve(pub.k.X, pub.k.Y) { return nil, fmt.Errorf("invalid P-256 public key") } - return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}}, nil + return pub, nil } // PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form @@ -128,13 +134,14 @@ func (p *PublicKey) VerifyBytes(message, signature []byte) bool { return false } - // Hash the message with SHA-256 - hash := sha256.Sum256(message) + // For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it. + // This means it's actually more efficient to encode the signature as ASN.1 here. + sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:]) + if err != nil { + return false + } - r := new(big.Int).SetBytes(signature[:SignatureBytesSize/2]) - s := new(big.Int).SetBytes(signature[SignatureBytesSize/2:]) - - return ecdsa.Verify(p.k, hash[:], r, s) + return p.VerifyASN1(message, sigAsn1) } func (p *PublicKey) VerifyASN1(message, signature []byte) bool { diff --git a/crypto/p384/public.go b/crypto/p384/public.go index 1909d7a..c9e984a 100644 --- a/crypto/p384/public.go +++ b/crypto/p384/public.go @@ -34,11 +34,17 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) { } // PublicKeyFromXY converts x and y coordinates into a PublicKey. -func PublicKeyFromXY(x, y *big.Int) (*PublicKey, error) { - if !elliptic.P384().IsOnCurve(x, y) { +func PublicKeyFromXY(x, y []byte) (*PublicKey, error) { + pub := &PublicKey{k: &ecdsa.PublicKey{ + Curve: elliptic.P384(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }} + + if !elliptic.P384().IsOnCurve(pub.k.X, pub.k.Y) { return nil, fmt.Errorf("invalid P-384 public key") } - return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P384(), X: x, Y: y}}, nil + return pub, nil } // PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form @@ -128,13 +134,14 @@ func (p *PublicKey) VerifyBytes(message, signature []byte) bool { return false } - // Hash the message with SHA-384 - hash := sha512.Sum384(message) + // For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it. + // This means it's actually more efficient to encode the signature as ASN.1 here. + sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:]) + if err != nil { + return false + } - r := new(big.Int).SetBytes(signature[:SignatureBytesSize/2]) - s := new(big.Int).SetBytes(signature[SignatureBytesSize/2:]) - - return ecdsa.Verify(p.k, hash[:], r, s) + return p.VerifyASN1(message, sigAsn1) } func (p *PublicKey) VerifyASN1(message, signature []byte) bool { diff --git a/crypto/p521/public.go b/crypto/p521/public.go index d4cad54..ac48c1c 100644 --- a/crypto/p521/public.go +++ b/crypto/p521/public.go @@ -34,11 +34,17 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) { } // PublicKeyFromXY converts x and y coordinates into a PublicKey. -func PublicKeyFromXY(x, y *big.Int) (*PublicKey, error) { - if !elliptic.P521().IsOnCurve(x, y) { +func PublicKeyFromXY(x, y []byte) (*PublicKey, error) { + pub := &PublicKey{k: &ecdsa.PublicKey{ + Curve: elliptic.P521(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }} + + if !elliptic.P521().IsOnCurve(pub.k.X, pub.k.Y) { return nil, fmt.Errorf("invalid P-521 public key") } - return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}}, nil + return pub, nil } // PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form @@ -128,13 +134,14 @@ func (p *PublicKey) VerifyBytes(message, signature []byte) bool { return false } - // Hash the message with SHA-512 - hash := sha512.Sum512(message) + // For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it. + // This means it's actually more efficient to encode the signature as ASN.1 here. + sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:]) + if err != nil { + return false + } - r := new(big.Int).SetBytes(signature[:SignatureBytesSize/2]) - s := new(big.Int).SetBytes(signature[SignatureBytesSize/2:]) - - return ecdsa.Verify(p.k, hash[:], r, s) + return p.VerifyASN1(message, sigAsn1) } func (p *PublicKey) VerifyASN1(message, signature []byte) bool {