package noise

import (
	
	
	
	
	
	
	
	

	
	
	
	
)

// A DHKey is a keypair used for Diffie-Hellman key agreement.
type DHKey struct {
	Private []byte
	Public  []byte
}

// A DHFunc implements Diffie-Hellman key agreement.
type DHFunc interface {
	// GenerateKeypair generates a new keypair using random as a source of
	// entropy.
	GenerateKeypair(random io.Reader) (DHKey, error)

	// DH performs a Diffie-Hellman calculation between the provided private and
	// public keys and returns the result.
	DH(privkey, pubkey []byte) ([]byte, error)

	// DHLen is the number of bytes returned by DH.
	DHLen() int

	// DHName is the name of the DH function.
	DHName() string
}

// A HashFunc implements a cryptographic hash function.
type HashFunc interface {
	// Hash returns a hash state.
	Hash() hash.Hash

	// HashName is the name of the hash function.
	HashName() string
}

// A CipherFunc implements an AEAD symmetric cipher.
type CipherFunc interface {
	// Cipher initializes the algorithm with the provided key and returns a Cipher.
	Cipher(k [32]byte) Cipher

	// CipherName is the name of the cipher.
	CipherName() string
}

// A Cipher is a AEAD cipher that has been initialized with a key.
type Cipher interface {
	// Encrypt encrypts the provided plaintext with a nonce and then appends the
	// ciphertext to out along with an authentication tag over the ciphertext
	// and optional authenticated data.
	Encrypt(out []byte, n uint64, ad, plaintext []byte) []byte

	// Decrypt authenticates the ciphertext and optional authenticated data and
	// then decrypts the provided ciphertext using the provided nonce and
	// appends it to out.
	Decrypt(out []byte, n uint64, ad, ciphertext []byte) ([]byte, error)
}

// A CipherSuite is a set of cryptographic primitives used in a Noise protocol.
// It should be constructed with NewCipherSuite.
type CipherSuite interface {
	DHFunc
	CipherFunc
	HashFunc
	Name() []byte
}

// NewCipherSuite returns a CipherSuite constructed from the specified
// primitives.
func ( DHFunc,  CipherFunc,  HashFunc) CipherSuite {
	return ciphersuite{
		DHFunc:     ,
		CipherFunc: ,
		HashFunc:   ,
		name:       []byte(.DHName() + "_" + .CipherName() + "_" + .HashName()),
	}
}

type ciphersuite struct {
	DHFunc
	CipherFunc
	HashFunc
	name []byte
}

func ( ciphersuite) () []byte { return .name }

// DH25519 is the Curve25519 ECDH function.
var DH25519 DHFunc = dh25519{}

type dh25519 struct{}

func (dh25519) ( io.Reader) (DHKey, error) {
	 := make([]byte, 32)
	if  == nil {
		 = rand.Reader
	}
	if ,  := io.ReadFull(, );  != nil {
		return DHKey{}, 
	}
	,  := curve25519.X25519(, curve25519.Basepoint)
	if  != nil {
		return DHKey{}, 
	}
	return DHKey{Private: , Public: }, nil
}

func (dh25519) (,  []byte) ([]byte, error) {
	return curve25519.X25519(, )
}

func (dh25519) () int     { return 32 }
func (dh25519) () string { return "25519" }

type cipherFn struct {
	fn   func([32]byte) Cipher
	name string
}

func ( cipherFn) ( [32]byte) Cipher { return .fn() }
func ( cipherFn) () string       { return .name }

// CipherAESGCM is the AES256-GCM AEAD cipher.
var CipherAESGCM CipherFunc = cipherFn{cipherAESGCM, "AESGCM"}

func cipherAESGCM( [32]byte) Cipher {
	,  := aes.NewCipher([:])
	if  != nil {
		panic()
	}
	,  := cipher.NewGCM()
	if  != nil {
		panic()
	}
	return aeadCipher{
		,
		func( uint64) []byte {
			var  [12]byte
			binary.BigEndian.PutUint64([4:], )
			return [:]
		},
	}
}

// CipherChaChaPoly is the ChaCha20-Poly1305 AEAD cipher construction.
var CipherChaChaPoly CipherFunc = cipherFn{cipherChaChaPoly, "ChaChaPoly"}

func cipherChaChaPoly( [32]byte) Cipher {
	,  := chacha20poly1305.New([:])
	if  != nil {
		panic()
	}
	return aeadCipher{
		,
		func( uint64) []byte {
			var  [12]byte
			binary.LittleEndian.PutUint64([4:], )
			return [:]
		},
	}
}

type aeadCipher struct {
	cipher.AEAD
	nonce func(uint64) []byte
}

func ( aeadCipher) ( []byte,  uint64, ,  []byte) []byte {
	return .Seal(, .nonce(), , )
}

func ( aeadCipher) ( []byte,  uint64, ,  []byte) ([]byte, error) {
	return .Open(, .nonce(), , )
}

type hashFn struct {
	fn   func() hash.Hash
	name string
}

func ( hashFn) () hash.Hash  { return .fn() }
func ( hashFn) () string { return .name }

// HashSHA256 is the SHA-256 hash function.
var HashSHA256 HashFunc = hashFn{sha256.New, "SHA256"}

// HashSHA512 is the SHA-512 hash function.
var HashSHA512 HashFunc = hashFn{sha512.New, "SHA512"}

func blake2bNew() hash.Hash {
	,  := blake2b.New512(nil)
	if  != nil {
		panic()
	}
	return 
}

// HashBLAKE2b is the BLAKE2b hash function.
var HashBLAKE2b HashFunc = hashFn{blake2bNew, "BLAKE2b"}

func blake2sNew() hash.Hash {
	,  := blake2s.New256(nil)
	if  != nil {
		panic()
	}
	return 
}

// HashBLAKE2s is the BLAKE2s hash function.
var HashBLAKE2s HashFunc = hashFn{blake2sNew, "BLAKE2s"}