diff --git a/x25519.go b/x25519.go index 8a5d2fa..ef3010a 100644 --- a/x25519.go +++ b/x25519.go @@ -6,46 +6,95 @@ import ( "crypto/rand" "io" + "github.com/agl/ed25519/extra25519" "golang.org/x/crypto/curve25519" ) // KeySize is the size of keys in bytes used in this package. const KeySize = 32 -// PublicKey is the type of Curve25519 public keys -type PublicKey []byte +type PublicKey [32]byte + +func (pk *PublicKey) String() string { return string(pk[:]) } // SecretKey is the type of Curve25519 secret keys -type SecretKey []byte - -// Public returns the PublicKey corresponding to the SecretKey. -func (k SecretKey) Public() PublicKey { - var sk, pk [KeySize]byte - copy(sk[:], k) - curve25519.ScalarBaseMult(&pk, &sk) - return pk[:] +type SecretKey struct { + sk [32]byte + pk *PublicKey + ur *[32]byte } -// GenerateKey generates a public/secret key pair using entropy from random, or crypto/rand.Reader +func NewSecretKey(sk []byte) *SecretKey { + k := new(SecretKey) + copy(k.sk[:], sk) + return k +} + +func (k *SecretKey) Bytes() []byte { return k.sk[:] } +func (k *SecretKey) String() string { return string(k.sk[:]) } + +// Public returns the PublicKey corresponding to the secret key. +func (k *SecretKey) Public() *PublicKey { + if k.pk == nil { + k.pk = new(PublicKey) + curve25519.ScalarBaseMult((*[32]byte)(k.pk), &k.sk) + } + return k.pk +} + +// Uniform returns the uniform representative of the public key corresponding to the secret key, or nil +// if the public key does not have a uniform representative. +func (k *SecretKey) Uniform() *[32]byte { + if k.ur == nil { + pk := new(PublicKey) + ur := new([32]byte) + if extra25519.ScalarBaseMult((*[32]byte)(pk), ur, &k.sk) { + k.pk = pk + k.ur = ur + } + } + return k.ur +} + +// Shared computes the shared secret between our secret key and their public key. +func (k *SecretKey) Shared(shared *[32]byte, theirPublic *PublicKey) { + curve25519.ScalarMult(shared, &k.sk, (*[32]byte)(theirPublic)) +} + +// SharedUniform computes the shared secret between our secret key and their public key's uniform representative. +func (k *SecretKey) SharedUniform(shared, theirRepresentative *[32]byte) { + pk := new([32]byte) + extra25519.RepresentativeToPublicKey(pk, theirRepresentative) + curve25519.ScalarMult(shared, &k.sk, pk) +} + +// GenerateKey generates a secret key using entropy from random, or crypto/rand.Reader // if random is nil. -func GenerateKey(random io.Reader) (PublicKey, SecretKey, error) { - var pk, sk [KeySize]byte +func GenerateKey(random io.Reader) (*SecretKey, error) { if random == nil { random = rand.Reader } - if _, err := io.ReadFull(random, sk[:]); err != nil { - return nil, nil, err - } - // NOTE: clamping is not necessary because curve25519.ScaleMult will do it anyway. - curve25519.ScalarBaseMult(&pk, &sk) - return pk[:], sk[:], nil + sk := new(SecretKey) + _, err := io.ReadFull(random, sk.sk[:]) + return sk, err } -// ComputeSecret computes the shared secret between our secret key and their public key. -func ComputeSecret(our SecretKey, their PublicKey) []byte { - var shared, sk, pk [KeySize]byte - copy(sk[:], our) - copy(pk[:], their) - curve25519.ScalarMult(&shared, &sk, &pk) - return shared[:] +// GenerateKeyUniform generates a secret key whose corresponding public key has a uniform representative +// using entropy from random, or crypto/rand.Reader if random is nil. +func GenerateKeyUniform(random io.Reader) (*SecretKey, error) { + if random == nil { + random = rand.Reader + } + sk := new(SecretKey) + for ok := false; !ok; ok = extra25519.ScalarBaseMult((*[32]byte)(sk.pk), sk.ur, &sk.sk) { + if _, err := io.ReadFull(random, sk.sk[:]); err != nil { + return nil, err + } + } + return sk, nil +} + +// RepresentativeToPublicKey converts a uniform representative to a curve25519 public key. +func RepresentativeToPublicKey(publicKey, representative *[32]byte) { + extra25519.RepresentativeToPublicKey(publicKey, representative) } diff --git a/x25519_test.go b/x25519_test.go index a64af2a..b3b8f13 100644 --- a/x25519_test.go +++ b/x25519_test.go @@ -16,48 +16,54 @@ var ( ) func TestGenerateKey(t *testing.T) { - ask, err := hex.DecodeString(aliceSK) + askhex, err := hex.DecodeString(aliceSK) if err != nil { t.Fatal(err) } - apk := SecretKey(ask).Public() + ask := NewSecretKey(askhex) + apk := ask.Public() if alicePK != hex.EncodeToString(apk[:]) { t.Fatal("public key failed") } - bsk, err := hex.DecodeString(bobSK) + bskhex, err := hex.DecodeString(bobSK) if err != nil { t.Fatal(err) } - bpk := SecretKey(bsk).Public() - if bobPK != hex.EncodeToString(bpk) { + bsk := NewSecretKey(bskhex) + bpk := bsk.Public() + if bobPK != hex.EncodeToString(bpk[:]) { t.Fatal("public key failed") } - s1 := hex.EncodeToString(ComputeSecret(ask, bpk)) - s2 := hex.EncodeToString(ComputeSecret(bsk, apk)) - if s1 != s2 { + s1 := new([32]byte) + s2 := new([32]byte) + ask.Shared(s1, bpk) + bsk.Shared(s2, apk) + if !bytes.Equal(s1[:], s2[:]) { t.Fatal("shared secret failed") } - if s1 != sharedSecret { + if hex.EncodeToString(s1[:]) != sharedSecret { t.Fatal("shared secret failed") } } func TestGenerateKey2(t *testing.T) { - ourPK, ourSK, _ := GenerateKey(nil) - theirPK, theirSK, _ := GenerateKey(nil) + ourSK, _ := GenerateKey(nil) + theirSK, _ := GenerateKey(nil) t.Logf("our secret = %0x", ourSK) - t.Logf("our public = %0x", ourPK) + t.Logf("our public = %0x", ourSK.Public()) t.Logf("their secret = %0x", theirSK) - t.Logf("their public = %0x", theirPK) + t.Logf("their public = %0x", theirSK.Public()) - s1 := ComputeSecret(ourSK, theirPK) - s2 := ComputeSecret(theirSK, ourPK) - if !bytes.Equal(s1, s2) { + s1 := new([32]byte) + s2 := new([32]byte) + ourSK.Shared(s1, theirSK.Public()) + theirSK.Shared(s2, ourSK.Public()) + if !bytes.Equal(s1[:], s2[:]) { t.Fatal("computed shared secrets differs") } t.Logf("shared secret = %0x", s1)