Skip to content

Commit

Permalink
chore(curve): clean curve implementation (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
RTann authored May 24, 2024
1 parent 6dae35d commit a333d78
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 51 deletions.
59 changes: 35 additions & 24 deletions protocol/curve/curve25519/curve25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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()
Expand Down
5 changes: 2 additions & 3 deletions protocol/curve/curve25519/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 1 addition & 4 deletions protocol/curve/djb_private.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions protocol/curve/djb_public.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions protocol/curve/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package curve implements elliptic curve cryptography
// functions used for the protocol.
package curve
11 changes: 6 additions & 5 deletions protocol/curve/pair.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Package curve implements elliptic curve cryptography
// functions used for the protocol.
package curve

import "io"
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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...)
}
11 changes: 1 addition & 10 deletions protocol/curve/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 1 addition & 3 deletions protocol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
4 changes: 2 additions & 2 deletions protocol/go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down

0 comments on commit a333d78

Please sign in to comment.