ecdsa: avoid some big.Int conversion

This commit is contained in:
Michael Muré
2025-06-26 16:56:22 +02:00
parent 0e2ec97424
commit bd9851b184
4 changed files with 85 additions and 27 deletions

37
crypto/internal/asn1.go Normal file
View File

@@ -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)
})
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {