From a333d787a072b1bfeac77a50ec2a8ba31f04eb50 Mon Sep 17 00:00:00 2001 From: Ross Tannenbaum Date: Fri, 24 May 2024 12:18:11 -0700 Subject: [PATCH] chore(curve): clean curve implementation (#80) --- protocol/curve/curve25519/curve25519.go | 59 +++++++++++++++---------- protocol/curve/curve25519/util.go | 5 +-- protocol/curve/djb_private.go | 5 +-- protocol/curve/djb_public.go | 1 + protocol/curve/doc.go | 3 ++ protocol/curve/pair.go | 11 ++--- protocol/curve/private.go | 11 +---- protocol/go.mod | 4 +- protocol/go.sum | 4 +- 9 files changed, 52 insertions(+), 51 deletions(-) create mode 100644 protocol/curve/doc.go diff --git a/protocol/curve/curve25519/curve25519.go b/protocol/curve/curve25519/curve25519.go index d8e65e3..694b9ae 100644 --- a/protocol/curve/curve25519/curve25519.go +++ b/protocol/curve/curve25519/curve25519.go @@ -32,35 +32,31 @@ var ( // PrivateKey represents a Montgomery private key used for the XEdDSA scheme. type PrivateKey struct { - key []byte - scalarKey *edwards25519.Scalar - ecdhKey *ecdh.PrivateKey + privateKey []byte + publicKey []byte + scalarKey *edwards25519.Scalar + ecdhKey *ecdh.PrivateKey } -// GeneratePrivateKey generates a cryptographic random private key. -func GeneratePrivateKey(r io.Reader) (*PrivateKey, error) { - if r == nil { - r = rand.Reader +// GeneratePrivateKey generates a random private key. +// +// It is recommended to use a cryptographic random reader. +// If random is nil, then [crypto/rand.Reader] is used. +func GeneratePrivateKey(random io.Reader) (*PrivateKey, error) { + if random == nil { + random = rand.Reader } key := make([]byte, PrivateKeySize) - _, err := io.ReadFull(r, key) + _, err := io.ReadFull(random, key) if err != nil { return nil, err } - // See step 2 in https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5. - key[0] &= 248 - key[31] &= 127 - key[31] |= 64 - return NewPrivateKey(key) } // NewPrivateKey creates a new private key based on the given input. -// -// It is expected that the given key is already clamped based on -// https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5. func NewPrivateKey(key []byte) (*PrivateKey, error) { if len(key) != PrivateKeySize { return nil, perrors.ErrInvalidKeyLength(PrivateKeySize, len(key)) @@ -71,28 +67,40 @@ func NewPrivateKey(key []byte) (*PrivateKey, error) { return nil, err } + // No need to clamp here, as ECDH will take care of it. ecdhKey, err := ecdh.X25519().NewPrivateKey(key) if err != nil { return nil, err } + privateKey := make([]byte, PrivateKeySize) + copy(privateKey, key) + // Clamp the given private key. + // See step 2 in https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5. + privateKey[0] &= 248 + privateKey[31] &= 63 + privateKey[31] |= 64 + return &PrivateKey{ - key: key, - scalarKey: scalarKey, - ecdhKey: ecdhKey, + privateKey: privateKey, + publicKey: xtou(scalarKey), + scalarKey: scalarKey, + ecdhKey: ecdhKey, }, nil } // Bytes returns a copy of the private key. func (p *PrivateKey) Bytes() []byte { bytes := make([]byte, PrivateKeySize) - copy(bytes, p.key) + copy(bytes, p.privateKey) return bytes } // PublicKeyBytes returns the public key in the form of a Montgomery u-point. func (p *PrivateKey) PublicKeyBytes() []byte { - return xtou(p.key) + bytes := make([]byte, PublicKeySize) + copy(bytes, p.publicKey) + return bytes } // Agreement computes the ECDH shared key between the private key and @@ -110,9 +118,12 @@ func (p *PrivateKey) Agreement(key []byte) ([]byte, error) { return p.ecdhKey.ECDH(publicKey) } -// Sign calculates an XEdDSA signature using the X25519 private key directly. +// Sign calculates an XEdDSA signature using the X25519 private key, directly. // // The calculated signature is a valid ed25519 signature. +// +// It is recommended to use a cryptographic random reader. +// If random is nil, then [crypto/rand.Reader] is used. func (p *PrivateKey) Sign(random io.Reader, messages ...[]byte) ([]byte, error) { if random == nil { random = rand.Reader @@ -125,7 +136,7 @@ func (p *PrivateKey) Sign(random io.Reader, messages ...[]byte) ([]byte, error) } a := p.scalarKey.Bytes() - A := (new(edwards25519.Point)).ScalarBaseMult(p.scalarKey).Bytes() + A := new(edwards25519.Point).ScalarBaseMult(p.scalarKey).Bytes() digest := make([]byte, 0, sha512.Size) hash := sha512.New() @@ -142,7 +153,7 @@ func (p *PrivateKey) Sign(random io.Reader, messages ...[]byte) ([]byte, error) if err != nil { return nil, err } - R := (new(edwards25519.Point)).ScalarBaseMult(r).Bytes() + R := new(edwards25519.Point).ScalarBaseMult(r).Bytes() digest = digest[:0] hash.Reset() diff --git a/protocol/curve/curve25519/util.go b/protocol/curve/curve25519/util.go index e9dcd03..7b1c95e 100644 --- a/protocol/curve/curve25519/util.go +++ b/protocol/curve/curve25519/util.go @@ -13,9 +13,8 @@ var ( ) // xtou converts an Edwards x-point to a Montgomery u-point. -func xtou(edX []byte) []byte { - k, _ := edwards25519.NewScalar().SetBytes(edX) - return new(edwards25519.Point).ScalarBaseMult(k).BytesMontgomery() +func xtou(edX *edwards25519.Scalar) []byte { + return new(edwards25519.Point).ScalarBaseMult(edX).BytesMontgomery() } // utoy converts a Montgomery u-point to an Edwards y-point. diff --git a/protocol/curve/djb_private.go b/protocol/curve/djb_private.go index 77e5fe8..c9ff27e 100644 --- a/protocol/curve/djb_private.go +++ b/protocol/curve/djb_private.go @@ -16,7 +16,7 @@ type DJBPrivateKey struct { // GeneratePrivateKey generates a private key using the given random reader. // // It is recommended to use a cryptographic random reader. -// If random is `nil`, then crypto/rand.Reader is used. +// If random is nil, then [crypto/rand.Reader] is used. func GeneratePrivateKey(random io.Reader) (PrivateKey, error) { privateKey, err := curve25519.GeneratePrivateKey(random) if err != nil { @@ -29,9 +29,6 @@ func GeneratePrivateKey(random io.Reader) (PrivateKey, error) { } // newDJBPrivateKey returns a private key based on the given key bytes. -// -// It is expected that the given key is already clamped based on -// https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5. func newDJBPrivateKey(key []byte) (PrivateKey, error) { privateKey, err := curve25519.NewPrivateKey(key) if err != nil { diff --git a/protocol/curve/djb_public.go b/protocol/curve/djb_public.go index 90d25ab..9a61223 100644 --- a/protocol/curve/djb_public.go +++ b/protocol/curve/djb_public.go @@ -14,6 +14,7 @@ type DJBPublicKey struct { key []byte } +// newDJBPublicKey returns a public key based on the given key bytes. func newDJBPublicKey(key []byte) (*DJBPublicKey, error) { if len(key) != PublicKeySize { return nil, perrors.ErrInvalidKeyLength(PublicKeySize, len(key)) diff --git a/protocol/curve/doc.go b/protocol/curve/doc.go new file mode 100644 index 0000000..b1bd7e6 --- /dev/null +++ b/protocol/curve/doc.go @@ -0,0 +1,3 @@ +// Package curve implements elliptic curve cryptography +// functions used for the protocol. +package curve diff --git a/protocol/curve/pair.go b/protocol/curve/pair.go index c1b5083..45252ee 100644 --- a/protocol/curve/pair.go +++ b/protocol/curve/pair.go @@ -1,5 +1,3 @@ -// Package curve implements elliptic curve cryptography -// functions used for the protocol. package curve import "io" @@ -13,7 +11,7 @@ type KeyPair struct { // GenerateKeyPair returns a public/private key pair using the given reader. // // It is recommended to use a cryptographic random reader. -// If random is `nil`, then crypto/rand.Reader is used. +// If random is nil, then [crypto/rand.Reader] is used. func GenerateKeyPair(random io.Reader) (*KeyPair, error) { privateKey, err := GeneratePrivateKey(random) if err != nil { @@ -30,8 +28,8 @@ func GenerateKeyPair(random io.Reader) (*KeyPair, error) { // NewKeyPair returns a public/private key pair from the given pair. // -// The given pair is expected to represent a valid curve.PrivateKey and -// curve.PublicKey, respectively. +// The given pair is expected to represent a valid [curve.PrivateKey] and +// [curve.PublicKey], respectively. func NewKeyPair(privateKey, publicKey []byte) (*KeyPair, error) { private, err := NewPrivateKey(privateKey) if err != nil { @@ -66,6 +64,9 @@ func (k *KeyPair) Agreement(key PublicKey) ([]byte, error) { // Sign calculates the digital signature of the messages using // the key pair's private key. +// +// It is recommended to use a cryptographic random reader. +// If random is nil, then [crypto/rand.Reader] is used. func (k *KeyPair) Sign(random io.Reader, messages ...[]byte) ([]byte, error) { return k.privateKey.Sign(random, messages...) } diff --git a/protocol/curve/private.go b/protocol/curve/private.go index f5e1b37..b509072 100644 --- a/protocol/curve/private.go +++ b/protocol/curve/private.go @@ -28,14 +28,5 @@ func NewPrivateKey(key []byte) (PrivateKey, error) { return nil, perrors.ErrInvalidKeyLength(PrivateKeySize, len(key)) } - djbKey := make([]byte, len(key)) - copy(djbKey, key) - - // Clamp the given key. - // See step 2 in https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5. - djbKey[0] &= 248 - djbKey[31] &= 63 - djbKey[31] |= 64 - - return newDJBPrivateKey(djbKey) + return newDJBPrivateKey(key) } diff --git a/protocol/go.mod b/protocol/go.mod index 8834e37..c2ec30b 100644 --- a/protocol/go.mod +++ b/protocol/go.mod @@ -3,7 +3,7 @@ module github.com/RTann/libsignal-go/protocol go 1.20 require ( - filippo.io/edwards25519 v1.0.0 + filippo.io/edwards25519 v1.1.0 github.com/golang/glog v1.2.1 github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.9.0 @@ -19,5 +19,3 @@ require ( golang.org/x/sync v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace filippo.io/edwards25519 => github.com/RTann/edwards25519 v0.0.0-20230216062325-3c460db4d075 diff --git a/protocol/go.sum b/protocol/go.sum index fc9872d..68233ce 100644 --- a/protocol/go.sum +++ b/protocol/go.sum @@ -1,5 +1,5 @@ -github.com/RTann/edwards25519 v0.0.0-20230216062325-3c460db4d075 h1:Hq8Dm4YeNa7W7UlR3ZPAPy+Ktzl51OnCe+3liDNlJO0= -github.com/RTann/edwards25519 v0.0.0-20230216062325-3c460db4d075/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=