// Copyright (c) 2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// Package stdscript provides facilities for working with standard scripts.
package stdscript

// ScriptType identifies the type of known scripts in the blockchain that are
// typically considered standard by the default policy of most nodes.  All other
// scripts are considered non-standard.
type ScriptType byte

const (
	// STNonStandard indicates a script is none of the recognized standard
	// forms.
	STNonStandard ScriptType = iota

	// STPubKeyEcdsaSecp256k1 identifies a standard script that imposes an
	// encumbrance that requires a valid ECDSA signature for a specific
	// secp256k1 public key.
	//
	// This is commonly referred to as either a pay-to-pubkey (P2PK) script or
	// the more specific pay-to-pubkey-ecdsa-secp256k1 script.
	STPubKeyEcdsaSecp256k1

	// STPubKeyEd25519 identifies a standard script that imposes an encumbrance
	// that requires a valid Ed25519 signature for a specific Ed25519 public
	// key.
	//
	// This is commonly referred to as a pay-to-pubkey-ed25519 script.
	STPubKeyEd25519

	// STPubKeySchnorrSecp256k1 identifies a standard script that imposes an
	// encumbrance that requires a valid EC-Schnorr-DCRv0 signature for a
	// specific secp256k1 public key.
	//
	// This is commonly referred to as a pay-to-pubkey-schnorr-secp256k1 script.
	STPubKeySchnorrSecp256k1

	// STPubKeyHashEcdsaSecp256k1 identifies a standard script that imposes an
	// encumbrance that requires a secp256k1 public key that hashes to a
	// specific value along with a valid ECDSA signature for that public key.
	//
	// This is commonly referred to as either a pay-to-pubkey-hash (P2PKH)
	// script or the more specific pay-to-pubkey-hash-ecdsa-secp256k1 script.
	STPubKeyHashEcdsaSecp256k1

	// STPubKeyHashEd25519 identifies a standard script that imposes an
	// encumbrance that requires an Ed25519 public key that hashes to a specific
	// value along with a valid Ed25519 signature for that public key.
	//
	// This is commonly referred to as a pay-to-pubkey-hash-ed25519 script.
	STPubKeyHashEd25519

	// STPubKeyHashSchnorrSecp256k1 identifies a standard script that imposes an
	// encumbrance that requires a secp256k1 public key that hashes to a
	// specific value along with a valid EC-Schnorr-DCRv0 signature for that
	// public key.
	//
	// This is commonly referred to as a pay-to-pubkey-hash-schnorr-secp256k1
	// script.
	STPubKeyHashSchnorrSecp256k1

	// STScriptHash identifies a standard script that imposes an encumbrance
	// that requires a script that hashes to a specific value along with all of
	// the encumbrances that script itself imposes.  The script is commonly
	// referred to as a redeem script.
	//
	// This is commonly referred to as pay-to-script-hash (P2SH).
	STScriptHash

	// STMultiSig identifies a standard script that imposes an encumbrance that
	// requires a given number of valid ECDSA signatures which correspond to
	// given secp256k1 public keys.
	//
	// This is commonly referred to as a standard ECDSA n-of-m multi-signature
	// script.
	STMultiSig

	// STNullData identifies a standard null data script that is provably
	// prunable.
	STNullData

	// STStakeSubmissionPubKeyHash identifies a script that is only valid when
	// used as part of a ticket purchase transaction in the staking system and
	// is used for imposing voting rights.
	//
	// It imposes an encumbrance that requires a secp256k1 public key that
	// hashes to a specific value along with a valid ECDSA signature for that
	// public key.
	STStakeSubmissionPubKeyHash

	// STStakeSubmissionScriptHash identifies a script that is only valid when
	// used as part of a ticket purchase transaction in the staking system and
	// is used for imposing voting rights.
	//
	// It imposes an encumbrance that requires a script that hashes to a
	// specific value along with all of the encumbrances that script itself
	// imposes.  The script is commonly referred to as a redeem script.
	STStakeSubmissionScriptHash

	// STStakeGenPubKeyHash identifies a script that is only valid when used as
	// part of a vote transaction in the staking system.
	//
	// It imposes an encumbrance that requires a secp256k1 public key that
	// hashes to a specific value along with a valid ECDSA signature for that
	// public key.
	STStakeGenPubKeyHash

	// STStakeGenScriptHash identifies a script that is only valid when used as
	// part of a vote transaction in the staking system.
	//
	// It imposes an encumbrance that requires a script that hashes to a
	// specific value along with all of the encumbrances that script itself
	// imposes.  The script is commonly referred to as a redeem script.
	STStakeGenScriptHash

	// STStakeRevocationPubKeyHash identifies a script that is only valid when
	// used as part of a revocation transaction in the staking system.
	//
	// It imposes an encumbrance that requires a secp256k1 public key that
	// hashes to a specific value along with a valid ECDSA signature for that
	// public key.
	STStakeRevocationPubKeyHash

	// STStakeRevocationScriptHash identifies a script that is only valid when
	// used as part of a revocation transaction in the staking system.
	//
	// It imposes an encumbrance that requires a script that hashes to a
	// specific value along with all of the encumbrances that script itself
	// imposes.  The script is commonly referred to as a redeem script.
	STStakeRevocationScriptHash

	// STStakeChangePubKeyHash identifies a script that is only valid when used
	// as part of supported transactions in the staking system.
	//
	// It imposes an encumbrance that requires a secp256k1 public key that
	// hashes to a specific value along with a valid ECDSA signature for that
	// public key.
	STStakeChangePubKeyHash

	// STStakeChangeScriptHash identifies a script that is only valid when used
	// as part of supported transactions in the staking system.
	//
	// It imposes an encumbrance that requires a script that hashes to a
	// specific value along with all of the encumbrances that script itself
	// imposes.  The script is commonly referred to as a redeem script.
	STStakeChangeScriptHash

	// STTreasuryAdd identifies a script that is only valid when used as part
	// supported transactions in the staking system and adds value to the
	// treasury account.
	STTreasuryAdd

	// STTreasuryGenPubKeyHash identifies a script that is only valid when used
	// as part supported transactions in the staking system and generates utxos
	// from the treasury account.
	//
	// It imposes an encumbrance that requires a secp256k1 public key that
	// hashes to a specific value along with a valid ECDSA signature for that
	// public key.
	STTreasuryGenPubKeyHash

	// STTreasuryGenScriptHash identifies a script that is only valid when used
	// as part supported transactions in the staking system and generates utxos
	// from the treasury account.
	//
	// It imposes an encumbrance that requires a script that hashes to a
	// specific value along with all of the encumbrances that script itself
	// imposes.  The script is commonly referred to as a redeem script.
	STTreasuryGenScriptHash

	// numScriptTypes is the maximum script type number used in tests.  This
	// entry MUST be the last entry in the enum.
	numScriptTypes
)

// scriptTypeToName houses the human-readable strings which describe each script
// type.
var scriptTypeToName = []string{
	STNonStandard:                "nonstandard",
	STPubKeyEcdsaSecp256k1:       "pubkey",
	STPubKeyEd25519:              "pubkey-ed25519",
	STPubKeySchnorrSecp256k1:     "pubkey-schnorr-secp256k1",
	STPubKeyHashEcdsaSecp256k1:   "pubkeyhash",
	STPubKeyHashEd25519:          "pubkeyhash-ed25519",
	STPubKeyHashSchnorrSecp256k1: "pubkeyhash-schnorr-secp256k1",
	STScriptHash:                 "scripthash",
	STMultiSig:                   "multisig",
	STNullData:                   "nulldata",
	STStakeSubmissionPubKeyHash:  "stakesubmission-pubkeyhash",
	STStakeSubmissionScriptHash:  "stakesubmission-scripthash",
	STStakeGenPubKeyHash:         "stakegen-pubkeyhash",
	STStakeGenScriptHash:         "stakegen-scripthash",
	STStakeRevocationPubKeyHash:  "stakerevoke-pubkeyhash",
	STStakeRevocationScriptHash:  "stakerevoke-scripthash",
	STStakeChangePubKeyHash:      "stakechange-pubkeyhash",
	STStakeChangeScriptHash:      "stakechange-scripthash",
	STTreasuryAdd:                "treasuryadd",
	STTreasuryGenPubKeyHash:      "treasurygen-pubkeyhash",
	STTreasuryGenScriptHash:      "treasurygen-scripthash",
}

// String returns the ScriptType as a human-readable name.
func (t ScriptType) String() string {
	if t >= numScriptTypes {
		return "invalid"
	}
	return scriptTypeToName[t]
}

// IsPubKeyScript returns whether or not the passed script is either a standard
// pay-to-compressed-secp256k1-pubkey or pay-to-uncompressed-secp256k1-pubkey
// script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsPubKeyScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsPubKeyScriptV0(script)
	}

	return false
}

// IsPubKeyEd25519Script returns whether or not the passed script is a standard
// pay-to-ed25519-pubkey script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsPubKeyEd25519Script(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsPubKeyEd25519ScriptV0(script)
	}

	return false
}

// IsPubKeySchnorrSecp256k1Script returns whether or not the passed script is a
// standard pay-to-schnorr-secp256k1-pubkey script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsPubKeySchnorrSecp256k1Script(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsPubKeySchnorrSecp256k1ScriptV0(script)
	}

	return false
}

// IsPubKeyHashScript returns whether or not the passed script is a standard
// pay-to-pubkey-hash-ecdsa-secp256k1 script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsPubKeyHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsPubKeyHashScriptV0(script)
	}

	return false
}

// IsPubKeyHashEd25519Script returns whether or not the passed script is a
// standard pay-to-pubkey-hash-ed25519 script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsPubKeyHashEd25519Script(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsPubKeyHashEd25519ScriptV0(script)
	}

	return false
}

// IsPubKeyHashSchnorrSecp256k1Script returns whether or not the passed script
// is a standard pay-to-pubkey-hash-schnorr-secp256k1 script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsPubKeyHashSchnorrSecp256k1Script(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsPubKeyHashSchnorrSecp256k1ScriptV0(script)
	}

	return false
}

// IsScriptHashScript returns whether or not the passed script is a standard
// pay-to-script-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsScriptHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsScriptHashScriptV0(script)
	}

	return false
}

// IsMultiSigScript returns whether or not the passed script is a standard
// ECDSA multisig script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsMultiSigScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsMultiSigScriptV0(script)
	}

	return false
}

// IsMultiSigSigScript returns whether or not the passed script appears to be a
// signature script which consists of a pay-to-script-hash multi-signature
// redeem script.  Determining if a signature script is actually a redemption of
// pay-to-script-hash requires the associated public key script which is often
// expensive to obtain.  Therefore, this makes a fast best effort guess that has
// a high probability of being correct by checking if the signature script ends
// with a data push and treating that data push as if it were a p2sh redeem
// script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsMultiSigSigScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsMultiSigSigScriptV0(script)
	}

	return false
}

// IsNullDataScript returns whether or not the passed script is a standard
// null data script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsNullDataScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsNullDataScriptV0(script)
	}

	return false
}

// IsStakeSubmissionPubKeyHashScript returns whether or not the passed script is
// a standard stake submission pay-to-pubkey-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeSubmissionPubKeyHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeSubmissionPubKeyHashScriptV0(script)
	}

	return false
}

// IsStakeSubmissionScriptHashScript returns whether or not the passed script is
// a standard stake submission pay-to-script-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeSubmissionScriptHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeSubmissionScriptHashScriptV0(script)
	}

	return false
}

// IsStakeGenPubKeyHashScript returns whether or not the passed script is a
// standard stake generation pay-to-pubkey-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeGenPubKeyHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeGenPubKeyHashScriptV0(script)
	}

	return false
}

// IsStakeGenScriptHashScript returns whether or not the passed script is a
// standard stake generation pay-to-script-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeGenScriptHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeGenScriptHashScriptV0(script)
	}

	return false
}

// IsStakeRevocationPubKeyHashScript returns whether or not the passed script is
// a standard stake revocation pay-to-pubkey-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeRevocationPubKeyHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeRevocationPubKeyHashScriptV0(script)
	}

	return false
}

// IsStakeRevocationScriptHashScript returns whether or not the passed script is
// a standard stake revocation pay-to-script-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeRevocationScriptHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeRevocationScriptHashScriptV0(script)
	}

	return false
}

// IsStakeChangePubKeyHashScript returns whether or not the passed script is a
// standard stake change pay-to-pubkey-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeChangePubKeyHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeChangePubKeyHashScriptV0(script)
	}

	return false
}

// IsStakeChangeScriptHashScript returns whether or not the passed script is a
// standard stake change pay-to-script-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsStakeChangeScriptHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsStakeChangeScriptHashScriptV0(script)
	}

	return false
}

// IsTreasuryAddScript returns whether or not the passed script is a supported
// treasury add script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsTreasuryAddScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsTreasuryAddScriptV0(script)
	}

	return false
}

// IsTreasuryGenPubKeyHashScript returns whether or not the passed script is a
// standard treasury generation pay-to-pubkey-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsTreasuryGenPubKeyHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsTreasuryGenPubKeyHashScriptV0(script)
	}

	return false
}

// IsTreasuryGenScriptHashScript returns whether or not the passed script is a
// standard treasury generation pay-to-script-hash script.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return false for other script versions.
func IsTreasuryGenScriptHashScript(scriptVersion uint16, script []byte) bool {
	switch scriptVersion {
	case 0:
		return IsTreasuryGenScriptHashScriptV0(script)
	}

	return false
}

// DetermineScriptType returns the type of the script passed.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return STNonStandard for other script versions.
//
// Similarly, STNonStandard is returned when the script does not parse.
func DetermineScriptType(scriptVersion uint16, script []byte) ScriptType {
	switch scriptVersion {
	case 0:
		return DetermineScriptTypeV0(script)
	}

	// All scripts with newer versions are considered non standard.
	return STNonStandard
}

// DetermineRequiredSigs attempts to identify the number of signatures required
// by the passed script for the known standard types.
//
// NOTE: Version 0 scripts are the only currently supported version.  It will
// always return 0 for other script versions.
//
// Similarly, 0 is returned when the script does not parse or is not one of the
// known standard types.
func DetermineRequiredSigs(scriptVersion uint16, script []byte) uint16 {
	switch scriptVersion {
	case 0:
		return DetermineRequiredSigsV0(script)
	}

	return 0
}
