package keychain

import (
	"fmt"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
	"github.com/btcsuite/btcd/btcec/v2/schnorr"
)

const (
	// KeyDerivationVersionLegacy is the previous version of the key
	// derivation schema defined below. We use a version as this means that
	// we'll be able to accept new seed in the future and be able to discern
	// if the software is compatible with the version of the seed.
	KeyDerivationVersionLegacy = 0

	// KeyDerivationVersionTaproot is the most recent version of the key
	// derivation scheme that marks the introduction of the Taproot
	// derivation with BIP0086 support.
	KeyDerivationVersionTaproot = 1

	// CurrentKeyDerivationVersion is the current default key derivation
	// version that is used for new seeds.
	CurrentKeyDerivationVersion = KeyDerivationVersionTaproot

	// BIP0043Purpose is the "purpose" value that we'll use for the first
	// version or our key derivation scheme. All keys are expected to be
	// derived from this purpose, then the particular coin type of the
	// chain where the keys are to be used.  Slightly adhering to BIP0043
	// allows us to not deviate too far from a widely used standard, and
	// also fits into existing implementations of the BIP's template.
	//
	// NOTE: BRICK SQUUUUUAD.
	BIP0043Purpose = 1017
)

// IsKnownVersion returns true if the given version is one of the known
// derivation scheme versions as defined by this package.
func IsKnownVersion(internalVersion uint8) bool {
	return internalVersion == KeyDerivationVersionLegacy ||
		internalVersion == KeyDerivationVersionTaproot
}

var (
	// MaxKeyRangeScan is the maximum number of keys that we'll attempt to
	// scan with if a caller knows the public key, but not the KeyLocator
	// and wishes to derive a private key.
	MaxKeyRangeScan = 100000

	// ErrCannotDerivePrivKey is returned when DerivePrivKey is unable to
	// derive a private key given only the public key and target key
	// family.
	ErrCannotDerivePrivKey = fmt.Errorf("unable to derive private key")
)

// KeyFamily represents a "family" of keys that will be used within various
// contracts created by lnd. These families are meant to be distinct branches
// within the HD key chain of the backing wallet. Usage of key families within
// the interface below are strict in order to promote integrability and the
// ability to restore all keys given a user master seed backup.
//
// The key derivation in this file follows the following hierarchy based on
// BIP43:
//
//   - m/1017'/coinType'/keyFamily'/0/index
type KeyFamily uint32

const (
	// KeyFamilyMultiSig are keys to be used within multi-sig scripts.
	KeyFamilyMultiSig KeyFamily = 0

	// KeyFamilyRevocationBase are keys that are used within channels to
	// create revocation basepoints that the remote party will use to
	// create revocation keys for us.
	KeyFamilyRevocationBase KeyFamily = 1

	// KeyFamilyHtlcBase are keys used within channels that will be
	// combined with per-state randomness to produce public keys that will
	// be used in HTLC scripts.
	KeyFamilyHtlcBase KeyFamily = 2

	// KeyFamilyPaymentBase are keys used within channels that will be
	// combined with per-state randomness to produce public keys that will
	// be used in scripts that pay directly to us without any delay.
	KeyFamilyPaymentBase KeyFamily = 3

	// KeyFamilyDelayBase are keys used within channels that will be
	// combined with per-state randomness to produce public keys that will
	// be used in scripts that pay to us, but require a CSV delay before we
	// can sweep the funds.
	KeyFamilyDelayBase KeyFamily = 4

	// KeyFamilyRevocationRoot is a family of keys which will be used to
	// derive the root of a revocation tree for a particular channel.
	KeyFamilyRevocationRoot KeyFamily = 5

	// KeyFamilyNodeKey is a family of keys that will be used to derive
	// keys that will be advertised on the network to represent our current
	// "identity" within the network. Peers will need our latest node key
	// in order to establish a transport session with us on the Lightning
	// p2p level (BOLT-0008).
	KeyFamilyNodeKey KeyFamily = 6

	// KeyFamilyBaseEncryption is the family of keys that will be used to
	// derive keys that we use to encrypt and decrypt any general blob data
	// like static channel backups and the TLS private key. Often used when
	// encrypting files on disk.
	KeyFamilyBaseEncryption KeyFamily = 7

	// KeyFamilyTowerSession is the family of keys that will be used to
	// derive session keys when negotiating sessions with watchtowers. The
	// session keys are limited to the lifetime of the session and are used
	// to increase privacy in the watchtower protocol.
	KeyFamilyTowerSession KeyFamily = 8

	// KeyFamilyTowerID is the family of keys used to derive the public key
	// of a watchtower. This made distinct from the node key to offer a form
	// of rudimentary whitelisting, i.e. via knowledge of the pubkey,
	// preventing others from having full access to the tower just as a
	// result of knowing the node key.
	KeyFamilyTowerID KeyFamily = 9
)

// VersionZeroKeyFamilies is a slice of all the known key families for first
// version of the key derivation schema defined in this package.
var VersionZeroKeyFamilies = []KeyFamily{
	KeyFamilyMultiSig,
	KeyFamilyRevocationBase,
	KeyFamilyHtlcBase,
	KeyFamilyPaymentBase,
	KeyFamilyDelayBase,
	KeyFamilyRevocationRoot,
	KeyFamilyNodeKey,
	KeyFamilyBaseEncryption,
	KeyFamilyTowerSession,
	KeyFamilyTowerID,
}

// KeyLocator is a two-tuple that can be used to derive *any* key that has ever
// been used under the key derivation mechanisms described in this file.
// Version 0 of our key derivation schema uses the following BIP43-like
// derivation:
//
//   - m/1017'/coinType'/keyFamily'/0/index
//
// Our purpose is 1017 (chosen arbitrary for now), and the coin type will vary
// based on which coin/chain the channels are being created on. The key family
// are actually just individual "accounts" in the nomenclature of BIP43. By
// default we assume a branch of 0 (external). Finally, the key index (which
// will vary per channel and use case) is the final element which allows us to
// deterministically derive keys.
type KeyLocator struct {
	// TODO(roasbeef): add the key scope as well??

	// Family is the family of key being identified.
	Family KeyFamily

	// Index is the precise index of the key being identified.
	Index uint32
}

// IsEmpty returns true if a KeyLocator is "empty". This may be the case where
// we learn of a key from a remote party for a contract, but don't know the
// precise details of its derivation (as we don't know the private key!).
func (k KeyLocator) IsEmpty() bool {
	return k.Family == 0 && k.Index == 0
}

// KeyDescriptor wraps a KeyLocator and also optionally includes a public key.
// Either the KeyLocator must be non-empty, or the public key pointer be
// non-nil. This will be used by the KeyRing interface to lookup arbitrary
// private keys, and also within the SignDescriptor struct to locate precisely
// which keys should be used for signing.
type KeyDescriptor struct {
	// KeyLocator is the internal KeyLocator of the descriptor.
	KeyLocator

	// PubKey is an optional public key that fully describes a target key.
	// If this is nil, the KeyLocator MUST NOT be empty.
	PubKey *btcec.PublicKey
}

// KeyRing is the primary interface that will be used to perform public
// derivation of various keys used within the peer-to-peer network, and also
// within any created contracts. All derivation required by the KeyRing is
// based off of public derivation, so a system with only an extended public key
// (for the particular purpose+family) can derive this set of keys.
type KeyRing interface {
	// DeriveNextKey attempts to derive the *next* key within the key
	// family (account in BIP43) specified. This method should return the
	// next external child within this branch.
	DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error)

	// DeriveKey attempts to derive an arbitrary key specified by the
	// passed KeyLocator. This may be used in several recovery scenarios,
	// or when manually rotating something like our current default node
	// key.
	DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error)
}

// SecretKeyRing is a ring similar to the regular KeyRing interface, but it is
// also able to derive *private keys*. As this is a super-set of the regular
// KeyRing, we also expect the SecretKeyRing to implement the fully KeyRing
// interface. The methods in this struct may be used to extract the node key in
// order to accept inbound network connections, or to do manual signing for
// recovery purposes.
type SecretKeyRing interface {
	KeyRing

	ECDHRing

	MessageSignerRing

	// DerivePrivKey attempts to derive the private key that corresponds to
	// the passed key descriptor.  If the public key is set, then this
	// method will perform an in-order scan over the key set, with a max of
	// MaxKeyRangeScan keys. In order for this to work, the caller MUST set
	// the KeyFamily within the partially populated KeyLocator.
	DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error)
}

// MessageSignerRing is an interface that abstracts away basic low-level ECDSA
// signing on keys within a key ring.
type MessageSignerRing interface {
	// SignMessage signs the given message, single or double SHA256 hashing
	// it first, with the private key described in the key locator.
	SignMessage(keyLoc KeyLocator, msg []byte,
		doubleHash bool) (*ecdsa.Signature, error)

	// SignMessageCompact signs the given message, single or double SHA256
	// hashing it first, with the private key described in the key locator
	// and returns the signature in the compact, public key recoverable
	// format.
	SignMessageCompact(keyLoc KeyLocator, msg []byte,
		doubleHash bool) ([]byte, error)

	// SignMessageSchnorr signs the given message, single or double SHA256
	// hashing it first, with the private key described in the key locator
	// and the optional Taproot tweak applied to the private key.
	SignMessageSchnorr(keyLoc KeyLocator, msg []byte,
		doubleHash bool, taprootTweak []byte,
		tag []byte) (*schnorr.Signature, error)
}

// SingleKeyMessageSigner is an abstraction interface that hides the
// implementation of the low-level ECDSA signing operations by wrapping a
// single, specific private key.
type SingleKeyMessageSigner interface {
	// PubKey returns the public key of the wrapped private key.
	PubKey() *btcec.PublicKey

	// KeyLocator returns the locator that describes the wrapped private
	// key.
	KeyLocator() KeyLocator

	// SignMessage signs the given message, single or double SHA256 hashing
	// it first, with the wrapped private key.
	SignMessage(message []byte, doubleHash bool) (*ecdsa.Signature, error)

	// SignMessageCompact signs the given message, single or double SHA256
	// hashing it first, with the wrapped private key and returns the
	// signature in the compact, public key recoverable format.
	SignMessageCompact(message []byte, doubleHash bool) ([]byte, error)
}

// ECDHRing is an interface that abstracts away basic low-level ECDH shared key
// generation on keys within a key ring.
type ECDHRing interface {
	// ECDH performs a scalar multiplication (ECDH-like operation) between
	// the target key descriptor and remote public key. The output
	// returned will be the sha256 of the resulting shared point serialized
	// in compressed format. If k is our private key, and P is the public
	// key, we perform the following operation:
	//
	//  sx := k*P
	//  s := sha256(sx.SerializeCompressed())
	ECDH(keyDesc KeyDescriptor, pubKey *btcec.PublicKey) ([32]byte, error)
}

// SingleKeyECDH is an abstraction interface that hides the implementation of an
// ECDH operation by wrapping a single, specific private key.
type SingleKeyECDH interface {
	// PubKey returns the public key of the wrapped private key.
	PubKey() *btcec.PublicKey

	// ECDH performs a scalar multiplication (ECDH-like operation) between
	// the wrapped private key and remote public key. The output returned
	// will be the sha256 of the resulting shared point serialized in
	// compressed format.
	ECDH(pubKey *btcec.PublicKey) ([32]byte, error)
}

// TODO(roasbeef): extend to actually support scalar mult of key?
//  * would allow to push in initial handshake auth into interface as well
