package ecdsa
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/elliptic"
"crypto/internal/bigmod"
"crypto/internal/boring"
"crypto/internal/boring/bbig"
"crypto/internal/nistec"
"crypto/internal/randutil"
"crypto/sha512"
"crypto/subtle"
"errors"
"io"
"math/big"
"sync"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
type PublicKey struct {
elliptic .Curve
X, Y *big .Int
}
func (k *PublicKey ) ECDH () (*ecdh .PublicKey , error ) {
c := curveToECDH (k .Curve )
if c == nil {
return nil , errors .New ("ecdsa: unsupported curve by crypto/ecdh" )
}
if !k .Curve .IsOnCurve (k .X , k .Y ) {
return nil , errors .New ("ecdsa: invalid public key" )
}
return c .NewPublicKey (elliptic .Marshal (k .Curve , k .X , k .Y ))
}
func (pub *PublicKey ) Equal (x crypto .PublicKey ) bool {
xx , ok := x .(*PublicKey )
if !ok {
return false
}
return bigIntEqual (pub .X , xx .X ) && bigIntEqual (pub .Y , xx .Y ) &&
pub .Curve == xx .Curve
}
type PrivateKey struct {
PublicKey
D *big .Int
}
func (k *PrivateKey ) ECDH () (*ecdh .PrivateKey , error ) {
c := curveToECDH (k .Curve )
if c == nil {
return nil , errors .New ("ecdsa: unsupported curve by crypto/ecdh" )
}
size := (k .Curve .Params ().N .BitLen () + 7 ) / 8
if k .D .BitLen () > size *8 {
return nil , errors .New ("ecdsa: invalid private key" )
}
return c .NewPrivateKey (k .D .FillBytes (make ([]byte , size )))
}
func curveToECDH(c elliptic .Curve ) ecdh .Curve {
switch c {
case elliptic .P256 ():
return ecdh .P256 ()
case elliptic .P384 ():
return ecdh .P384 ()
case elliptic .P521 ():
return ecdh .P521 ()
default :
return nil
}
}
func (priv *PrivateKey ) Public () crypto .PublicKey {
return &priv .PublicKey
}
func (priv *PrivateKey ) Equal (x crypto .PrivateKey ) bool {
xx , ok := x .(*PrivateKey )
if !ok {
return false
}
return priv .PublicKey .Equal (&xx .PublicKey ) && bigIntEqual (priv .D , xx .D )
}
func bigIntEqual(a , b *big .Int ) bool {
return subtle .ConstantTimeCompare (a .Bytes (), b .Bytes ()) == 1
}
func (priv *PrivateKey ) Sign (rand io .Reader , digest []byte , opts crypto .SignerOpts ) ([]byte , error ) {
return SignASN1 (rand , priv , digest )
}
func GenerateKey (c elliptic .Curve , rand io .Reader ) (*PrivateKey , error ) {
randutil .MaybeReadByte (rand )
if boring .Enabled && rand == boring .RandReader {
x , y , d , err := boring .GenerateKeyECDSA (c .Params ().Name )
if err != nil {
return nil , err
}
return &PrivateKey {PublicKey : PublicKey {Curve : c , X : bbig .Dec (x ), Y : bbig .Dec (y )}, D : bbig .Dec (d )}, nil
}
boring .UnreachableExceptTests ()
switch c .Params () {
case elliptic .P224 ().Params ():
return generateNISTEC (p224 (), rand )
case elliptic .P256 ().Params ():
return generateNISTEC (p256 (), rand )
case elliptic .P384 ().Params ():
return generateNISTEC (p384 (), rand )
case elliptic .P521 ().Params ():
return generateNISTEC (p521 (), rand )
default :
return generateLegacy (c , rand )
}
}
func generateNISTEC[Point nistPoint [Point ]](c *nistCurve [Point ], rand io .Reader ) (*PrivateKey , error ) {
k , Q , err := randomPoint (c , rand )
if err != nil {
return nil , err
}
priv := new (PrivateKey )
priv .PublicKey .Curve = c .curve
priv .D = new (big .Int ).SetBytes (k .Bytes (c .N ))
priv .PublicKey .X , priv .PublicKey .Y , err = c .pointToAffine (Q )
if err != nil {
return nil , err
}
return priv , nil
}
func randomPoint[Point nistPoint [Point ]](c *nistCurve [Point ], rand io .Reader ) (k *bigmod .Nat , p Point , err error ) {
k = bigmod .NewNat ()
for {
b := make ([]byte , c .N .Size ())
if _, err = io .ReadFull (rand , b ); err != nil {
return
}
if excess := len (b )*8 - c .N .BitLen (); excess > 0 {
if excess != 0 && c .curve .Params ().Name != "P-521" {
panic ("ecdsa: internal error: unexpectedly masking off bits" )
}
b [0 ] >>= excess
}
if _, err = k .SetBytes (b , c .N ); err == nil && k .IsZero () == 0 {
break
}
if testingOnlyRejectionSamplingLooped != nil {
testingOnlyRejectionSamplingLooped ()
}
}
p , err = c .newPoint ().ScalarBaseMult (k .Bytes (c .N ))
return
}
var testingOnlyRejectionSamplingLooped func ()
var errNoAsm = errors .New ("no assembly implementation available" )
func SignASN1 (rand io .Reader , priv *PrivateKey , hash []byte ) ([]byte , error ) {
randutil .MaybeReadByte (rand )
if boring .Enabled && rand == boring .RandReader {
b , err := boringPrivateKey (priv )
if err != nil {
return nil , err
}
return boring .SignMarshalECDSA (b , hash )
}
boring .UnreachableExceptTests ()
csprng , err := mixedCSPRNG (rand , priv , hash )
if err != nil {
return nil , err
}
if sig , err := signAsm (priv , csprng , hash ); err != errNoAsm {
return sig , err
}
switch priv .Curve .Params () {
case elliptic .P224 ().Params ():
return signNISTEC (p224 (), priv , csprng , hash )
case elliptic .P256 ().Params ():
return signNISTEC (p256 (), priv , csprng , hash )
case elliptic .P384 ().Params ():
return signNISTEC (p384 (), priv , csprng , hash )
case elliptic .P521 ().Params ():
return signNISTEC (p521 (), priv , csprng , hash )
default :
return signLegacy (priv , csprng , hash )
}
}
func signNISTEC[Point nistPoint [Point ]](c *nistCurve [Point ], priv *PrivateKey , csprng io .Reader , hash []byte ) (sig []byte , err error ) {
k , R , err := randomPoint (c , csprng )
if err != nil {
return nil , err
}
kInv := bigmod .NewNat ()
inverse (c , kInv , k )
Rx , err := R .BytesX ()
if err != nil {
return nil , err
}
r , err := bigmod .NewNat ().SetOverflowingBytes (Rx , c .N )
if err != nil {
return nil , err
}
if r .IsZero () == 1 {
return nil , errors .New ("ecdsa: internal error: r is zero" )
}
e := bigmod .NewNat ()
hashToNat (c , e , hash )
s , err := bigmod .NewNat ().SetBytes (priv .D .Bytes (), c .N )
if err != nil {
return nil , err
}
s .Mul (r , c .N )
s .Add (e , c .N )
s .Mul (kInv , c .N )
if s .IsZero () == 1 {
return nil , errors .New ("ecdsa: internal error: s is zero" )
}
return encodeSignature (r .Bytes (c .N ), s .Bytes (c .N ))
}
func encodeSignature(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 ()
}
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 )
})
}
func inverse[Point nistPoint [Point ]](c *nistCurve [Point ], kInv , k *bigmod .Nat ) {
if c .curve .Params ().Name == "P-256" {
kBytes , err := nistec .P256OrdInverse (k .Bytes (c .N ))
if err == nil {
_ , err := kInv .SetBytes (kBytes , c .N )
if err != nil {
panic ("ecdsa: internal error: P256OrdInverse produced an invalid value" )
}
return
}
}
kInv .Exp (k , c .nMinus2 , c .N )
}
func hashToNat[Point nistPoint [Point ]](c *nistCurve [Point ], e *bigmod .Nat , hash []byte ) {
if size := c .N .Size (); len (hash ) >= size {
hash = hash [:size ]
if excess := len (hash )*8 - c .N .BitLen (); excess > 0 {
hash = bytes .Clone (hash )
for i := len (hash ) - 1 ; i >= 0 ; i -- {
hash [i ] >>= excess
if i > 0 {
hash [i ] |= hash [i -1 ] << (8 - excess )
}
}
}
}
_ , err := e .SetOverflowingBytes (hash , c .N )
if err != nil {
panic ("ecdsa: internal error: truncated hash is too long" )
}
}
func mixedCSPRNG(rand io .Reader , priv *PrivateKey , hash []byte ) (io .Reader , error ) {
entropy := make ([]byte , 32 )
if _ , err := io .ReadFull (rand , entropy ); err != nil {
return nil , err
}
md := sha512 .New ()
md .Write (priv .D .Bytes ())
md .Write (entropy )
md .Write (hash )
key := md .Sum (nil )[:32 ]
block , err := aes .NewCipher (key )
if err != nil {
return nil , err
}
const aesIV = "IV for ECDSA CTR"
return &cipher .StreamReader {
R : zeroReader ,
S : cipher .NewCTR (block , []byte (aesIV )),
}, nil
}
type zr struct {}
var zeroReader = zr {}
func (zr ) Read (dst []byte ) (n int , err error ) {
clear (dst )
return len (dst ), nil
}
func VerifyASN1 (pub *PublicKey , hash , sig []byte ) bool {
if boring .Enabled {
key , err := boringPublicKey (pub )
if err != nil {
return false
}
return boring .VerifyECDSA (key , hash , sig )
}
boring .UnreachableExceptTests ()
if err := verifyAsm (pub , hash , sig ); err != errNoAsm {
return err == nil
}
switch pub .Curve .Params () {
case elliptic .P224 ().Params ():
return verifyNISTEC (p224 (), pub , hash , sig )
case elliptic .P256 ().Params ():
return verifyNISTEC (p256 (), pub , hash , sig )
case elliptic .P384 ().Params ():
return verifyNISTEC (p384 (), pub , hash , sig )
case elliptic .P521 ().Params ():
return verifyNISTEC (p521 (), pub , hash , sig )
default :
return verifyLegacy (pub , hash , sig )
}
}
func verifyNISTEC[Point nistPoint [Point ]](c *nistCurve [Point ], pub *PublicKey , hash , sig []byte ) bool {
rBytes , sBytes , err := parseSignature (sig )
if err != nil {
return false
}
Q , err := c .pointFromAffine (pub .X , pub .Y )
if err != nil {
return false
}
r , err := bigmod .NewNat ().SetBytes (rBytes , c .N )
if err != nil || r .IsZero () == 1 {
return false
}
s , err := bigmod .NewNat ().SetBytes (sBytes , c .N )
if err != nil || s .IsZero () == 1 {
return false
}
e := bigmod .NewNat ()
hashToNat (c , e , hash )
w := bigmod .NewNat ()
inverse (c , w , s )
p1 , err := c .newPoint ().ScalarBaseMult (e .Mul (w , c .N ).Bytes (c .N ))
if err != nil {
return false
}
p2 , err := Q .ScalarMult (Q , w .Mul (r , c .N ).Bytes (c .N ))
if err != nil {
return false
}
Rx , err := p1 .Add (p1 , p2 ).BytesX ()
if err != nil {
return false
}
v , err := bigmod .NewNat ().SetOverflowingBytes (Rx , c .N )
if err != nil {
return false
}
return v .Equal (r ) == 1
}
func parseSignature(sig []byte ) (r , s []byte , err error ) {
var inner cryptobyte .String
input := cryptobyte .String (sig )
if !input .ReadASN1 (&inner , asn1 .SEQUENCE ) ||
!input .Empty () ||
!inner .ReadASN1Integer (&r ) ||
!inner .ReadASN1Integer (&s ) ||
!inner .Empty () {
return nil , nil , errors .New ("invalid ASN.1" )
}
return r , s , nil
}
type nistCurve[Point nistPoint [Point ]] struct {
newPoint func () Point
curve elliptic .Curve
N *bigmod .Modulus
nMinus2 []byte
}
type nistPoint[T any ] interface {
Bytes() []byte
BytesX() ([]byte , error )
SetBytes([]byte ) (T , error )
Add(T , T ) T
ScalarMult(T , []byte ) (T , error )
ScalarBaseMult([]byte ) (T , error )
}
func (curve *nistCurve [Point ]) pointFromAffine (x , y *big .Int ) (p Point , err error ) {
bitSize := curve .curve .Params ().BitSize
if x .Sign () < 0 || y .Sign () < 0 {
return p , errors .New ("negative coordinate" )
}
if x .BitLen () > bitSize || y .BitLen () > bitSize {
return p , errors .New ("overflowing coordinate" )
}
byteLen := (bitSize + 7 ) / 8
buf := make ([]byte , 1 +2 *byteLen )
buf [0 ] = 4
x .FillBytes (buf [1 : 1 +byteLen ])
y .FillBytes (buf [1 +byteLen : 1 +2 *byteLen ])
return curve .newPoint ().SetBytes (buf )
}
func (curve *nistCurve [Point ]) pointToAffine (p Point ) (x , y *big .Int , err error ) {
out := p .Bytes ()
if len (out ) == 1 && out [0 ] == 0 {
return nil , nil , errors .New ("ecdsa: public key point is the infinity" )
}
byteLen := (curve .curve .Params ().BitSize + 7 ) / 8
x = new (big .Int ).SetBytes (out [1 : 1 +byteLen ])
y = new (big .Int ).SetBytes (out [1 +byteLen :])
return x , y , nil
}
var p224Once sync .Once
var _p224 *nistCurve [*nistec .P224Point ]
func p224() *nistCurve [*nistec .P224Point ] {
p224Once .Do (func () {
_p224 = &nistCurve [*nistec .P224Point ]{
newPoint : func () *nistec .P224Point { return nistec .NewP224Point () },
}
precomputeParams (_p224 , elliptic .P224 ())
})
return _p224
}
var p256Once sync .Once
var _p256 *nistCurve [*nistec .P256Point ]
func p256() *nistCurve [*nistec .P256Point ] {
p256Once .Do (func () {
_p256 = &nistCurve [*nistec .P256Point ]{
newPoint : func () *nistec .P256Point { return nistec .NewP256Point () },
}
precomputeParams (_p256 , elliptic .P256 ())
})
return _p256
}
var p384Once sync .Once
var _p384 *nistCurve [*nistec .P384Point ]
func p384() *nistCurve [*nistec .P384Point ] {
p384Once .Do (func () {
_p384 = &nistCurve [*nistec .P384Point ]{
newPoint : func () *nistec .P384Point { return nistec .NewP384Point () },
}
precomputeParams (_p384 , elliptic .P384 ())
})
return _p384
}
var p521Once sync .Once
var _p521 *nistCurve [*nistec .P521Point ]
func p521() *nistCurve [*nistec .P521Point ] {
p521Once .Do (func () {
_p521 = &nistCurve [*nistec .P521Point ]{
newPoint : func () *nistec .P521Point { return nistec .NewP521Point () },
}
precomputeParams (_p521 , elliptic .P521 ())
})
return _p521
}
func precomputeParams[Point nistPoint [Point ]](c *nistCurve [Point ], curve elliptic .Curve ) {
params := curve .Params ()
c .curve = curve
var err error
c .N , err = bigmod .NewModulusFromBig (params .N )
if err != nil {
panic (err )
}
c .nMinus2 = new (big .Int ).Sub (params .N , big .NewInt (2 )).Bytes ()
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .