Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ go get github.com/AeonDave/cryptonite-go

### Public Key Crypto
- **Signatures**: Ed25519, ECDSA P-256
- **Key Exchange**: X25519, ECDH P-256/P-384
- **Key Exchange**: X25519, X448, ECDH P-256/P-384
- **Post-Quantum**: Hybrid X25519+ML-KEM ready (via `pq` package)

Full algorithm matrix with specs:
Expand Down Expand Up @@ -88,18 +88,24 @@ digest := hasher.Hash([]byte("hello world"))
fmt.Printf("%x\n", digest)
```

### Key Exchange (X25519)
### Key Exchange (X25519 / X448)

```go
import "github.com/AeonDave/cryptonite-go/ecdh"

x25519 := ecdh.NewX25519()
x448 := ecdh.NewX448()
alicePriv, _ := x25519.GenerateKey()
bobPriv, _ := x25519.GenerateKey()

aliceShared, _ := x25519.SharedSecret(alicePriv, bobPriv.PublicKey())
bobShared, _ := x25519.SharedSecret(bobPriv, alicePriv.PublicKey())
// aliceShared == bobShared

// X448 exposes the same API for higher security deployments.
alice448, _ := x448.GenerateKey()
bob448, _ := x448.GenerateKey()
shared448, _ := x448.SharedSecret(alice448, bob448.PublicKey())
```

### Digital Signatures (Ed25519)
Expand Down
1 change: 1 addition & 0 deletions docs/ALGORITHMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ sites.
| Algorithm | Constructor | Public | Private | Shared | Notes | RFC / Spec |
|-----------|--------------------|--------------------|------------|--------|---------------------------|---------------------------------------------------------|
| X25519 | `ecdh.NewX25519()` | 32B | 32B | 32B | RFC 7748 (crypto/ecdh) | [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748.html) |
| X448 | `ecdh.NewX448()` | 56B | 56B | 56B | RFC 7748 (pure Go impl.) | [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748.html) |
| P-256 | `ecdh.NewP256()` | 65B (uncompressed) | 32B scalar | 32B | Uncompressed public: 0x04 | | X || Y | [FIPS 186-5](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf) |
| P-384 | `ecdh.NewP384()` | 97B (uncompressed) | 48B scalar | 48B | Uncompressed public: 0x04 | | X || Y | [FIPS 186-5](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf) |

Expand Down
2 changes: 2 additions & 0 deletions docs/INTEROP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ and compatibility notes.

- `ecdh.NewX25519` expects 32-byte private keys and outputs 32-byte Montgomery u-coordinates. Use the standard base point
defined in RFC 7748.
- `ecdh.NewX448` expects 56-byte private scalars and emits 56-byte Montgomery u-coordinates. The canonical base point is
the little-endian encoding of the integer 5.
- `ecdh.NewP256` / `NewP384` accept scalar private keys and return uncompressed SEC1 public points.

## HPKE Records
Expand Down
141 changes: 125 additions & 16 deletions ecdh/ecdh.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,62 @@
package ecdh

import (
"crypto"
stdecdh "crypto/ecdh"
"crypto/rand"
"errors"
)

// PrivateKey represents an ECDH private key backed either by the Go standard
// library implementation or by a custom curve implementation (such as X448).
//
// The interface intentionally mirrors the small subset of methods exposed by
// crypto/ecdh.PrivateKey that are required across the repository. This allows
// callers to operate on keys uniformly without leaking the concrete
// implementation details or exposing mutable internal buffers.
type PrivateKey interface {
// Bytes returns the canonical encoding of the private key.
Bytes() []byte
// PublicKey returns the corresponding public key instance.
PublicKey() PublicKey
// ECDH computes the shared secret with the peer public key.
ECDH(peer PublicKey) ([]byte, error)
// Equal reports whether the provided key matches this private key.
Equal(x crypto.PrivateKey) bool
}

// PublicKey represents an ECDH public key suitable for the associated
// PrivateKey type.
type PublicKey interface {
// Bytes returns the canonical encoding of the public key.
Bytes() []byte
// Equal reports whether the provided key matches this public key.
Equal(x crypto.PublicKey) bool
}

// KeyExchange describes the minimal API shared by ECDH helpers exposed by the
// library. Implementations are thin wrappers around crypto/ecdh curves and
// provide uniform helpers for performing Diffie-Hellman operations without
// leaking the underlying curve-specific types to callers.
// library. Implementations may wrap crypto/ecdh curves or provide custom
// curve-specific logic while presenting a uniform surface to callers.
type KeyExchange interface {
// Curve returns the underlying crypto/ecdh curve implementation.
// Curve returns the underlying crypto/ecdh curve implementation when
// available. For custom curves without a crypto/ecdh counterpart this may
// return nil.
Curve() stdecdh.Curve
// GenerateKey creates a new private key using crypto/rand.
GenerateKey() (*stdecdh.PrivateKey, error)
GenerateKey() (PrivateKey, error)
// NewPrivateKey constructs a private key from scalar bytes.
NewPrivateKey(d []byte) (*stdecdh.PrivateKey, error)
NewPrivateKey(d []byte) (PrivateKey, error)
// NewPublicKey parses a peer public key in the format required by the curve.
NewPublicKey(b []byte) (*stdecdh.PublicKey, error)
NewPublicKey(b []byte) (PublicKey, error)
// SharedSecret performs the ECDH operation between private and peer.
SharedSecret(p *stdecdh.PrivateKey, peer *stdecdh.PublicKey) ([]byte, error)
SharedSecret(p PrivateKey, peer PublicKey) ([]byte, error)
}

var (
errIncompatiblePrivate = errors.New("ecdh: incompatible private key type")
errIncompatiblePublic = errors.New("ecdh: incompatible public key type")
)

type curveImpl struct {
curve stdecdh.Curve
}
Expand All @@ -36,18 +71,92 @@ func NewKeyExchange(curve stdecdh.Curve) KeyExchange {

func (c *curveImpl) Curve() stdecdh.Curve { return c.curve }

func (c *curveImpl) GenerateKey() (*stdecdh.PrivateKey, error) {
return c.curve.GenerateKey(rand.Reader)
func (c *curveImpl) GenerateKey() (PrivateKey, error) {
priv, err := c.curve.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
return &stdPrivateKey{key: priv}, nil
}

func (c *curveImpl) NewPrivateKey(d []byte) (PrivateKey, error) {
priv, err := c.curve.NewPrivateKey(d)
if err != nil {
return nil, err
}
return &stdPrivateKey{key: priv}, nil
}

func (c *curveImpl) NewPublicKey(b []byte) (PublicKey, error) {
pub, err := c.curve.NewPublicKey(b)
if err != nil {
return nil, err
}
return &stdPublicKey{key: pub}, nil
}

func (c *curveImpl) SharedSecret(p PrivateKey, peer PublicKey) ([]byte, error) {
sp, ok := p.(*stdPrivateKey)
if !ok {
return nil, errIncompatiblePrivate
}
pp, ok := peer.(*stdPublicKey)
if !ok {
return nil, errIncompatiblePublic
}
return sp.key.ECDH(pp.key)
}

type stdPrivateKey struct {
key *stdecdh.PrivateKey
}

func (c *curveImpl) NewPrivateKey(d []byte) (*stdecdh.PrivateKey, error) {
return c.curve.NewPrivateKey(d)
func (k *stdPrivateKey) Bytes() []byte {
if k == nil || k.key == nil {
return nil
}
return k.key.Bytes()
}

func (c *curveImpl) NewPublicKey(b []byte) (*stdecdh.PublicKey, error) {
return c.curve.NewPublicKey(b)
func (k *stdPrivateKey) PublicKey() PublicKey {
if k == nil || k.key == nil {
return nil
}
return &stdPublicKey{key: k.key.PublicKey()}
}

func (c *curveImpl) SharedSecret(p *stdecdh.PrivateKey, peer *stdecdh.PublicKey) ([]byte, error) {
return p.ECDH(peer)
func (k *stdPrivateKey) ECDH(peer PublicKey) ([]byte, error) {
if k == nil || k.key == nil {
return nil, errors.New("ecdh: nil private key")
}
pp, ok := peer.(*stdPublicKey)
if !ok {
return nil, errIncompatiblePublic
}
return k.key.ECDH(pp.key)
}

func (k *stdPrivateKey) Equal(x crypto.PrivateKey) bool {
if k == nil || k.key == nil {
return false
}
return k.key.Equal(x)
}

type stdPublicKey struct {
key *stdecdh.PublicKey
}

func (k *stdPublicKey) Bytes() []byte {
if k == nil || k.key == nil {
return nil
}
return k.key.Bytes()
}

func (k *stdPublicKey) Equal(x crypto.PublicKey) bool {
if k == nil || k.key == nil {
return false
}
return k.key.Equal(x)
}
8 changes: 4 additions & 4 deletions ecdh/p256.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ func CurveP256() stdecdh.Curve { return p256Curve }
func NewP256() KeyExchange { return p256Impl }

// GenerateKeyP256 creates a new private key using crypto/rand.
func GenerateKeyP256() (*stdecdh.PrivateKey, error) { return p256Impl.GenerateKey() }
func GenerateKeyP256() (PrivateKey, error) { return p256Impl.GenerateKey() }

// NewPrivateKeyP256 constructs a private key from scalar bytes.
func NewPrivateKeyP256(d []byte) (*stdecdh.PrivateKey, error) { return p256Impl.NewPrivateKey(d) }
func NewPrivateKeyP256(d []byte) (PrivateKey, error) { return p256Impl.NewPrivateKey(d) }

// NewPublicKeyP256 parses an uncompressed public key.
func NewPublicKeyP256(b []byte) (*stdecdh.PublicKey, error) { return p256Impl.NewPublicKey(b) }
func NewPublicKeyP256(b []byte) (PublicKey, error) { return p256Impl.NewPublicKey(b) }

// SharedSecretP256 performs the ECDH operation between private and peer.
func SharedSecretP256(p *stdecdh.PrivateKey, peer *stdecdh.PublicKey) ([]byte, error) {
func SharedSecretP256(p PrivateKey, peer PublicKey) ([]byte, error) {
return p256Impl.SharedSecret(p, peer)
}
8 changes: 4 additions & 4 deletions ecdh/p384.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ func CurveP384() stdecdh.Curve { return p384Curve }
func NewP384() KeyExchange { return p384Impl }

// GenerateKeyP384 creates a new private key using crypto/rand.
func GenerateKeyP384() (*stdecdh.PrivateKey, error) { return p384Impl.GenerateKey() }
func GenerateKeyP384() (PrivateKey, error) { return p384Impl.GenerateKey() }

// NewPrivateKeyP384 constructs a private key from scalar bytes.
func NewPrivateKeyP384(d []byte) (*stdecdh.PrivateKey, error) { return p384Impl.NewPrivateKey(d) }
func NewPrivateKeyP384(d []byte) (PrivateKey, error) { return p384Impl.NewPrivateKey(d) }

// NewPublicKeyP384 parses an uncompressed public key.
func NewPublicKeyP384(b []byte) (*stdecdh.PublicKey, error) { return p384Impl.NewPublicKey(b) }
func NewPublicKeyP384(b []byte) (PublicKey, error) { return p384Impl.NewPublicKey(b) }

// SharedSecretP384 performs the ECDH operation between private and peer.
func SharedSecretP384(p *stdecdh.PrivateKey, peer *stdecdh.PublicKey) ([]byte, error) {
func SharedSecretP384(p PrivateKey, peer PublicKey) ([]byte, error) {
return p384Impl.SharedSecret(p, peer)
}
8 changes: 4 additions & 4 deletions ecdh/x25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ func CurveX25519() stdecdh.Curve { return x25519Curve }
func NewX25519() KeyExchange { return x25519Impl }

// GenerateKeyX25519 creates a new private key using crypto/rand.
func GenerateKeyX25519() (*stdecdh.PrivateKey, error) { return x25519Impl.GenerateKey() }
func GenerateKeyX25519() (PrivateKey, error) { return x25519Impl.GenerateKey() }

// NewPrivateKeyX25519 constructs a private key from scalar bytes.
func NewPrivateKeyX25519(d []byte) (*stdecdh.PrivateKey, error) { return x25519Impl.NewPrivateKey(d) }
func NewPrivateKeyX25519(d []byte) (PrivateKey, error) { return x25519Impl.NewPrivateKey(d) }

// NewPublicKeyX25519 parses a 32-byte Montgomery u-coordinate public key.
func NewPublicKeyX25519(b []byte) (*stdecdh.PublicKey, error) { return x25519Impl.NewPublicKey(b) }
func NewPublicKeyX25519(b []byte) (PublicKey, error) { return x25519Impl.NewPublicKey(b) }

// SharedSecretX25519 performs the X25519 Diffie-Hellman operation between private and peer.
func SharedSecretX25519(p *stdecdh.PrivateKey, peer *stdecdh.PublicKey) ([]byte, error) {
func SharedSecretX25519(p PrivateKey, peer PublicKey) ([]byte, error) {
return x25519Impl.SharedSecret(p, peer)
}
Loading