// Copyright Simcord LLC
// This file is part of the Bitbon System Blockchain Node library.
// This file has been modified, you can find the original version by following the link
// <https://github.com/ethereum/go-ethereum>
//
// The Bitbon System Blockchain Node library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Bitbon System Blockchain Node library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Bitbon System Blockchain Node library. If not, see <http://www.gnu.org/licenses/>.

package discover

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/ecdsa"
	"crypto/elliptic"
	crand "crypto/rand"
	"crypto/sha256"
	"errors"
	"fmt"
	"hash"
	"net"
	"time"

	"github.com/simcord-llc/bitbon-system-blockchain/log"

	"github.com/simcord-llc/bitbon-system-blockchain/common/math"
	"github.com/simcord-llc/bitbon-system-blockchain/common/mclock"
	"github.com/simcord-llc/bitbon-system-blockchain/crypto"
	"github.com/simcord-llc/bitbon-system-blockchain/p2p/enode"
	"github.com/simcord-llc/bitbon-system-blockchain/p2p/enr"
	"github.com/simcord-llc/bitbon-system-blockchain/rlp"
	"golang.org/x/crypto/hkdf"
)

// Discovery v5 packet types.
const (
	p_pingV5 byte = iota + 1
	p_pongV5
	p_findnodeV5
	p_nodesV5
	p_requestTicketV5
	p_ticketV5
	p_regtopicV5
	p_regconfirmationV5
	p_topicqueryV5
	p_unknownV5   = byte(255) // any non-decryptable packet
	p_whoareyouV5 = byte(254) // the WHOAREYOU packet
)

// Discovery v5 packet structures.
type (
	// unknownV5 represents any packet that can't be decrypted.
	unknownV5 struct {
		AuthTag []byte
	}

	// WHOAREYOU contains the handshake challenge.
	whoareyouV5 struct {
		AuthTag   []byte
		IDNonce   [32]byte // To be signed by recipient.
		RecordSeq uint64   // ENR sequence number of recipient

		node *enode.Node
		sent mclock.AbsTime
	}

	// PING is sent during liveness checks.
	pingV5 struct {
		ReqID  []byte
		ENRSeq uint64
	}

	// PONG is the reply to PING.
	pongV5 struct {
		ReqID  []byte
		ENRSeq uint64
		ToIP   net.IP // These fields should mirror the UDP envelope address of the ping
		ToPort uint16 // packet, which provides a way to discover the the external address (after NAT).
	}

	// FINDNODE is a query for nodes in the given bucket.
	findnodeV5 struct {
		ReqID    []byte
		Distance uint
	}

	// NODES is the reply to FINDNODE and TOPICQUERY.
	nodesV5 struct {
		ReqID []byte
		Total uint8
		Nodes []*enr.Record
	}

	// REQUESTTICKET requests a ticket for a topic queue.
	requestTicketV5 struct {
		ReqID []byte
		Topic []byte
	}

	// TICKET is the response to REQUESTTICKET.
	ticketV5 struct {
		ReqID  []byte
		Ticket []byte
	}

	// REGTOPIC registers the sender in a topic queue using a ticket.
	regtopicV5 struct {
		ReqID  []byte
		Ticket []byte
		ENR    *enr.Record
	}

	// REGCONFIRMATION is the reply to REGTOPIC.
	regconfirmationV5 struct {
		ReqID      []byte
		Registered bool
	}

	// TOPICQUERY asks for nodes with the given topic.
	topicqueryV5 struct {
		ReqID []byte
		Topic []byte
	}
)

const (
	// Encryption/authentication parameters.
	authSchemeName   = "gcm"
	aesKeySize       = 16
	gcmNonceSize     = 12
	idNoncePrefix    = "discovery-id-nonce"
	handshakeTimeout = time.Second
)

var (
	errTooShort               = errors.New("packet too short")
	errUnexpectedHandshake    = errors.New("unexpected auth response, not in handshake")
	errHandshakeNonceMismatch = errors.New("wrong nonce in auth response")
	errInvalidAuthKey         = errors.New("invalid ephemeral pubkey")
	errUnknownAuthScheme      = errors.New("unknown auth scheme in handshake")
	errNoRecord               = errors.New("expected ENR in handshake but none sent")
	errInvalidNonceSig        = errors.New("invalid ID nonce signature")
	zeroNonce                 = make([]byte, gcmNonceSize)
)

// wireCodec encodes and decodes discovery v5 packets.
type wireCodec struct {
	sha256           hash.Hash
	localnode        *enode.LocalNode
	privkey          *ecdsa.PrivateKey
	myChtagHash      enode.ID
	myWhoareyouMagic []byte

	sc *sessionCache
}

type handshakeSecrets struct {
	writeKey, readKey, authRespKey []byte
}

type authHeader struct {
	authHeaderList
	isHandshake bool
}

type authHeaderList struct {
	Auth         []byte   // authentication info of packet
	IDNonce      [32]byte // IDNonce of WHOAREYOU
	Scheme       string   // name of encryption/authentication scheme
	EphemeralKey []byte   // ephemeral public key
	Response     []byte   // encrypted authResponse
}

type authResponse struct {
	Version   uint
	Signature []byte
	Record    *enr.Record `rlp:"nil"` // sender's record
}

func (h *authHeader) DecodeRLP(r *rlp.Stream) error {
	k, _, err := r.Kind()
	if err != nil {
		return err
	}
	if k == rlp.Byte || k == rlp.String {
		return r.Decode(&h.Auth)
	}
	h.isHandshake = true
	return r.Decode(&h.authHeaderList)
}

// ephemeralKey decodes the ephemeral public key in the header.
func (h *authHeaderList) ephemeralKey(curve elliptic.Curve) *ecdsa.PublicKey {
	var key encPubkey
	copy(key[:], h.EphemeralKey)
	pubkey, _ := decodePubkey(curve, key)
	return pubkey
}

// newWireCodec creates a wire codec.
func newWireCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *wireCodec {
	c := &wireCodec{
		sha256:    sha256.New(),
		localnode: ln,
		privkey:   key,
		sc:        newSessionCache(1024, clock),
	}
	// Create magic strings for packet matching.
	self := ln.ID()
	c.myWhoareyouMagic = c.sha256sum(self[:], []byte("WHOAREYOU"))
	copy(c.myChtagHash[:], c.sha256sum(self[:]))
	return c
}

// encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
// node.
func (c *wireCodec) encode(id enode.ID, addr string, packet packetV5, challenge *whoareyouV5) ([]byte, []byte, error) {
	if packet.kind() == p_whoareyouV5 {
		p := packet.(*whoareyouV5)
		enc, err := c.encodeWhoareyou(id, p)
		if err == nil {
			c.sc.storeSentHandshake(id, addr, p)
		}
		return enc, nil, err
	}
	// Ensure calling code sets node if needed.
	if challenge != nil && challenge.node == nil {
		panic("BUG: missing challenge.node in encode")
	}
	writeKey := c.sc.writeKey(id, addr)
	if writeKey != nil || challenge != nil {
		return c.encodeEncrypted(id, addr, packet, writeKey, challenge)
	}
	return c.encodeRandom(id)
}

// encodeRandom encodes a random packet.
func (c *wireCodec) encodeRandom(toID enode.ID) ([]byte, []byte, error) {
	tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID())
	r := make([]byte, 44)
	if _, err := crand.Read(r); err != nil {
		return nil, nil, err
	}
	nonce := make([]byte, gcmNonceSize)
	if _, err := crand.Read(nonce); err != nil {
		return nil, nil, fmt.Errorf("can't get random data: %v", err)
	}
	b := new(bytes.Buffer)
	b.Write(tag[:])
	rlp.Encode(b, nonce)
	b.Write(r)
	return b.Bytes(), nonce, nil
}

// encodeWhoareyou encodes WHOAREYOU.
func (c *wireCodec) encodeWhoareyou(toID enode.ID, packet *whoareyouV5) ([]byte, error) {
	// Sanity check node field to catch misbehaving callers.
	if packet.RecordSeq > 0 && packet.node == nil {
		panic("BUG: missing node in whoareyouV5 with non-zero seq")
	}
	b := new(bytes.Buffer)
	b.Write(c.sha256sum(toID[:], []byte("WHOAREYOU")))
	err := rlp.Encode(b, packet)
	return b.Bytes(), err
}

// encodeEncrypted encodes an encrypted packet.
func (c *wireCodec) encodeEncrypted(toID enode.ID, toAddr string, packet packetV5, writeKey []byte, challenge *whoareyouV5) (enc, authTag []byte, err error) {
	nonce := make([]byte, gcmNonceSize)
	if _, err = crand.Read(nonce); err != nil {
		return nil, nil, fmt.Errorf("can't get random data: %v", err)
	}

	var headEnc []byte
	if challenge == nil {
		// Regular packet, use existing key and simply encode nonce.
		headEnc, _ = rlp.EncodeToBytes(nonce)
	} else {
		// We're answering WHOAREYOU, generate new keys and encrypt with those.
		var (
			header *authHeaderList
			sec    *handshakeSecrets
		)
		header, sec, err = c.makeAuthHeader(nonce, challenge)
		if err != nil {
			return nil, nil, err
		}
		if headEnc, err = rlp.EncodeToBytes(header); err != nil {
			return nil, nil, err
		}
		c.sc.storeNewSession(toID, toAddr, sec.readKey, sec.writeKey)
		writeKey = sec.writeKey
	}

	// Encode the packet.
	body := new(bytes.Buffer)
	body.WriteByte(packet.kind())
	if err := rlp.Encode(body, packet); err != nil {
		return nil, nil, err
	}
	tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID())
	headsize := len(tag) + len(headEnc)
	headbuf := make([]byte, headsize)
	copy(headbuf, tag[:])
	copy(headbuf[len(tag):], headEnc)

	// Encrypt the body.
	enc, err = encryptGCM(headbuf, writeKey, nonce, body.Bytes(), tag[:])
	return enc, nonce, err
}

// encodeAuthHeader creates the auth header on a call packet following WHOAREYOU.
func (c *wireCodec) makeAuthHeader(nonce []byte, challenge *whoareyouV5) (*authHeaderList, *handshakeSecrets, error) {
	resp := &authResponse{Version: 5}

	// Add our record to response if it's newer than what remote
	// side has.
	ln := c.localnode.Node()
	if challenge.RecordSeq < ln.Seq() {
		resp.Record = ln.Record()
	}

	// Create the ephemeral key. This needs to be first because the
	// key is part of the ID nonce signature.
	var remotePubkey = new(ecdsa.PublicKey)
	if err := challenge.node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
		return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
	}
	ephkey, err := crypto.GenerateKey()
	if err != nil {
		return nil, nil, fmt.Errorf("can't generate ephemeral key")
	}
	ephpubkey := encodePubkey(&ephkey.PublicKey)

	// Add ID nonce signature to response.
	idsig, err := c.signIDNonce(challenge.IDNonce[:], ephpubkey[:])
	if err != nil {
		return nil, nil, fmt.Errorf("can't sign: %v", err)
	}
	resp.Signature = idsig

	// Create session keys.
	sec := c.deriveKeys(c.localnode.ID(), challenge.node.ID(), ephkey, remotePubkey, challenge)
	if sec == nil {
		return nil, nil, fmt.Errorf("key derivation failed")
	}

	// Encrypt the authentication response and assemble the auth header.
	respRLP, err := rlp.EncodeToBytes(resp)
	if err != nil {
		return nil, nil, fmt.Errorf("can't encode auth response: %v", err)
	}
	respEnc, err := encryptGCM(nil, sec.authRespKey, zeroNonce, respRLP, nil)
	if err != nil {
		return nil, nil, fmt.Errorf("can't encrypt auth response: %v", err)
	}
	head := &authHeaderList{
		Auth:         nonce,
		Scheme:       authSchemeName,
		IDNonce:      challenge.IDNonce,
		EphemeralKey: ephpubkey[:],
		Response:     respEnc,
	}
	return head, sec, err
}

// deriveKeys generates session keys using elliptic-curve Diffie-Hellman key agreement.
func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, challenge *whoareyouV5) *handshakeSecrets {
	eph := ecdh(priv, pub)
	if eph == nil {
		return nil
	}

	info := []byte("discovery v5 key agreement")
	info = append(info, n1[:]...)
	info = append(info, n2[:]...)
	kdf := hkdf.New(c.sha256reset, eph, challenge.IDNonce[:], info)
	sec := handshakeSecrets{
		writeKey:    make([]byte, aesKeySize),
		readKey:     make([]byte, aesKeySize),
		authRespKey: make([]byte, aesKeySize),
	}
	kdf.Read(sec.writeKey)
	kdf.Read(sec.readKey)
	kdf.Read(sec.authRespKey)
	for i := range eph {
		eph[i] = 0
	}
	return &sec
}

// signIDNonce creates the ID nonce signature.
func (c *wireCodec) signIDNonce(nonce, ephkey []byte) ([]byte, error) {
	idsig, err := crypto.Sign(c.idNonceHash(nonce, ephkey), c.privkey)
	if err != nil {
		return nil, fmt.Errorf("can't sign: %v", err)
	}
	return idsig[:len(idsig)-1], nil // remove recovery ID
}

// idNonceHash computes the hash of id nonce with prefix.
func (c *wireCodec) idNonceHash(nonce, ephkey []byte) []byte {
	h := c.sha256reset()
	h.Write([]byte(idNoncePrefix))
	h.Write(nonce)
	h.Write(ephkey)
	return h.Sum(nil)
}

// decode decodes a discovery packet.
func (c *wireCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) {
	// Delete timed-out handshakes. This must happen before decoding to avoid
	// processing the same handshake twice.
	c.sc.handshakeGC()

	if len(input) < 32 {
		return enode.ID{}, nil, nil, errTooShort
	}
	if bytes.HasPrefix(input, c.myWhoareyouMagic) {
		p, err := c.decodeWhoareyou(input)
		return enode.ID{}, nil, p, err
	}
	sender := xorTag(input[:32], c.myChtagHash)
	p, n, err := c.decodeEncrypted(sender, addr, input)
	return sender, n, p, err
}

// decodeWhoareyou decode a WHOAREYOU packet.
func (c *wireCodec) decodeWhoareyou(input []byte) (packetV5, error) {
	packet := new(whoareyouV5)
	err := rlp.DecodeBytes(input[32:], packet)
	return packet, err
}

// decodeEncrypted decodes an encrypted discovery packet.
func (c *wireCodec) decodeEncrypted(fromID enode.ID, fromAddr string, input []byte) (packetV5, *enode.Node, error) {
	// Decode packet header.
	var head authHeader
	r := bytes.NewReader(input[32:])
	err := rlp.Decode(r, &head)
	if err != nil {
		return nil, nil, err
	}

	// Decrypt and process auth response.
	readKey, node, err := c.decodeAuth(fromID, fromAddr, &head)
	if err != nil {
		return nil, nil, err
	}

	// Decrypt and decode the packet body.
	headsize := len(input) - r.Len()
	bodyEnc := input[headsize:]
	body, err := decryptGCM(readKey, head.Auth, bodyEnc, input[:32])
	if err != nil {
		if !head.isHandshake {
			// Can't decrypt, start handshake.
			return &unknownV5{AuthTag: head.Auth}, nil, nil
		}
		return nil, nil, fmt.Errorf("handshake failed: %v", err)
	}
	if len(body) == 0 {
		return nil, nil, errTooShort
	}
	p, err := decodePacketBodyV5(body[0], body[1:])
	return p, node, err
}

// decodeAuth processes an auth header.
func (c *wireCodec) decodeAuth(fromID enode.ID, fromAddr string, head *authHeader) ([]byte, *enode.Node, error) {
	if !head.isHandshake {
		return c.sc.readKey(fromID, fromAddr), nil, nil
	}

	// Remote is attempting handshake. Verify against our last WHOAREYOU.
	challenge := c.sc.getHandshake(fromID, fromAddr)
	if challenge == nil {
		return nil, nil, errUnexpectedHandshake
	}
	if head.IDNonce != challenge.IDNonce {
		return nil, nil, errHandshakeNonceMismatch
	}
	sec, n, err := c.decodeAuthResp(fromID, fromAddr, &head.authHeaderList, challenge)
	if err != nil {
		return nil, n, err
	}
	// Swap keys to match remote.
	sec.readKey, sec.writeKey = sec.writeKey, sec.readKey
	c.sc.storeNewSession(fromID, fromAddr, sec.readKey, sec.writeKey)
	c.sc.deleteHandshake(fromID, fromAddr)
	return sec.readKey, n, err
}

// decodeAuthResp decodes and verifies an authentication response.
func (c *wireCodec) decodeAuthResp(fromID enode.ID, fromAddr string, head *authHeaderList, challenge *whoareyouV5) (*handshakeSecrets, *enode.Node, error) {
	// Decrypt / decode the response.
	if head.Scheme != authSchemeName {
		return nil, nil, errUnknownAuthScheme
	}
	ephkey := head.ephemeralKey(c.privkey.Curve)
	if ephkey == nil {
		return nil, nil, errInvalidAuthKey
	}
	sec := c.deriveKeys(fromID, c.localnode.ID(), c.privkey, ephkey, challenge)
	respPT, err := decryptGCM(sec.authRespKey, zeroNonce, head.Response, nil)
	if err != nil {
		return nil, nil, fmt.Errorf("can't decrypt auth response header: %v", err)
	}
	var resp authResponse
	if err = rlp.DecodeBytes(respPT, &resp); err != nil {
		return nil, nil, fmt.Errorf("invalid auth response: %v", err)
	}

	// Verify response node record. The remote node should include the record
	// if we don't have one or if ours is older than the latest version.
	node := challenge.node
	if resp.Record != nil {
		if node == nil || node.Seq() < resp.Record.Seq() {
			var n *enode.Node
			n, err = enode.New(enode.ValidSchemes, resp.Record)
			if err != nil {
				return nil, nil, fmt.Errorf("invalid node record: %v", err)
			}
			if n.ID() != fromID {
				return nil, nil, fmt.Errorf("record in auth respose has wrong ID: %v", n.ID())
			}
			node = n
		}
	}
	if node == nil {
		return nil, nil, errNoRecord
	}

	// Verify ID nonce signature.
	err = c.verifyIDSignature(challenge.IDNonce[:], head.EphemeralKey, resp.Signature, node)
	if err != nil {
		return nil, nil, err
	}
	return sec, node, nil
}

// verifyIDSignature checks that signature over idnonce was made by the node with given record.
func (c *wireCodec) verifyIDSignature(nonce, ephkey, sig []byte, n *enode.Node) error {
	switch idscheme := n.Record().IdentityScheme(); idscheme {
	case "v4":
		var pk ecdsa.PublicKey
		n.Load((*enode.Secp256k1)(&pk)) // cannot fail because record is valid
		if !crypto.VerifySignature(crypto.FromECDSAPub(&pk), c.idNonceHash(nonce, ephkey), sig) {
			return errInvalidNonceSig
		}
		return nil
	default:
		return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
	}
}

// decodePacketBody decodes the body of an encrypted discovery packet.
func decodePacketBodyV5(ptype byte, body []byte) (packetV5, error) {
	var dec packetV5
	switch ptype {
	case p_pingV5:
		dec = new(pingV5)
	case p_pongV5:
		dec = new(pongV5)
	case p_findnodeV5:
		dec = new(findnodeV5)
	case p_nodesV5:
		dec = new(nodesV5)
	case p_requestTicketV5:
		dec = new(requestTicketV5)
	case p_ticketV5:
		dec = new(ticketV5)
	case p_regtopicV5:
		dec = new(regtopicV5)
	case p_regconfirmationV5:
		dec = new(regconfirmationV5)
	case p_topicqueryV5:
		dec = new(topicqueryV5)
	default:
		return nil, fmt.Errorf("unknown packet type %d", ptype)
	}
	if err := rlp.DecodeBytes(body, dec); err != nil {
		return nil, err
	}
	return dec, nil
}

// sha256reset returns the shared hash instance.
func (c *wireCodec) sha256reset() hash.Hash {
	c.sha256.Reset()
	return c.sha256
}

// sha256sum computes sha256 on the concatenation of inputs.
func (c *wireCodec) sha256sum(inputs ...[]byte) []byte {
	c.sha256.Reset()
	for _, b := range inputs {
		c.sha256.Write(b)
	}
	return c.sha256.Sum(nil)
}

func xorTag(a []byte, b enode.ID) enode.ID {
	var r enode.ID
	for i := range r {
		r[i] = a[i] ^ b[i]
	}
	return r
}

// ecdh creates a shared secret.
func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
	if secX == nil {
		return nil
	}
	sec := make([]byte, 33)
	sec[0] = 0x02 | byte(secY.Bit(0))
	math.ReadBits(secX, sec[1:])
	return sec
}

// encryptGCM encrypts pt using AES-GCM with the given key and nonce.
func encryptGCM(dest, key, nonce, pt, authData []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		log.Error("can't create block cipher", "err", err)
		return nil, err
	}
	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
	if err != nil {
		log.Error("can't create GCM", "err", err)
		return nil, err
	}
	return aesgcm.Seal(dest, nonce, pt, authData), nil
}

// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("can't create block cipher: %v", err)
	}
	if len(nonce) != gcmNonceSize {
		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
	}
	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
	if err != nil {
		return nil, fmt.Errorf("can't create GCM: %v", err)
	}
	pt := make([]byte, 0, len(ct))
	return aesgcm.Open(pt, nonce, ct, authData)
}
