// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ecdh

import (
	"bytes"
	"crypto/internal/boring"
	"crypto/internal/fips140/ecdh"
	"crypto/internal/fips140only"
	"errors"
	"io"
)

type nistCurve struct {
	name          string
	generate      func(io.Reader) (*ecdh.PrivateKey, error)
	newPrivateKey func([]byte) (*ecdh.PrivateKey, error)
	newPublicKey  func(publicKey []byte) (*ecdh.PublicKey, error)
	sharedSecret  func(*ecdh.PrivateKey, *ecdh.PublicKey) (sharedSecret []byte, err error)
}

func (c *nistCurve) String() string {
	return c.name
}

func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
	if boring.Enabled && rand == boring.RandReader {
		key, bytes, err := boring.GenerateKeyECDH(c.name)
		if err != nil {
			return nil, err
		}
		pub, err := key.PublicKey()
		if err != nil {
			return nil, err
		}
		k := &PrivateKey{
			curve:      c,
			privateKey: bytes,
			publicKey:  &PublicKey{curve: c, publicKey: pub.Bytes(), boring: pub},
			boring:     key,
		}
		return k, nil
	}

	if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) {
		return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode")
	}

	privateKey, err := c.generate(rand)
	if err != nil {
		return nil, err
	}

	k := &PrivateKey{
		curve:      c,
		privateKey: privateKey.Bytes(),
		fips:       privateKey,
		publicKey: &PublicKey{
			curve:     c,
			publicKey: privateKey.PublicKey().Bytes(),
			fips:      privateKey.PublicKey(),
		},
	}
	if boring.Enabled {
		bk, err := boring.NewPrivateKeyECDH(c.name, k.privateKey)
		if err != nil {
			return nil, err
		}
		pub, err := bk.PublicKey()
		if err != nil {
			return nil, err
		}
		k.boring = bk
		k.publicKey.boring = pub
	}
	return k, nil
}

func (c *nistCurve) NewPrivateKey(key []byte) (*PrivateKey, error) {
	if boring.Enabled {
		bk, err := boring.NewPrivateKeyECDH(c.name, key)
		if err != nil {
			return nil, errors.New("crypto/ecdh: invalid private key")
		}
		pub, err := bk.PublicKey()
		if err != nil {
			return nil, errors.New("crypto/ecdh: invalid private key")
		}
		k := &PrivateKey{
			curve:      c,
			privateKey: bytes.Clone(key),
			publicKey:  &PublicKey{curve: c, publicKey: pub.Bytes(), boring: pub},
			boring:     bk,
		}
		return k, nil
	}

	fk, err := c.newPrivateKey(key)
	if err != nil {
		return nil, err
	}
	k := &PrivateKey{
		curve:      c,
		privateKey: bytes.Clone(key),
		fips:       fk,
		publicKey: &PublicKey{
			curve:     c,
			publicKey: fk.PublicKey().Bytes(),
			fips:      fk.PublicKey(),
		},
	}
	return k, nil
}

func (c *nistCurve) NewPublicKey(key []byte) (*PublicKey, error) {
	// Reject the point at infinity and compressed encodings.
	// Note that boring.NewPublicKeyECDH would accept them.
	if len(key) == 0 || key[0] != 4 {
		return nil, errors.New("crypto/ecdh: invalid public key")
	}
	k := &PublicKey{
		curve:     c,
		publicKey: bytes.Clone(key),
	}
	if boring.Enabled {
		bk, err := boring.NewPublicKeyECDH(c.name, k.publicKey)
		if err != nil {
			return nil, errors.New("crypto/ecdh: invalid public key")
		}
		k.boring = bk
	} else {
		fk, err := c.newPublicKey(key)
		if err != nil {
			return nil, err
		}
		k.fips = fk
	}
	return k, nil
}

func (c *nistCurve) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) {
	// Note that this function can't return an error, as NewPublicKey rejects
	// invalid points and the point at infinity, and NewPrivateKey rejects
	// invalid scalars and the zero value. BytesX returns an error for the point
	// at infinity, but in a prime order group such as the NIST curves that can
	// only be the result of a scalar multiplication if one of the inputs is the
	// zero scalar or the point at infinity.

	if boring.Enabled {
		return boring.ECDH(local.boring, remote.boring)
	}
	return c.sharedSecret(local.fips, remote.fips)
}

// P256 returns a [Curve] which implements NIST P-256 (FIPS 186-3, section D.2.3),
// also known as secp256r1 or prime256v1.
//
// Multiple invocations of this function will return the same value, which can
// be used for equality checks and switch statements.
func P256() Curve { return p256 }

var p256 = &nistCurve{
	name: "P-256",
	generate: func(r io.Reader) (*ecdh.PrivateKey, error) {
		return ecdh.GenerateKey(ecdh.P256(), r)
	},
	newPrivateKey: func(b []byte) (*ecdh.PrivateKey, error) {
		return ecdh.NewPrivateKey(ecdh.P256(), b)
	},
	newPublicKey: func(publicKey []byte) (*ecdh.PublicKey, error) {
		return ecdh.NewPublicKey(ecdh.P256(), publicKey)
	},
	sharedSecret: func(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) (sharedSecret []byte, err error) {
		return ecdh.ECDH(ecdh.P256(), priv, pub)
	},
}

// P384 returns a [Curve] which implements NIST P-384 (FIPS 186-3, section D.2.4),
// also known as secp384r1.
//
// Multiple invocations of this function will return the same value, which can
// be used for equality checks and switch statements.
func P384() Curve { return p384 }

var p384 = &nistCurve{
	name: "P-384",
	generate: func(r io.Reader) (*ecdh.PrivateKey, error) {
		return ecdh.GenerateKey(ecdh.P384(), r)
	},
	newPrivateKey: func(b []byte) (*ecdh.PrivateKey, error) {
		return ecdh.NewPrivateKey(ecdh.P384(), b)
	},
	newPublicKey: func(publicKey []byte) (*ecdh.PublicKey, error) {
		return ecdh.NewPublicKey(ecdh.P384(), publicKey)
	},
	sharedSecret: func(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) (sharedSecret []byte, err error) {
		return ecdh.ECDH(ecdh.P384(), priv, pub)
	},
}

// P521 returns a [Curve] which implements NIST P-521 (FIPS 186-3, section D.2.5),
// also known as secp521r1.
//
// Multiple invocations of this function will return the same value, which can
// be used for equality checks and switch statements.
func P521() Curve { return p521 }

var p521 = &nistCurve{
	name: "P-521",
	generate: func(r io.Reader) (*ecdh.PrivateKey, error) {
		return ecdh.GenerateKey(ecdh.P521(), r)
	},
	newPrivateKey: func(b []byte) (*ecdh.PrivateKey, error) {
		return ecdh.NewPrivateKey(ecdh.P521(), b)
	},
	newPublicKey: func(publicKey []byte) (*ecdh.PublicKey, error) {
		return ecdh.NewPublicKey(ecdh.P521(), publicKey)
	},
	sharedSecret: func(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) (sharedSecret []byte, err error) {
		return ecdh.ECDH(ecdh.P521(), priv, pub)
	},
}
