ed25519: actually be compatible with RFC 8032

Most implementations, including this one, consider the private key to be
the concatenation of the initial 32-byte seed and the public key.
However the RFC 8032 formulation considers the "private key" to just be
the seed, which, in turn, means the upcoming draft-ietf-curdle-pkix
specification for embedding Ed25519 into PKCS#8 only stores the seed.

Exporting ed25519.PrivateKey to the seed is easy: key[:32]. Importing
the seed to ed25519.PrivateKey is not currently possible because the
logic is tied up in ed25519.GenerateKey. Split out
ed25519.NewKeyFromSeed for this, as well as an ed25519.PrivateKey.Seed
accessor to keep the abstraction consistent.

Change-Id: I4068eaf2073009dff3d84224aa145b56b59a5854
Reviewed-on: https://go-review.googlesource.com/115297
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/ed25519/ed25519.go b/ed25519/ed25519.go
index a57771a..d6f683b 100644
--- a/ed25519/ed25519.go
+++ b/ed25519/ed25519.go
@@ -6,7 +6,10 @@
 // https://ed25519.cr.yp.to/.
 //
 // These functions are also compatible with the “Ed25519” function defined in
-// RFC 8032.
+// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
+// representation includes a public key suffix to make multiple signing
+// operations with the same key more efficient. This package refers to the RFC
+// 8032 private key as the “seed”.
 package ed25519
 
 // This code is a port of the public domain, “ref10” implementation of ed25519
@@ -31,6 +34,8 @@
 	PrivateKeySize = 64
 	// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
 	SignatureSize = 64
+	// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
+	SeedSize = 32
 )
 
 // PublicKey is the type of Ed25519 public keys.
@@ -46,6 +51,15 @@
 	return PublicKey(publicKey)
 }
 
+// Seed returns the private key seed corresponding to priv. It is provided for
+// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
+// in this package.
+func (priv PrivateKey) Seed() []byte {
+	seed := make([]byte, SeedSize)
+	copy(seed, priv[:32])
+	return seed
+}
+
 // 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
@@ -61,19 +75,33 @@
 
 // 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 PublicKey, privateKey PrivateKey, err error) {
+func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
 	if rand == nil {
 		rand = cryptorand.Reader
 	}
 
-	privateKey = make([]byte, PrivateKeySize)
-	publicKey = make([]byte, PublicKeySize)
-	_, err = io.ReadFull(rand, privateKey[:32])
-	if err != nil {
+	seed := make([]byte, SeedSize)
+	if _, err := io.ReadFull(rand, seed); err != nil {
 		return nil, nil, err
 	}
 
-	digest := sha512.Sum512(privateKey[:32])
+	privateKey := NewKeyFromSeed(seed)
+	publicKey := make([]byte, PublicKeySize)
+	copy(publicKey, privateKey[32:])
+
+	return publicKey, privateKey, nil
+}
+
+// NewKeyFromSeed calculates a private key from a seed. It will panic if
+// len(seed) is not SeedSize. This function is provided for interoperability
+// with RFC 8032. RFC 8032's private keys correspond to seeds in this
+// package.
+func NewKeyFromSeed(seed []byte) PrivateKey {
+	if l := len(seed); l != SeedSize {
+		panic("ed25519: bad seed length: " + strconv.Itoa(l))
+	}
+
+	digest := sha512.Sum512(seed)
 	digest[0] &= 248
 	digest[31] &= 127
 	digest[31] |= 64
@@ -85,10 +113,11 @@
 	var publicKeyBytes [32]byte
 	A.ToBytes(&publicKeyBytes)
 
+	privateKey := make([]byte, PrivateKeySize)
+	copy(privateKey, seed)
 	copy(privateKey[32:], publicKeyBytes[:])
-	copy(publicKey, publicKeyBytes[:])
 
-	return publicKey, privateKey, nil
+	return privateKey
 }
 
 // Sign signs the message with privateKey and returns a signature. It will
diff --git a/ed25519/ed25519_test.go b/ed25519/ed25519_test.go
index 5f946e9..8094603 100644
--- a/ed25519/ed25519_test.go
+++ b/ed25519/ed25519_test.go
@@ -139,6 +139,19 @@
 		if !Verify(pubKey, msg, sig2) {
 			t.Errorf("signature failed to verify on line %d", lineNo)
 		}
+
+		priv2 := NewKeyFromSeed(priv[:32])
+		if !bytes.Equal(priv[:], priv2) {
+			t.Errorf("recreating key pair gave different private key on line %d: %x vs %x", lineNo, priv[:], priv2)
+		}
+
+		if pubKey2 := priv2.Public().(PublicKey); !bytes.Equal(pubKey, pubKey2) {
+			t.Errorf("recreating key pair gave different public key on line %d: %x vs %x", lineNo, pubKey, pubKey2)
+		}
+
+		if seed := priv2.Seed(); !bytes.Equal(priv[:32], seed) {
+			t.Errorf("recreating key pair gave different seed on line %d: %x vs %x", lineNo, priv[:32], seed)
+		}
 	}
 
 	if err := scanner.Err(); err != nil {