return bytes.Clone(priv[:SeedSize])
}
-// Sign signs the given message with priv.
-// Ed25519 performs two passes over messages to be signed and therefore cannot
-// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
-// indicate the message hasn't been hashed. This can be achieved by passing
-// crypto.Hash(0) as the value for opts.
+// Sign signs the given message with priv. rand is ignored. If opts.HashFunc()
+// is crypto.SHA512, the pre-hashed variant Ed25519ph is used and message is
+// expected to be a SHA-512 hash, otherwise opts.HashFunc() must be
+// crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two
+// passes over messages to be signed.
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
- if opts.HashFunc() != crypto.Hash(0) {
- return nil, errors.New("ed25519: cannot sign hashed message")
+ switch opts.HashFunc() {
+ case crypto.SHA512:
+ if l := len(message); l != sha512.Size {
+ return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
+ }
+ signature := make([]byte, SignatureSize)
+ sign(signature, priv, message, domPrefixPh)
+ return signature, nil
+ case crypto.Hash(0):
+ return Sign(priv, message), nil
+ default:
+ return nil, errors.New("ed25519: expected opts zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)")
}
+}
+
+// Options can be used with PrivateKey.Sign or VerifyWithOptions
+// to select Ed25519 variants.
+type Options struct {
+ // Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph.
+ Hash crypto.Hash
- return Sign(priv, message), nil
+ // TODO(filippo): add Context, a string of at most 255 bytes which when
+ // non-zero selects Ed25519ctx.
}
+func (o *Options) HashFunc() crypto.Hash { return o.Hash }
+
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
// Outline the function body so that the returned signature can be
// stack-allocated.
signature := make([]byte, SignatureSize)
- sign(signature, privateKey, message)
+ sign(signature, privateKey, message, domPrefixPure)
return signature
}
-func sign(signature, privateKey, message []byte) {
+// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph.
+// See RFC 8032, Section 2 and Section 5.1.
+const (
+ // domPrefixPure is empty for pure Ed25519.
+ domPrefixPure = ""
+ // domPrefixPh is dom2(phflag=1, context="") for Ed25519ph.
+ domPrefixPh = "SigEd25519 no Ed25519 collisions\x01\x00"
+)
+
+func sign(signature, privateKey, message []byte, domPrefix string) {
if l := len(privateKey); l != PrivateKeySize {
panic("ed25519: bad private key length: " + strconv.Itoa(l))
}
prefix := h[32:]
mh := sha512.New()
+ mh.Write([]byte(domPrefix))
mh.Write(prefix)
mh.Write(message)
messageDigest := make([]byte, 0, sha512.Size)
R := (&edwards25519.Point{}).ScalarBaseMult(r)
kh := sha512.New()
+ kh.Write([]byte(domPrefix))
kh.Write(R.Bytes())
kh.Write(publicKey)
kh.Write(message)
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
func Verify(publicKey PublicKey, message, sig []byte) bool {
+ return verify(publicKey, message, sig, domPrefixPure)
+}
+
+// VerifyWithOptions reports whether sig is a valid signature of message by
+// publicKey. A valid signature is indicated by returning a nil error.
+// If opts.HashFunc() is crypto.SHA512, the pre-hashed variant Ed25519ph is used
+// and message is expected to be a SHA-512 hash, otherwise opts.HashFunc() must
+// be crypto.Hash(0) and the message must not be hashed, as Ed25519 performs two
+// passes over messages to be signed.
+func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error {
+ switch opts.HashFunc() {
+ case crypto.SHA512:
+ if l := len(message); l != sha512.Size {
+ return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
+ }
+ if !verify(publicKey, message, sig, domPrefixPh) {
+ return errors.New("ed25519: invalid signature")
+ }
+ return nil
+ case crypto.Hash(0):
+ if !verify(publicKey, message, sig, domPrefixPure) {
+ return errors.New("ed25519: invalid signature")
+ }
+ return nil
+ default:
+ return errors.New("ed25519: expected opts zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)")
+ }
+}
+
+func verify(publicKey PublicKey, message, sig []byte, domPrefix string) bool {
if l := len(publicKey); l != PublicKeySize {
panic("ed25519: bad public key length: " + strconv.Itoa(l))
}
}
kh := sha512.New()
+ kh.Write([]byte(domPrefix))
kh.Write(sig[:32])
kh.Write(publicKey)
kh.Write(message)
"crypto"
"crypto/internal/boring"
"crypto/rand"
+ "crypto/sha512"
"encoding/hex"
"internal/testenv"
"os"
}
}
+func TestSignVerifyHashed(t *testing.T) {
+ // From RFC 8032, Section 7.3
+ key, _ := hex.DecodeString("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf")
+ expectedSig, _ := hex.DecodeString("98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406")
+ message, _ := hex.DecodeString("616263")
+
+ private := PrivateKey(key)
+ public := private.Public().(PublicKey)
+ hash := sha512.Sum512(message)
+ sig, err := private.Sign(nil, hash[:], crypto.SHA512)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(sig, expectedSig) {
+ t.Error("signature doesn't match test vector")
+ }
+ sig, err = private.Sign(nil, hash[:], &Options{Hash: crypto.SHA512})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(sig, expectedSig) {
+ t.Error("signature doesn't match test vector")
+ }
+ if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}); err != nil {
+ t.Errorf("valid signature rejected: %v", err)
+ }
+
+ wrongHash := sha512.Sum512([]byte("wrong message"))
+ if VerifyWithOptions(public, wrongHash[:], sig, &Options{Hash: crypto.SHA512}) == nil {
+ t.Errorf("signature of different message accepted")
+ }
+
+ sig[0] ^= 0xff
+ if VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}) == nil {
+ t.Errorf("invalid signature accepted")
+ }
+ sig[0] ^= 0xff
+ sig[SignatureSize-1] ^= 0xff
+ if VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}) == nil {
+ t.Errorf("invalid signature accepted")
+ }
+}
+
func TestCryptoSigner(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
t.Fatalf("error from Sign(): %s", err)
}
+ signature2, err := signer.Sign(zero, message, &Options{Hash: noHash})
+ if err != nil {
+ t.Fatalf("error from Sign(): %s", err)
+ }
+ if !bytes.Equal(signature, signature2) {
+ t.Errorf("signatures keys do not match")
+ }
+
if !Verify(public, message, signature) {
t.Errorf("Verify failed on signature from Sign()")
}