// Package ed448 implements Ed448 signature scheme as described in RFC-8032.
//
// This package implements two signature variants.
//
//  | Scheme Name | Sign Function     | Verification  | Context           |
//  |-------------|-------------------|---------------|-------------------|
//  | Ed448       | Sign              | Verify        | Yes, can be empty |
//  | Ed448Ph     | SignPh            | VerifyPh      | Yes, can be empty |
//  | All above   | (PrivateKey).Sign | VerifyAny     | As above          |
//
// Specific functions for sign and verify are defined. A generic signing
// function for all schemes is available through the crypto.Signer interface,
// which is implemented by the PrivateKey type. A correspond all-in-one
// verification method is provided by the VerifyAny function.
//
// Both schemes require a context string for domain separation. This parameter
// is passed using a SignerOptions struct defined in this package.
//
// References:
//
//  - RFC8032 https://rfc-editor.org/rfc/rfc8032.txt
//  - EdDSA for more curves https://eprint.iacr.org/2015/677
//  - High-speed high-security signatures. https://doi.org/10.1007/s13389-012-0027-1
package ed448

import (
	"bytes"
	"crypto"
	cryptoRand "crypto/rand"
	"crypto/subtle"
	"errors"
	"fmt"
	"io"
	"strconv"

	"github.com/cloudflare/circl/ecc/goldilocks"
	sha3 "github.com/cloudflare/circl/internal/shake"
	"github.com/cloudflare/circl/sign"
)

const (
	// ContextMaxSize is the maximum length (in bytes) allowed for context.
	ContextMaxSize = 255
	// PublicKeySize is the length in bytes of Ed448 public keys.
	PublicKeySize = 57
	// PrivateKeySize is the length in bytes of Ed448 private keys.
	PrivateKeySize = 114
	// SignatureSize is the length in bytes of signatures.
	SignatureSize = 114
	// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
	SeedSize = 57
)

const (
	paramB   = 456 / 8    // Size of keys in bytes.
	hashSize = 2 * paramB // Size of the hash function's output.
)

// SignerOptions implements crypto.SignerOpts and augments with parameters
// that are specific to the Ed448 signature schemes.
type SignerOptions struct {
	// Hash must be crypto.Hash(0) for both Ed448 and Ed448Ph.
	crypto.Hash

	// Context is an optional domain separation string for signing.
	// Its length must be less or equal than 255 bytes.
	Context string

	// Scheme is an identifier for choosing a signature scheme.
	Scheme SchemeID
}

// SchemeID is an identifier for each signature scheme.
type SchemeID uint

const (
	ED448 SchemeID = iota
	ED448Ph
)

// PublicKey is the type of Ed448 public keys.
type PublicKey []byte

// Equal reports whether pub and x have the same value.
func (pub PublicKey) Equal(x crypto.PublicKey) bool {
	xx, ok := x.(PublicKey)
	return ok && bytes.Equal(pub, xx)
}

// PrivateKey is the type of Ed448 private keys. It implements crypto.Signer.
type PrivateKey []byte

// Equal reports whether priv and x have the same value.
func (priv PrivateKey) Equal(x crypto.PrivateKey) bool {
	xx, ok := x.(PrivateKey)
	return ok && subtle.ConstantTimeCompare(priv, xx) == 1
}

// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
	publicKey := make([]byte, PublicKeySize)
	copy(publicKey, priv[SeedSize:])
	return PublicKey(publicKey)
}

// Seed returns the private key seed corresponding to priv. It is provided for
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
// in this package.
func (priv PrivateKey) Seed() []byte {
	seed := make([]byte, SeedSize)
	copy(seed, priv[:SeedSize])
	return seed
}

func (priv PrivateKey) Scheme() sign.Scheme { return Scheme }

func (pub PublicKey) Scheme() sign.Scheme { return Scheme }

func (priv PrivateKey) MarshalBinary() (data []byte, err error) {
	privateKey := make(PrivateKey, PrivateKeySize)
	copy(privateKey, priv)
	return privateKey, nil
}

func (pub PublicKey) MarshalBinary() (data []byte, err error) {
	publicKey := make(PublicKey, PublicKeySize)
	copy(publicKey, pub)
	return publicKey, nil
}

// Sign creates a signature of a message given a key pair.
// This function supports all the two signature variants defined in RFC-8032,
// namely Ed448 (or pure EdDSA) and Ed448Ph.
// The opts.HashFunc() must return zero to the specify Ed448 variant. This can
// be achieved by passing crypto.Hash(0) as the value for opts.
// Use an Options struct to pass a bool indicating that the ed448Ph variant
// should be used.
// The struct can also be optionally used to pass a context string for signing.
func (priv PrivateKey) Sign(
	rand io.Reader,
	message []byte,
	opts crypto.SignerOpts) (signature []byte, err error) {
	var ctx string
	var scheme SchemeID

	if o, ok := opts.(SignerOptions); ok {
		ctx = o.Context
		scheme = o.Scheme
	}

	switch true {
	case scheme == ED448 && opts.HashFunc() == crypto.Hash(0):
		return Sign(priv, message, ctx), nil
	case scheme == ED448Ph && opts.HashFunc() == crypto.Hash(0):
		return SignPh(priv, message, ctx), nil
	default:
		return nil, errors.New("ed448: bad hash algorithm")
	}
}

// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
	if rand == nil {
		rand = cryptoRand.Reader
	}

	seed := make(PrivateKey, SeedSize)
	if _, err := io.ReadFull(rand, seed); err != nil {
		return nil, nil, err
	}

	privateKey := NewKeyFromSeed(seed)
	publicKey := make([]byte, PublicKeySize)
	copy(publicKey, privateKey[SeedSize:])

	return publicKey, privateKey, nil
}

// NewKeyFromSeed calculates a private key from a seed. It will panic if
// len(seed) is not SeedSize. This function is provided for interoperability
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
// package.
func NewKeyFromSeed(seed []byte) PrivateKey {
	privateKey := make([]byte, PrivateKeySize)
	newKeyFromSeed(privateKey, seed)
	return privateKey
}

func newKeyFromSeed(privateKey, seed []byte) {
	if l := len(seed); l != SeedSize {
		panic("ed448: bad seed length: " + strconv.Itoa(l))
	}

	var h [hashSize]byte
	H := sha3.NewShake256()
	_, _ = H.Write(seed)
	_, _ = H.Read(h[:])
	s := &goldilocks.Scalar{}
	deriveSecretScalar(s, h[:paramB])

	copy(privateKey[:SeedSize], seed)
	_ = goldilocks.Curve{}.ScalarBaseMult(s).ToBytes(privateKey[SeedSize:])
}

func signAll(signature []byte, privateKey PrivateKey, message, ctx []byte, preHash bool) {
	if len(ctx) > ContextMaxSize {
		panic(fmt.Errorf("ed448: bad context length: " + strconv.Itoa(len(ctx))))
	}

	H := sha3.NewShake256()
	var PHM []byte

	if preHash {
		var h [64]byte
		_, _ = H.Write(message)
		_, _ = H.Read(h[:])
		PHM = h[:]
		H.Reset()
	} else {
		PHM = message
	}

	// 1.  Hash the 57-byte private key using SHAKE256(x, 114).
	var h [hashSize]byte
	_, _ = H.Write(privateKey[:SeedSize])
	_, _ = H.Read(h[:])
	s := &goldilocks.Scalar{}
	deriveSecretScalar(s, h[:paramB])
	prefix := h[paramB:]

	// 2.  Compute SHAKE256(dom4(F, C) || prefix || PH(M), 114).
	var rPM [hashSize]byte
	H.Reset()

	writeDom(&H, ctx, preHash)

	_, _ = H.Write(prefix)
	_, _ = H.Write(PHM)
	_, _ = H.Read(rPM[:])

	// 3.  Compute the point [r]B.
	r := &goldilocks.Scalar{}
	r.FromBytes(rPM[:])
	R := (&[paramB]byte{})[:]
	if err := (goldilocks.Curve{}.ScalarBaseMult(r).ToBytes(R)); err != nil {
		panic(err)
	}
	// 4.  Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114)
	var hRAM [hashSize]byte
	H.Reset()

	writeDom(&H, ctx, preHash)

	_, _ = H.Write(R)
	_, _ = H.Write(privateKey[SeedSize:])
	_, _ = H.Write(PHM)
	_, _ = H.Read(hRAM[:])

	// 5.  Compute S = (r + k * s) mod order.
	k := &goldilocks.Scalar{}
	k.FromBytes(hRAM[:])
	S := &goldilocks.Scalar{}
	S.Mul(k, s)
	S.Add(S, r)

	// 6.  The signature is the concatenation of R and S.
	copy(signature[:paramB], R[:])
	copy(signature[paramB:], S[:])
}

// Sign signs the message with privateKey and returns a signature.
// This function supports the signature variant defined in RFC-8032: Ed448,
// also known as the pure version of EdDSA.
// It will panic if len(privateKey) is not PrivateKeySize.
func Sign(priv PrivateKey, message []byte, ctx string) []byte {
	signature := make([]byte, SignatureSize)
	signAll(signature, priv, message, []byte(ctx), false)
	return signature
}

// SignPh creates a signature of a message given a keypair.
// This function supports the signature variant defined in RFC-8032: Ed448ph,
// meaning it internally hashes the message using SHAKE-256.
// Context could be passed to this function, which length should be no more than
// 255. It can be empty.
func SignPh(priv PrivateKey, message []byte, ctx string) []byte {
	signature := make([]byte, SignatureSize)
	signAll(signature, priv, message, []byte(ctx), true)
	return signature
}

func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool {
	if len(public) != PublicKeySize ||
		len(signature) != SignatureSize ||
		len(ctx) > ContextMaxSize ||
		!isLessThanOrder(signature[paramB:]) {
		return false
	}

	P, err := goldilocks.FromBytes(public)
	if err != nil {
		return false
	}

	H := sha3.NewShake256()
	var PHM []byte

	if preHash {
		var h [64]byte
		_, _ = H.Write(message)
		_, _ = H.Read(h[:])
		PHM = h[:]
		H.Reset()
	} else {
		PHM = message
	}

	var hRAM [hashSize]byte
	R := signature[:paramB]

	writeDom(&H, ctx, preHash)

	_, _ = H.Write(R)
	_, _ = H.Write(public)
	_, _ = H.Write(PHM)
	_, _ = H.Read(hRAM[:])

	k := &goldilocks.Scalar{}
	k.FromBytes(hRAM[:])
	S := &goldilocks.Scalar{}
	S.FromBytes(signature[paramB:])

	encR := (&[paramB]byte{})[:]
	P.Neg()
	_ = goldilocks.Curve{}.CombinedMult(S, k, P).ToBytes(encR)
	return bytes.Equal(R, encR)
}

// VerifyAny returns true if the signature is valid. Failure cases are invalid
// signature, or when the public key cannot be decoded.
// This function supports all the two signature variants defined in RFC-8032,
// namely Ed448 (or pure EdDSA) and Ed448Ph.
// The opts.HashFunc() must return zero, this can be achieved by passing
// crypto.Hash(0) as the value for opts.
// Use a SignerOptions struct to pass a context string for signing.
func VerifyAny(public PublicKey, message, signature []byte, opts crypto.SignerOpts) bool {
	var ctx string
	var scheme SchemeID
	if o, ok := opts.(SignerOptions); ok {
		ctx = o.Context
		scheme = o.Scheme
	}

	switch true {
	case scheme == ED448 && opts.HashFunc() == crypto.Hash(0):
		return Verify(public, message, signature, ctx)
	case scheme == ED448Ph && opts.HashFunc() == crypto.Hash(0):
		return VerifyPh(public, message, signature, ctx)
	default:
		return false
	}
}

// Verify returns true if the signature is valid. Failure cases are invalid
// signature, or when the public key cannot be decoded.
// This function supports the signature variant defined in RFC-8032: Ed448,
// also known as the pure version of EdDSA.
func Verify(public PublicKey, message, signature []byte, ctx string) bool {
	return verify(public, message, signature, []byte(ctx), false)
}

// VerifyPh returns true if the signature is valid. Failure cases are invalid
// signature, or when the public key cannot be decoded.
// This function supports the signature variant defined in RFC-8032: Ed448ph,
// meaning it internally hashes the message using SHAKE-256.
// Context could be passed to this function, which length should be no more than
// 255. It can be empty.
func VerifyPh(public PublicKey, message, signature []byte, ctx string) bool {
	return verify(public, message, signature, []byte(ctx), true)
}

func deriveSecretScalar(s *goldilocks.Scalar, h []byte) {
	h[0] &= 0xFC        // The two least significant bits of the first octet are cleared,
	h[paramB-1] = 0x00  // all eight bits the last octet are cleared, and
	h[paramB-2] |= 0x80 // the highest bit of the second to last octet is set.
	s.FromBytes(h[:paramB])
}

// isLessThanOrder returns true if 0 <= x < order and if the last byte of x is zero.
func isLessThanOrder(x []byte) bool {
	order := goldilocks.Curve{}.Order()
	i := len(order) - 1
	for i > 0 && x[i] == order[i] {
		i--
	}
	return x[paramB-1] == 0 && x[i] < order[i]
}

func writeDom(h io.Writer, ctx []byte, preHash bool) {
	dom4 := "SigEd448"
	_, _ = h.Write([]byte(dom4))

	if preHash {
		_, _ = h.Write([]byte{byte(0x01), byte(len(ctx))})
	} else {
		_, _ = h.Write([]byte{byte(0x00), byte(len(ctx))})
	}
	_, _ = h.Write(ctx)
}
