package ed25519
import (
"bytes"
"crypto"
"crypto/internal/edwards25519"
cryptorand "crypto/rand"
"crypto/sha512"
"crypto/subtle"
"errors"
"io"
"strconv"
)
const (
PublicKeySize = 32
PrivateKeySize = 64
SignatureSize = 64
SeedSize = 32
)
type PublicKey []byte
func (pub PublicKey ) Equal (x crypto .PublicKey ) bool {
xx , ok := x .(PublicKey )
if !ok {
return false
}
return subtle .ConstantTimeCompare (pub , xx ) == 1
}
type PrivateKey []byte
func (priv PrivateKey ) Public () crypto .PublicKey {
publicKey := make ([]byte , PublicKeySize )
copy (publicKey , priv [32 :])
return PublicKey (publicKey )
}
func (priv PrivateKey ) Equal (x crypto .PrivateKey ) bool {
xx , ok := x .(PrivateKey )
if !ok {
return false
}
return subtle .ConstantTimeCompare (priv , xx ) == 1
}
func (priv PrivateKey ) Seed () []byte {
return bytes .Clone (priv [:SeedSize ])
}
func (priv PrivateKey ) Sign (rand io .Reader , message []byte , opts crypto .SignerOpts ) (signature []byte , err error ) {
hash := opts .HashFunc ()
context := ""
if opts , ok := opts .(*Options ); ok {
context = opts .Context
}
switch {
case hash == crypto .SHA512 :
if l := len (message ); l != sha512 .Size {
return nil , errors .New ("ed25519: bad Ed25519ph message hash length: " + strconv .Itoa (l ))
}
if l := len (context ); l > 255 {
return nil , errors .New ("ed25519: bad Ed25519ph context length: " + strconv .Itoa (l ))
}
signature := make ([]byte , SignatureSize )
sign (signature , priv , message , domPrefixPh , context )
return signature , nil
case hash == crypto .Hash (0 ) && context != "" :
if l := len (context ); l > 255 {
return nil , errors .New ("ed25519: bad Ed25519ctx context length: " + strconv .Itoa (l ))
}
signature := make ([]byte , SignatureSize )
sign (signature , priv , message , domPrefixCtx , context )
return signature , nil
case hash == crypto .Hash (0 ):
return Sign (priv , message ), nil
default :
return nil , errors .New ("ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)" )
}
}
type Options struct {
Hash crypto .Hash
Context string
}
func (o *Options ) HashFunc () crypto .Hash { return o .Hash }
func GenerateKey (rand io .Reader ) (PublicKey , PrivateKey , error ) {
if rand == nil {
rand = cryptorand .Reader
}
seed := make ([]byte , SeedSize )
if _ , err := io .ReadFull (rand , seed ); err != nil {
return nil , nil , err
}
privateKey := NewKeyFromSeed (seed )
publicKey := make ([]byte , PublicKeySize )
copy (publicKey , privateKey [32 :])
return publicKey , privateKey , nil
}
func NewKeyFromSeed (seed []byte ) PrivateKey {
privateKey := make ([]byte , PrivateKeySize )
newKeyFromSeed (privateKey , seed )
return privateKey
}
func newKeyFromSeed (privateKey , seed []byte ) {
if l := len (seed ); l != SeedSize {
panic ("ed25519: bad seed length: " + strconv .Itoa (l ))
}
h := sha512 .Sum512 (seed )
s , err := edwards25519 .NewScalar ().SetBytesWithClamping (h [:32 ])
if err != nil {
panic ("ed25519: internal error: setting scalar failed" )
}
A := (&edwards25519 .Point {}).ScalarBaseMult (s )
publicKey := A .Bytes ()
copy (privateKey , seed )
copy (privateKey [32 :], publicKey )
}
func Sign (privateKey PrivateKey , message []byte ) []byte {
signature := make ([]byte , SignatureSize )
sign (signature , privateKey , message , domPrefixPure , "" )
return signature
}
const (
domPrefixPure = ""
domPrefixPh = "SigEd25519 no Ed25519 collisions\x01"
domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00"
)
func sign (signature , privateKey , message []byte , domPrefix , context string ) {
if l := len (privateKey ); l != PrivateKeySize {
panic ("ed25519: bad private key length: " + strconv .Itoa (l ))
}
seed , publicKey := privateKey [:SeedSize ], privateKey [SeedSize :]
h := sha512 .Sum512 (seed )
s , err := edwards25519 .NewScalar ().SetBytesWithClamping (h [:32 ])
if err != nil {
panic ("ed25519: internal error: setting scalar failed" )
}
prefix := h [32 :]
mh := sha512 .New ()
if domPrefix != domPrefixPure {
mh .Write ([]byte (domPrefix ))
mh .Write ([]byte {byte (len (context ))})
mh .Write ([]byte (context ))
}
mh .Write (prefix )
mh .Write (message )
messageDigest := make ([]byte , 0 , sha512 .Size )
messageDigest = mh .Sum (messageDigest )
r , err := edwards25519 .NewScalar ().SetUniformBytes (messageDigest )
if err != nil {
panic ("ed25519: internal error: setting scalar failed" )
}
R := (&edwards25519 .Point {}).ScalarBaseMult (r )
kh := sha512 .New ()
if domPrefix != domPrefixPure {
kh .Write ([]byte (domPrefix ))
kh .Write ([]byte {byte (len (context ))})
kh .Write ([]byte (context ))
}
kh .Write (R .Bytes ())
kh .Write (publicKey )
kh .Write (message )
hramDigest := make ([]byte , 0 , sha512 .Size )
hramDigest = kh .Sum (hramDigest )
k , err := edwards25519 .NewScalar ().SetUniformBytes (hramDigest )
if err != nil {
panic ("ed25519: internal error: setting scalar failed" )
}
S := edwards25519 .NewScalar ().MultiplyAdd (k , s , r )
copy (signature [:32 ], R .Bytes ())
copy (signature [32 :], S .Bytes ())
}
func Verify (publicKey PublicKey , message , sig []byte ) bool {
return verify (publicKey , message , sig , domPrefixPure , "" )
}
func VerifyWithOptions (publicKey PublicKey , message , sig []byte , opts *Options ) error {
switch {
case opts .Hash == crypto .SHA512 :
if l := len (message ); l != sha512 .Size {
return errors .New ("ed25519: bad Ed25519ph message hash length: " + strconv .Itoa (l ))
}
if l := len (opts .Context ); l > 255 {
return errors .New ("ed25519: bad Ed25519ph context length: " + strconv .Itoa (l ))
}
if !verify (publicKey , message , sig , domPrefixPh , opts .Context ) {
return errors .New ("ed25519: invalid signature" )
}
return nil
case opts .Hash == crypto .Hash (0 ) && opts .Context != "" :
if l := len (opts .Context ); l > 255 {
return errors .New ("ed25519: bad Ed25519ctx context length: " + strconv .Itoa (l ))
}
if !verify (publicKey , message , sig , domPrefixCtx , opts .Context ) {
return errors .New ("ed25519: invalid signature" )
}
return nil
case opts .Hash == crypto .Hash (0 ):
if !verify (publicKey , message , sig , domPrefixPure , "" ) {
return errors .New ("ed25519: invalid signature" )
}
return nil
default :
return errors .New ("ed25519: expected opts.Hash zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)" )
}
}
func verify (publicKey PublicKey , message , sig []byte , domPrefix , context string ) bool {
if l := len (publicKey ); l != PublicKeySize {
panic ("ed25519: bad public key length: " + strconv .Itoa (l ))
}
if len (sig ) != SignatureSize || sig [63 ]&224 != 0 {
return false
}
A , err := (&edwards25519 .Point {}).SetBytes (publicKey )
if err != nil {
return false
}
kh := sha512 .New ()
if domPrefix != domPrefixPure {
kh .Write ([]byte (domPrefix ))
kh .Write ([]byte {byte (len (context ))})
kh .Write ([]byte (context ))
}
kh .Write (sig [:32 ])
kh .Write (publicKey )
kh .Write (message )
hramDigest := make ([]byte , 0 , sha512 .Size )
hramDigest = kh .Sum (hramDigest )
k , err := edwards25519 .NewScalar ().SetUniformBytes (hramDigest )
if err != nil {
panic ("ed25519: internal error: setting scalar failed" )
}
S , err := edwards25519 .NewScalar ().SetCanonicalBytes (sig [32 :])
if err != nil {
return false
}
minusA := (&edwards25519 .Point {}).Negate (A )
R := (&edwards25519 .Point {}).VarTimeDoubleScalarBaseMult (k , minusA , S )
return bytes .Equal (sig [:32 ], R .Bytes ())
}
The pages are generated with Golds v0.6.7 . (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 @Go100and1 (reachable from the left QR code) to get the latest news of Golds .