package hpke

import (
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/elliptic"
	"crypto/hmac"
	"crypto/subtle"
	"encoding/binary"
	"fmt"
	"io"
	"math/big"

	_ "crypto/sha256"
	_ "crypto/sha512"

	"github.com/cloudflare/circl/dh/x448"
	"golang.org/x/crypto/chacha20poly1305"
	"golang.org/x/crypto/curve25519"
)

////////
// DHKEM

type dhScheme interface {
	ID() KEMID
	DeriveKeyPair(ikm []byte) (KEMPrivateKey, KEMPublicKey, error)
	SerializePublicKey(pk KEMPublicKey) []byte
	DeserializePublicKey(enc []byte) (KEMPublicKey, error)
	DH(priv KEMPrivateKey, pub KEMPublicKey) ([]byte, error)
	PublicKeySize() int
	PrivateKeySize() int

	SerializePrivateKey(sk KEMPrivateKey) []byte
	DeserializePrivateKey(enc []byte) (KEMPrivateKey, error)

	internalKDF() KDFScheme
}

type dhkemScheme struct {
	group dhScheme
	skE   KEMPrivateKey
}

func (s dhkemScheme) ID() KEMID {
	return s.group.ID()
}

func (s dhkemScheme) DeriveKeyPair(ikm []byte) (KEMPrivateKey, KEMPublicKey, error) {
	return s.group.DeriveKeyPair(ikm)
}

func (s dhkemScheme) SerializePublicKey(pk KEMPublicKey) []byte {
	return s.group.SerializePublicKey(pk)
}

func (s dhkemScheme) SerializePrivateKey(sk KEMPrivateKey) []byte {
	return s.group.SerializePrivateKey(sk)
}

func (s dhkemScheme) DeserializePublicKey(enc []byte) (KEMPublicKey, error) {
	return s.group.DeserializePublicKey(enc)
}

func (s dhkemScheme) DeserializePrivateKey(enc []byte) (KEMPrivateKey, error) {
	return s.group.DeserializePrivateKey(enc)
}

func (s *dhkemScheme) setEphemeralKeyPair(skE KEMPrivateKey) {
	s.skE = skE
}

func (s dhkemScheme) getEphemeralKeyPair(rand io.Reader) (KEMPrivateKey, KEMPublicKey, error) {
	if s.skE != nil {
		return s.skE, s.skE.PublicKey(), nil
	}

	ikm := make([]byte, s.PrivateKeySize())
	rand.Read(ikm)

	return s.group.DeriveKeyPair(ikm)
}

func (s dhkemScheme) extractAndExpand(dh []byte, kemContext []byte, Nsecret int) []byte {
	suiteID := kemSuiteFromID(s.ID())
	eae_prk := s.group.internalKDF().LabeledExtract(nil, suiteID, "eae_prk", dh)
	return s.group.internalKDF().LabeledExpand(eae_prk, suiteID, "shared_secret", kemContext, Nsecret)
}

func (s dhkemScheme) Encap(rand io.Reader, pkR KEMPublicKey) ([]byte, []byte, error) {
	skE, pkE, err := s.getEphemeralKeyPair(rand)
	if err != nil {
		return nil, nil, err
	}

	dh, err := s.group.DH(skE, pkR)
	if err != nil {
		return nil, nil, err
	}

	enc := s.group.SerializePublicKey(pkE)
	pkRm := s.group.SerializePublicKey(pkR)

	kemContext := make([]byte, len(enc)+len(pkRm))
	copy(kemContext, enc)
	copy(kemContext[len(enc):], pkRm)

	Nsecret := s.group.internalKDF().OutputSize()
	sharedSecret := s.extractAndExpand(dh, kemContext, Nsecret)

	return sharedSecret, enc, nil
}

func (s dhkemScheme) Decap(enc []byte, skR KEMPrivateKey) ([]byte, error) {
	pkE, err := s.group.DeserializePublicKey(enc)
	if err != nil {
		return nil, err
	}

	dh, err := s.group.DH(skR, pkE)
	if err != nil {
		return nil, err
	}

	pkRm := s.group.SerializePublicKey(skR.PublicKey())

	kemContext := make([]byte, len(enc)+len(pkRm))
	copy(kemContext, enc)
	copy(kemContext[len(enc):], pkRm)

	Nsecret := s.group.internalKDF().OutputSize()
	sharedSecret := s.extractAndExpand(dh, kemContext, Nsecret)

	return sharedSecret, nil
}

func (s dhkemScheme) AuthEncap(rand io.Reader, pkR KEMPublicKey, skS KEMPrivateKey) ([]byte, []byte, error) {
	skE, pkE, err := s.getEphemeralKeyPair(rand)
	if err != nil {
		return nil, nil, err
	}

	dhER, err := s.group.DH(skE, pkR)
	if err != nil {
		return nil, nil, err
	}

	dhIR, err := s.group.DH(skS, pkR)
	if err != nil {
		return nil, nil, err
	}

	dh := append(dhER, dhIR...)

	enc := s.group.SerializePublicKey(pkE)
	pkRm := s.group.SerializePublicKey(pkR)
	pkSm := s.group.SerializePublicKey(skS.PublicKey())

	Nenc := len(enc)
	Npk := len(pkRm)
	Nsk := len(pkSm)
	kemContext := make([]byte, Nenc+Npk+Nsk)
	copy(kemContext[:Nenc], enc)
	copy(kemContext[Nenc:Nenc+Npk], pkRm)
	copy(kemContext[Nenc+Npk:], pkSm)

	Nsecret := s.group.internalKDF().OutputSize()
	sharedSecret := s.extractAndExpand(dh, kemContext, Nsecret)

	return sharedSecret, enc, nil
}

func (s dhkemScheme) AuthDecap(enc []byte, skR KEMPrivateKey, pkS KEMPublicKey) ([]byte, error) {
	pkE, err := s.group.DeserializePublicKey(enc)
	if err != nil {
		return nil, err
	}

	dhER, err := s.group.DH(skR, pkE)
	if err != nil {
		return nil, err
	}

	dhIR, err := s.group.DH(skR, pkS)
	if err != nil {
		return nil, err
	}

	dh := append(dhER, dhIR...)

	pkRm := s.group.SerializePublicKey(skR.PublicKey())
	pkSm := s.group.SerializePublicKey(pkS)

	Nenc := len(enc)
	Npk := len(pkRm)
	Nsk := len(pkSm)
	kemContext := make([]byte, Nenc+Npk+Nsk)
	copy(kemContext[:Nenc], enc)
	copy(kemContext[Nenc:Nenc+Npk], pkRm)
	copy(kemContext[Nenc+Npk:], pkSm)

	Nsecret := s.group.internalKDF().OutputSize()
	sharedSecret := s.extractAndExpand(dh, kemContext, Nsecret)

	return sharedSecret, nil
}

func (s dhkemScheme) PublicKeySize() int {
	return s.group.PublicKeySize()
}

func (s dhkemScheme) PrivateKeySize() int {
	return s.group.PrivateKeySize()
}

////////////////////////
// ECDH with NIST curves

type ecdhPrivateKey struct {
	curve elliptic.Curve
	d     []byte
	x, y  *big.Int
}

func (priv ecdhPrivateKey) PublicKey() KEMPublicKey {
	return &ecdhPublicKey{priv.curve, priv.x, priv.y}
}

type ecdhPublicKey struct {
	curve elliptic.Curve
	x, y  *big.Int
}

type ecdhScheme struct {
	curve elliptic.Curve
	KDF   KDFScheme
	skE   KEMPrivateKey
}

func (s ecdhScheme) internalKDF() KDFScheme {
	return s.KDF
}

func (s ecdhScheme) ID() KEMID {
	switch s.curve.Params().Name {
	case "P-256":
		return DHKEM_P256
	case "P-521":
		return DHKEM_P521
	}
	panic(fmt.Sprintf("Unsupported curve: %s", s.curve.Params().Name))
}

func (s ecdhScheme) privateKeyBitmask() uint8 {
	switch s.curve.Params().Name {
	case "P-256":
		return 0xFF
	case "P-521":
		return 0x01
	}
	panic(fmt.Sprintf("Unsupported curve: %s", s.curve.Params().Name))
}

func (s ecdhScheme) DeriveKeyPair(ikm []byte) (KEMPrivateKey, KEMPublicKey, error) {
	suiteID := kemSuiteFromID(s.ID())
	dkp_prk := s.KDF.LabeledExtract(nil, suiteID, "dkp_prk", ikm)
	counter := 0
	for {
		if counter > 255 {
			return nil, nil, fmt.Errorf("Error deriving key pair")
		}

		bytes := s.KDF.LabeledExpand(dkp_prk, suiteID, "candidate", []byte{uint8(counter)}, s.PrivateKeySize())
		bytes[0] = bytes[0] & s.privateKeyBitmask()

		sk, err := s.DeserializePrivateKey(bytes)
		if err == nil {
			return sk, sk.PublicKey(), nil
		}

		counter = counter + 1
	}

	return nil, nil, fmt.Errorf("Error deriving key pair")
}

func (s ecdhScheme) SerializePublicKey(pk KEMPublicKey) []byte {
	if pk == nil {
		return nil
	}
	raw := pk.(*ecdhPublicKey)
	return elliptic.Marshal(raw.curve, raw.x, raw.y)
}

func (s ecdhScheme) SerializePrivateKey(sk KEMPrivateKey) []byte {
	if sk == nil {
		return nil
	}

	raw := sk.(*ecdhPrivateKey)
	copied := make([]byte, len(raw.d))
	copy(copied, raw.d)
	return copied
}

func (s ecdhScheme) DeserializePublicKey(enc []byte) (KEMPublicKey, error) {
	x, y := elliptic.Unmarshal(s.curve, enc)
	if x == nil {
		return nil, fmt.Errorf("Error deserializing public key")
	}

	return &ecdhPublicKey{s.curve, x, y}, nil
}

func (s ecdhScheme) DeserializePrivateKey(enc []byte) (KEMPrivateKey, error) {
	if enc == nil {
		return nil, fmt.Errorf("Invalid input")
	}

	x, y := s.curve.Params().ScalarBaseMult(enc)
	return &ecdhPrivateKey{s.curve, enc, x, y}, nil
}

func (s ecdhScheme) DH(priv KEMPrivateKey, pub KEMPublicKey) ([]byte, error) {
	ecdhPriv, ok := priv.(*ecdhPrivateKey)
	if !ok {
		return nil, fmt.Errorf("Private key not suitable for ECDH")
	}

	ecdhPub, ok := pub.(*ecdhPublicKey)
	if !ok {
		return nil, fmt.Errorf("Public key not suitable for ECDH")
	}

	x, _ := s.curve.Params().ScalarMult(ecdhPub.x, ecdhPub.y, ecdhPriv.d)
	xx := x.Bytes()

	size := (s.curve.Params().BitSize + 7) >> 3
	pad := make([]byte, size-len(xx))
	dh := append(pad, xx...)

	return dh, nil
}

func (s ecdhScheme) PublicKeySize() int {
	feSize := (s.curve.Params().BitSize + 7) >> 3
	return 1 + 2*feSize
}

func (s ecdhScheme) PrivateKeySize() int {
	return (s.curve.Params().BitSize + 7) >> 3
}

///////////////////
// ECDH with X25519

type x25519PrivateKey struct {
	val [32]byte
}

func (priv x25519PrivateKey) PublicKey() KEMPublicKey {
	pub := &x25519PublicKey{}
	curve25519.ScalarBaseMult(&pub.val, &priv.val)
	return pub
}

type x25519PublicKey struct {
	val [32]byte
}

type x25519Scheme struct {
	skE KEMPrivateKey
}

func (s x25519Scheme) internalKDF() KDFScheme {
	return hkdfScheme{hash: crypto.SHA256}
}

func (s x25519Scheme) ID() KEMID {
	return DHKEM_X25519
}

func (s x25519Scheme) DeriveKeyPair(ikm []byte) (KEMPrivateKey, KEMPublicKey, error) {
	suiteID := kemSuiteFromID(s.ID())
	dkp_prk := s.internalKDF().LabeledExtract(nil, suiteID, "dkp_prk", ikm)
	sk_bytes := s.internalKDF().LabeledExpand(dkp_prk, suiteID, "sk", nil, s.PrivateKeySize())
	sk, err := s.DeserializePrivateKey(sk_bytes)
	if err != nil {
		return nil, nil, err
	} else {
		return sk, sk.PublicKey(), nil
	}
}

func (s x25519Scheme) SerializePublicKey(pk KEMPublicKey) []byte {
	if pk == nil {
		return nil
	}
	raw := pk.(*x25519PublicKey)
	return raw.val[:]
}

func (s x25519Scheme) SerializePrivateKey(sk KEMPrivateKey) []byte {
	if sk == nil {
		return nil
	}
	raw := sk.(*x25519PrivateKey)
	return raw.val[:]
}

func (s x25519Scheme) DeserializePublicKey(enc []byte) (KEMPublicKey, error) {
	if len(enc) != 32 {
		return nil, fmt.Errorf("Error deserializing X25519 public key")
	}

	pub := &x25519PublicKey{}
	copy(pub.val[:], enc)
	return pub, nil
}

func (s x25519Scheme) DeserializePrivateKey(enc []byte) (KEMPrivateKey, error) {
	if enc == nil {
		return nil, fmt.Errorf("Invalid input")
	}

	if len(enc) != 32 {
		return nil, fmt.Errorf("Error deserializing X25519 private key")
	}

	key := &x25519PrivateKey{}
	copy(key.val[:], enc[0:32])
	return key, nil
}

func (s x25519Scheme) DH(priv KEMPrivateKey, pub KEMPublicKey) ([]byte, error) {
	xPriv, ok := priv.(*x25519PrivateKey)
	if !ok {
		return nil, fmt.Errorf("Private key not suitable for X25519: %+v", priv)
	}

	xPub, ok := pub.(*x25519PublicKey)
	if !ok {
		return nil, fmt.Errorf("Private key not suitable for X25519")
	}

	sharedSecret, err := curve25519.X25519(xPriv.val[:], xPub.val[:])
	return sharedSecret, err
}

func (s x25519Scheme) PublicKeySize() int {
	return 32
}

func (s x25519Scheme) PrivateKeySize() int {
	return 32
}

///////////////////
// ECDH with X448

type x448PrivateKey struct {
	val x448.Key
}

func (priv x448PrivateKey) PublicKey() KEMPublicKey {
	pub := &x448PublicKey{}
	x448.KeyGen(&pub.val, &priv.val)
	return pub
}

type x448PublicKey struct {
	val x448.Key
}

type x448Scheme struct {
	skE KEMPrivateKey
}

func (s x448Scheme) internalKDF() KDFScheme {
	return hkdfScheme{hash: crypto.SHA512}
}

func (s x448Scheme) ID() KEMID {
	return DHKEM_X448
}

func (s x448Scheme) DeriveKeyPair(ikm []byte) (KEMPrivateKey, KEMPublicKey, error) {
	suiteID := kemSuiteFromID(s.ID())
	dkp_prk := s.internalKDF().LabeledExtract(nil, suiteID, "dkp_prk", ikm)
	sk_bytes := s.internalKDF().LabeledExpand(dkp_prk, suiteID, "sk", nil, s.PrivateKeySize())
	sk, err := s.DeserializePrivateKey(sk_bytes)
	if err != nil {
		return nil, nil, err
	} else {
		return sk, sk.PublicKey(), nil
	}
}

func (s x448Scheme) SerializePublicKey(pk KEMPublicKey) []byte {
	if pk == nil {
		return nil
	}
	raw := pk.(*x448PublicKey)
	return raw.val[:]
}

func (s x448Scheme) SerializePrivateKey(sk KEMPrivateKey) []byte {
	if sk == nil {
		return nil
	}
	raw := sk.(*x448PrivateKey)
	return raw.val[:]
}

func (s x448Scheme) DeserializePublicKey(enc []byte) (KEMPublicKey, error) {
	if len(enc) != 56 {
		return nil, fmt.Errorf("Error deserializing X448 public key")
	}

	pub := &x448PublicKey{}
	copy(pub.val[:], enc)
	return pub, nil
}

func (s x448Scheme) DeserializePrivateKey(enc []byte) (KEMPrivateKey, error) {
	if enc == nil {
		return nil, fmt.Errorf("Invalid input")
	}

	if len(enc) != 56 {
		return nil, fmt.Errorf("Error deserializing X448 private key")
	}

	key := &x448PrivateKey{}
	copy(key.val[:], enc[0:56])
	return key, nil
}

func (s x448Scheme) DH(priv KEMPrivateKey, pub KEMPublicKey) ([]byte, error) {
	xPriv, ok := priv.(*x448PrivateKey)
	if !ok {
		return nil, fmt.Errorf("Private key not suitable for X448: %+v", priv)
	}

	xPub, ok := pub.(*x448PublicKey)
	if !ok {
		return nil, fmt.Errorf("Public key not suitable for X448: %+v", pub)
	}

	var sharedSecret, zero x448.Key
	x448.Shared(&sharedSecret, &xPriv.val, &xPub.val)
	if subtle.ConstantTimeCompare(sharedSecret[:], zero[:]) == 1 {
		return nil, fmt.Errorf("bad input point: low order point")
	}

	return sharedSecret[:], nil
}

func (s x448Scheme) PublicKeySize() int {
	return 56
}

func (s x448Scheme) PrivateKeySize() int {
	return 56
}

//////////
// AES-GCM

type aesgcmScheme struct {
	keySize int
}

func (s aesgcmScheme) ID() AEADID {
	switch s.keySize {
	case 16:
		return AEAD_AESGCM128
	case 32:
		return AEAD_AESGCM256
	}
	panic(fmt.Sprintf("Unsupported key size: %d", s.keySize))

}

func (s aesgcmScheme) New(key []byte) (cipher.AEAD, error) {
	if len(key) != s.keySize {
		return nil, fmt.Errorf("Incorrect key size %d != %d", len(key), s.keySize)
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	return cipher.NewGCM(block)
}

func (s aesgcmScheme) KeySize() int {
	return s.keySize
}

func (s aesgcmScheme) NonceSize() int {
	return 12
}

//////////
// ChaCha20-Poly1305

type chachaPolyScheme struct {
}

func (s chachaPolyScheme) ID() AEADID {
	return AEAD_CHACHA20POLY1305
}

func (s chachaPolyScheme) New(key []byte) (cipher.AEAD, error) {
	return chacha20poly1305.New(key)
}

func (s chachaPolyScheme) KeySize() int {
	return chacha20poly1305.KeySize
}

func (s chachaPolyScheme) NonceSize() int {
	return chacha20poly1305.NonceSize
}

//////////
// Export-only AEAD scheme

type exportOnlyScheme struct {
}

func (s exportOnlyScheme) ID() AEADID {
	return AEAD_EXPORT_ONLY
}

func (s exportOnlyScheme) New(key []byte) (cipher.AEAD, error) {
	panic("Not supported")
}

func (s exportOnlyScheme) KeySize() int {
	panic("Not supported")
}

func (s exportOnlyScheme) NonceSize() int {
	panic("Not supported")
}

///////
// HKDF

type hkdfScheme struct {
	hash crypto.Hash
}

func (s hkdfScheme) ID() KDFID {
	switch s.hash {
	case crypto.SHA256:
		return KDF_HKDF_SHA256
	case crypto.SHA384:
		return KDF_HKDF_SHA384
	case crypto.SHA512:
		return KDF_HKDF_SHA512
	}
	panic(fmt.Sprintf("Unsupported hash: %d", s.hash))
}

func (s hkdfScheme) Hash(message []byte) []byte {
	h := s.hash.New()
	h.Write(message)
	return h.Sum(nil)
}

func (s hkdfScheme) Extract(salt, ikm []byte) []byte {
	saltOrZero := salt

	// if [salt is] not provided, it is set to a string of HashLen zeros
	if salt == nil {
		saltOrZero = make([]byte, s.hash.Size())
	}

	h := hmac.New(s.hash.New, saltOrZero)
	h.Write(ikm)
	return h.Sum(nil)
}

func (s hkdfScheme) Expand(prk, info []byte, outLen int) []byte {
	out := []byte{}
	T := []byte{}
	i := byte(1)
	for len(out) < outLen {
		block := append(T, info...)
		block = append(block, i)

		h := hmac.New(s.hash.New, prk)
		h.Write(block)

		T = h.Sum(nil)
		out = append(out, T...)
		i++
	}
	return out[:outLen]
}

func (s hkdfScheme) LabeledExtract(salt []byte, suiteID []byte, label string, ikm []byte) []byte {
	labeledIKM := append([]byte(versionLabel), suiteID...)
	labeledIKM = append(labeledIKM, []byte(label)...)
	labeledIKM = append(labeledIKM, ikm...)
	return s.Extract(salt, labeledIKM)
}

func (s hkdfScheme) LabeledExpand(prk []byte, suiteID []byte, label string, info []byte, L int) []byte {
	if L > (1 << 16) {
		panic("Expand length cannot be larger than 2^16")
	}

	lengthBuffer := make([]byte, 2)
	binary.BigEndian.PutUint16(lengthBuffer, uint16(L))
	labeledLength := append(lengthBuffer, []byte(versionLabel)...)
	labeledInfo := append(labeledLength, suiteID...)
	labeledInfo = append(labeledInfo, []byte(label)...)
	labeledInfo = append(labeledInfo, info...)

	return s.Expand(prk, labeledInfo, L)
}

func (s hkdfScheme) OutputSize() int {
	return s.hash.Size()
}

///////////////////////////
// Pre-defined KEM identifiers

type KEMID uint16

const (
	DHKEM_P256   KEMID = 0x0010
	DHKEM_P521   KEMID = 0x0012
	DHKEM_X25519 KEMID = 0x0020
	DHKEM_X448   KEMID = 0x0021

	// The following identifiers were used for prototype SIKE integration.  Since
	// SIKE has been broken, we have removed this prototype, but in case anyone is
	// using these identifiers, we list them here as reserved.
	reserved1 KEMID = 0xFFFE // Was KEM_SIKE503
	reserved2 KEMID = 0xFFFF // Was KEM_SIKE751
)

var kems = map[KEMID]KEMScheme{
	DHKEM_X25519: &dhkemScheme{group: x25519Scheme{}},
	DHKEM_X448:   &dhkemScheme{group: x448Scheme{}},
	DHKEM_P256:   &dhkemScheme{group: ecdhScheme{curve: elliptic.P256(), KDF: hkdfScheme{hash: crypto.SHA256}}},
	DHKEM_P521:   &dhkemScheme{group: ecdhScheme{curve: elliptic.P521(), KDF: hkdfScheme{hash: crypto.SHA512}}},
}

func newKEMScheme(kemID KEMID) (KEMScheme, bool) {
	switch kemID {
	case DHKEM_X25519:
		return &dhkemScheme{group: x25519Scheme{}}, true
	case DHKEM_X448:
		return &dhkemScheme{group: x448Scheme{}}, true
	case DHKEM_P256:
		return &dhkemScheme{group: ecdhScheme{curve: elliptic.P256(), KDF: hkdfScheme{hash: crypto.SHA256}}}, true
	case DHKEM_P521:
		return &dhkemScheme{group: ecdhScheme{curve: elliptic.P521(), KDF: hkdfScheme{hash: crypto.SHA512}}}, true
	default:
		return nil, false
	}
}

///////////////////////////
// Pre-defined KDF identifiers

type KDFID uint16

const (
	KDF_HKDF_SHA256 KDFID = 0x0001
	KDF_HKDF_SHA384 KDFID = 0x0002
	KDF_HKDF_SHA512 KDFID = 0x0003
)

var kdfs = map[KDFID]KDFScheme{
	KDF_HKDF_SHA256: hkdfScheme{hash: crypto.SHA256},
	KDF_HKDF_SHA384: hkdfScheme{hash: crypto.SHA384},
	KDF_HKDF_SHA512: hkdfScheme{hash: crypto.SHA512},
}

///////////////////////////
// Pre-defined AEAD identifiers

type AEADID uint16

const (
	AEAD_AESGCM128        AEADID = 0x0001
	AEAD_AESGCM256        AEADID = 0x0002
	AEAD_CHACHA20POLY1305 AEADID = 0x0003
	AEAD_EXPORT_ONLY      AEADID = 0xFFFF
)

var aeads = map[AEADID]AEADScheme{
	AEAD_AESGCM128:        aesgcmScheme{keySize: 16},
	AEAD_AESGCM256:        aesgcmScheme{keySize: 32},
	AEAD_CHACHA20POLY1305: chachaPolyScheme{},
	AEAD_EXPORT_ONLY:      exportOnlyScheme{},
}

func AssembleCipherSuite(kemID KEMID, kdfID KDFID, aeadID AEADID) (CipherSuite, error) {
	kem, ok := newKEMScheme(kemID)
	if !ok {
		return CipherSuite{}, fmt.Errorf("Unknown KEM id")
	}

	kdf, ok := kdfs[kdfID]
	if !ok {
		return CipherSuite{}, fmt.Errorf("Unknown KDF id")
	}

	aead, ok := aeads[aeadID]
	if !ok {
		return CipherSuite{}, fmt.Errorf("Unknown AEAD id")
	}

	return CipherSuite{
		KEM:  kem,
		KDF:  kdf,
		AEAD: aead,
	}, nil
}

//////////
// Helpers

func kemSuiteFromID(id KEMID) []byte {
	idBuffer := make([]byte, 2)
	binary.BigEndian.PutUint16(idBuffer, uint16(id))
	return append([]byte("KEM"), idBuffer...)
}
