This adds the dupword linter to the list of linters and addresses a few false positives it complains about.
985 lines
35 KiB
Go
985 lines
35 KiB
Go
// Copyright (c) 2021-2023 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package stdscript
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/decred/dcrd/dcrec"
|
|
"github.com/decred/dcrd/txscript/v4"
|
|
)
|
|
|
|
const (
|
|
// MaxDataCarrierSizeV0 is the maximum number of bytes allowed in pushed
|
|
// data to be considered a standard version 0 provably pruneable nulldata
|
|
// script.
|
|
MaxDataCarrierSizeV0 = 256
|
|
)
|
|
|
|
// ExtractCompressedPubKeyV0 extracts a compressed public key from the passed
|
|
// script if it is a standard version 0 pay-to-compressed-secp256k1-pubkey
|
|
// script. It will return nil otherwise.
|
|
func ExtractCompressedPubKeyV0(script []byte) []byte {
|
|
// A pay-to-compressed-pubkey script is of the form:
|
|
// OP_DATA_33 <33-byte compressed pubkey> OP_CHECKSIG
|
|
|
|
// All compressed secp256k1 public keys must start with 0x02 or 0x03.
|
|
if len(script) == 35 &&
|
|
script[34] == txscript.OP_CHECKSIG &&
|
|
script[0] == txscript.OP_DATA_33 &&
|
|
(script[1] == 0x02 || script[1] == 0x03) {
|
|
|
|
return script[1:34]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExtractUncompressedPubKeyV0 extracts an uncompressed public key from the
|
|
// passed script if it is a standard version 0
|
|
// pay-to-uncompressed-secp256k1-pubkey script. It will return nil otherwise.
|
|
func ExtractUncompressedPubKeyV0(script []byte) []byte {
|
|
// A pay-to-uncompressed-pubkey script is of the form:
|
|
// OP_DATA_65 <65-byte uncompressed pubkey> OP_CHECKSIG
|
|
|
|
// All non-hybrid uncompressed secp256k1 public keys must start with 0x04.
|
|
if len(script) == 67 &&
|
|
script[66] == txscript.OP_CHECKSIG &&
|
|
script[0] == txscript.OP_DATA_65 &&
|
|
script[1] == 0x04 {
|
|
|
|
return script[1:66]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExtractPubKeyV0 extracts either a compressed or uncompressed public key from
|
|
// the passed script if it is either a standard version 0
|
|
// pay-to-compressed-secp256k1-pubkey or pay-to-uncompressed-secp256k1-pubkey
|
|
// script, respectively. It will return nil otherwise.
|
|
func ExtractPubKeyV0(script []byte) []byte {
|
|
if pubKey := ExtractCompressedPubKeyV0(script); pubKey != nil {
|
|
return pubKey
|
|
}
|
|
return ExtractUncompressedPubKeyV0(script)
|
|
}
|
|
|
|
// IsPubKeyScriptV0 returns whether or not the passed script is either a
|
|
// standard version 0 pay-to-compressed-secp256k1-pubkey or
|
|
// pay-to-uncompressed-secp256k1-pubkey script.
|
|
func IsPubKeyScriptV0(script []byte) bool {
|
|
return ExtractPubKeyV0(script) != nil
|
|
}
|
|
|
|
// ExtractPubKeyAltDetailsV0 extracts the public key and signature type from the
|
|
// passed script if it is a standard version 0 pay-to-alt-pubkey script. It
|
|
// will return nil otherwise.
|
|
func ExtractPubKeyAltDetailsV0(script []byte) ([]byte, dcrec.SignatureType) {
|
|
// A pay-to-alt-pubkey script is of the form:
|
|
// PUBKEY SIGTYPE OP_CHECKSIGALT
|
|
//
|
|
// The only two currently supported alternative signature types are ed25519
|
|
// and schnorr + secp256k1 (with a compressed pubkey).
|
|
//
|
|
// OP_DATA_32 <32-byte pubkey> <1-byte ed25519 sigtype> OP_CHECKSIGALT
|
|
// OP_DATA_33 <33-byte pubkey> <1-byte schnorr+secp sigtype> OP_CHECKSIGALT
|
|
|
|
// The script can't possibly be a pay-to-alt-pubkey script if it doesn't
|
|
// end with OP_CHECKSIGALT or have at least two small integer pushes
|
|
// preceding it (although any reasonable pubkey will certainly be larger).
|
|
// Fail fast to avoid more work below.
|
|
if len(script) < 3 || script[len(script)-1] != txscript.OP_CHECKSIGALT {
|
|
return nil, 0
|
|
}
|
|
|
|
if len(script) == 35 && script[0] == txscript.OP_DATA_32 &&
|
|
txscript.IsSmallInt(script[33]) &&
|
|
txscript.AsSmallInt(script[33]) == dcrec.STEd25519 {
|
|
|
|
return script[1:33], dcrec.STEd25519
|
|
}
|
|
|
|
if len(script) == 36 && script[0] == txscript.OP_DATA_33 &&
|
|
txscript.IsSmallInt(script[34]) &&
|
|
txscript.AsSmallInt(script[34]) == dcrec.STSchnorrSecp256k1 &&
|
|
txscript.IsStrictCompressedPubKeyEncoding(script[1:34]) {
|
|
|
|
return script[1:34], dcrec.STSchnorrSecp256k1
|
|
}
|
|
|
|
return nil, 0
|
|
}
|
|
|
|
// ExtractPubKeyEd25519V0 extracts a public key from the passed script if it is
|
|
// a standard version 0 pay-to-ed25519-pubkey script. It will return nil
|
|
// otherwise.
|
|
func ExtractPubKeyEd25519V0(script []byte) []byte {
|
|
pk, sigType := ExtractPubKeyAltDetailsV0(script)
|
|
if pk == nil || sigType != dcrec.STEd25519 {
|
|
return nil
|
|
}
|
|
return pk
|
|
}
|
|
|
|
// IsPubKeyEd25519ScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 pay-to-ed25519-pubkey script.
|
|
func IsPubKeyEd25519ScriptV0(script []byte) bool {
|
|
return ExtractPubKeyEd25519V0(script) != nil
|
|
}
|
|
|
|
// ExtractPubKeySchnorrSecp256k1V0 extracts a public key from the passed script
|
|
// if it is a standard version 0 pay-to-schnorr-secp256k1-pubkey script. It
|
|
// will return nil otherwise.
|
|
func ExtractPubKeySchnorrSecp256k1V0(script []byte) []byte {
|
|
pk, sigType := ExtractPubKeyAltDetailsV0(script)
|
|
if pk == nil || sigType != dcrec.STSchnorrSecp256k1 {
|
|
return nil
|
|
}
|
|
return pk
|
|
}
|
|
|
|
// IsPubKeySchnorrSecp256k1ScriptV0 returns whether or not the passed script is
|
|
// a standard version 0 pay-to-schnorr-secp256k1-pubkey script.
|
|
func IsPubKeySchnorrSecp256k1ScriptV0(script []byte) bool {
|
|
return ExtractPubKeySchnorrSecp256k1V0(script) != nil
|
|
}
|
|
|
|
// ExtractPubKeyHashV0 extracts the public key hash from the passed script if it
|
|
// is a standard version 0 pay-to-pubkey-hash-ecdsa-secp256k1 script. It will
|
|
// return nil otherwise.
|
|
func ExtractPubKeyHashV0(script []byte) []byte {
|
|
// A pay-to-pubkey-hash script is of the form:
|
|
// OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG
|
|
if len(script) == 25 &&
|
|
script[0] == txscript.OP_DUP &&
|
|
script[1] == txscript.OP_HASH160 &&
|
|
script[2] == txscript.OP_DATA_20 &&
|
|
script[23] == txscript.OP_EQUALVERIFY &&
|
|
script[24] == txscript.OP_CHECKSIG {
|
|
|
|
return script[3:23]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsPubKeyHashScriptV0 returns whether or not the passed script is a standard
|
|
// version 0 pay-to-pubkey-hash-ecdsa-secp256k1 script.
|
|
func IsPubKeyHashScriptV0(script []byte) bool {
|
|
return ExtractPubKeyHashV0(script) != nil
|
|
}
|
|
|
|
// IsStandardAltSignatureTypeV0 returns whether or not the provided version 0
|
|
// script opcode represents a push of a standard alt signature type.
|
|
func IsStandardAltSignatureTypeV0(op byte) bool {
|
|
if !txscript.IsSmallInt(op) {
|
|
return false
|
|
}
|
|
|
|
sigType := txscript.AsSmallInt(op)
|
|
return sigType == dcrec.STEd25519 || sigType == dcrec.STSchnorrSecp256k1
|
|
}
|
|
|
|
// ExtractPubKeyHashAltDetailsV0 extracts the public key hash and signature type
|
|
// from the passed script if it is a standard version 0 pay-to-alt-pubkey-hash
|
|
// script. It will return nil otherwise.
|
|
func ExtractPubKeyHashAltDetailsV0(script []byte) ([]byte, dcrec.SignatureType) {
|
|
// A pay-to-alt-pubkey-hash script is of the form:
|
|
// DUP HASH160 <20-byte hash> EQUALVERIFY SIGTYPE CHECKSIG
|
|
//
|
|
// The only two currently supported alternative signature types are ed25519
|
|
// and schnorr + secp256k1 (with a compressed pubkey).
|
|
//
|
|
// DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte ed25519 sigtype> CHECKSIGALT
|
|
// DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte schnorr+secp sigtype> CHECKSIGALT
|
|
//
|
|
// Notice that OP_0 is not specified since signature type 0 disabled.
|
|
if len(script) == 26 &&
|
|
script[0] == txscript.OP_DUP &&
|
|
script[1] == txscript.OP_HASH160 &&
|
|
script[2] == txscript.OP_DATA_20 &&
|
|
script[23] == txscript.OP_EQUALVERIFY &&
|
|
IsStandardAltSignatureTypeV0(script[24]) &&
|
|
script[25] == txscript.OP_CHECKSIGALT {
|
|
|
|
return script[3:23], dcrec.SignatureType(txscript.AsSmallInt(script[24]))
|
|
}
|
|
|
|
return nil, 0
|
|
}
|
|
|
|
// ExtractPubKeyHashEd25519V0 extracts the public key hash from the passed
|
|
// script if it is a standard version 0 pay-to-pubkey-hash-ed25519 script. It
|
|
// will return nil otherwise.
|
|
func ExtractPubKeyHashEd25519V0(script []byte) []byte {
|
|
pkHash, sigType := ExtractPubKeyHashAltDetailsV0(script)
|
|
if pkHash == nil || sigType != dcrec.STEd25519 {
|
|
return nil
|
|
}
|
|
return pkHash
|
|
}
|
|
|
|
// IsPubKeyHashEd25519ScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 pay-to-pubkey-hash-ed25519 script.
|
|
func IsPubKeyHashEd25519ScriptV0(script []byte) bool {
|
|
return ExtractPubKeyHashEd25519V0(script) != nil
|
|
}
|
|
|
|
// ExtractPubKeyHashSchnorrSecp256k1V0 extracts the public key hash from the
|
|
// passed script if it is a standard version 0
|
|
// pay-to-pubkey-hash-schnorr-secp256k1 script. It will return nil otherwise.
|
|
func ExtractPubKeyHashSchnorrSecp256k1V0(script []byte) []byte {
|
|
pkHash, sigType := ExtractPubKeyHashAltDetailsV0(script)
|
|
if pkHash == nil || sigType != dcrec.STSchnorrSecp256k1 {
|
|
return nil
|
|
}
|
|
return pkHash
|
|
}
|
|
|
|
// IsPubKeyHashSchnorrSecp256k1ScriptV0 returns whether or not the passed script
|
|
// is a standard version 0 pay-to-pubkey-hash-schnorr-secp256k1 script.
|
|
func IsPubKeyHashSchnorrSecp256k1ScriptV0(script []byte) bool {
|
|
return ExtractPubKeyHashSchnorrSecp256k1V0(script) != nil
|
|
}
|
|
|
|
// ExtractScriptHashV0 extracts the script hash from the passed script if it is
|
|
// a standard version 0 pay-to-script-hash script. It will return nil
|
|
// otherwise.
|
|
func ExtractScriptHashV0(script []byte) []byte {
|
|
// Defer to consensus code.
|
|
return txscript.ExtractScriptHash(script)
|
|
}
|
|
|
|
// IsScriptHashScriptV0 returns whether or not the passed script is a standard
|
|
// version 0 pay-to-script-hash script.
|
|
func IsScriptHashScriptV0(script []byte) bool {
|
|
return ExtractScriptHashV0(script) != nil
|
|
}
|
|
|
|
// MultiSigDetailsV0 houses details extracted from a standard version 0 ECDSA
|
|
// multisig script.
|
|
type MultiSigDetailsV0 struct {
|
|
RequiredSigs uint16
|
|
NumPubKeys uint16
|
|
PubKeys [][]byte
|
|
Valid bool
|
|
}
|
|
|
|
// ExtractMultiSigScriptDetailsV0 attempts to extract details from the passed
|
|
// version 0 script if it is a standard ECDSA multisig script. The returned
|
|
// details struct will have the valid flag set to false otherwise.
|
|
//
|
|
// The extract pubkeys flag indicates whether or not the pubkeys themselves
|
|
// should also be extracted and is provided because extracting them results in
|
|
// an allocation that the caller might wish to avoid. The PubKeys member of the
|
|
// returned details struct will be nil when the flag is false.
|
|
func ExtractMultiSigScriptDetailsV0(script []byte, extractPubKeys bool) MultiSigDetailsV0 {
|
|
// nolint: dupword
|
|
//
|
|
// A multi-signature script is of the form:
|
|
// REQ_SIGS PUBKEY PUBKEY PUBKEY ... NUM_PUBKEYS OP_CHECKMULTISIG
|
|
|
|
// The script can't possibly be a multisig script if it doesn't end with
|
|
// OP_CHECKMULTISIG or have at least two small integer pushes preceding it.
|
|
// Fail fast to avoid more work below.
|
|
if len(script) < 3 || script[len(script)-1] != txscript.OP_CHECKMULTISIG {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
|
|
// The first opcode must be a small integer specifying the number of
|
|
// signatures required.
|
|
const scriptVersion = 0
|
|
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script)
|
|
if !tokenizer.Next() || !txscript.IsSmallInt(tokenizer.Opcode()) {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
requiredSigs := txscript.AsSmallInt(tokenizer.Opcode())
|
|
|
|
// There must be at least one required signature.
|
|
if requiredSigs == 0 {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
|
|
// The next series of opcodes must either push public keys or be a small
|
|
// integer specifying the number of public keys. It should be noted that
|
|
// although the consensus rules allow a higher maximum number of pubkeys,
|
|
// this intentionally further restricts the maximum number to what can be
|
|
// represented by a small integer push (up to a max of 16).
|
|
var numPubKeys int
|
|
var pubKeys [][]byte
|
|
if extractPubKeys {
|
|
pubKeys = make([][]byte, 0, txscript.MaxPubKeysPerMultiSig)
|
|
}
|
|
for tokenizer.Next() {
|
|
data := tokenizer.Data()
|
|
if !txscript.IsStrictCompressedPubKeyEncoding(data) {
|
|
break
|
|
}
|
|
numPubKeys++
|
|
if extractPubKeys {
|
|
pubKeys = append(pubKeys, data)
|
|
}
|
|
}
|
|
if tokenizer.Done() {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
|
|
// The next opcode must be a small integer specifying the number of public
|
|
// keys required.
|
|
op := tokenizer.Opcode()
|
|
if !txscript.IsSmallInt(op) || txscript.AsSmallInt(op) != numPubKeys {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
|
|
// There must be at least as many pubkeys as required signatures.
|
|
if numPubKeys < requiredSigs {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
|
|
// There must only be a single opcode left unparsed which will be
|
|
// OP_CHECKMULTISIG per the check above.
|
|
if int32(len(tokenizer.Script()))-tokenizer.ByteIndex() != 1 {
|
|
return MultiSigDetailsV0{}
|
|
}
|
|
|
|
return MultiSigDetailsV0{
|
|
RequiredSigs: uint16(requiredSigs),
|
|
NumPubKeys: uint16(numPubKeys),
|
|
PubKeys: pubKeys,
|
|
Valid: true,
|
|
}
|
|
}
|
|
|
|
// IsMultiSigScriptV0 returns whether or not the passed script is a standard
|
|
// version 0 ECDSA multisig script.
|
|
//
|
|
// NOTE: This function is only valid for version 0 scripts. It will always
|
|
// return false for other script versions.
|
|
func IsMultiSigScriptV0(script []byte) bool {
|
|
// Since this is only checking the form of the script, don't extract the
|
|
// public keys to avoid the allocation.
|
|
details := ExtractMultiSigScriptDetailsV0(script, false)
|
|
return details.Valid
|
|
}
|
|
|
|
// finalOpcodeDataV0 returns the data associated with the final opcode in the
|
|
// passed version 0 script. It will return nil if the script fails to parse.
|
|
func finalOpcodeDataV0(script []byte) []byte {
|
|
// Avoid unnecessary work.
|
|
if len(script) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var data []byte
|
|
const scriptVersion = 0
|
|
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script)
|
|
for tokenizer.Next() {
|
|
data = tokenizer.Data()
|
|
}
|
|
if tokenizer.Err() != nil {
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
// IsMultiSigSigScriptV0 returns whether or not the passed script appears to be
|
|
// a version 0 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.
|
|
func IsMultiSigSigScriptV0(script []byte) bool {
|
|
// The script can't possibly be a multisig signature script if it doesn't
|
|
// end with OP_CHECKMULTISIG in the redeem script or have at least two small
|
|
// integers preceding it, and the redeem script itself must be preceded by
|
|
// at least a data push opcode. Fail fast to avoid more work below.
|
|
if len(script) < 4 || script[len(script)-1] != txscript.OP_CHECKMULTISIG {
|
|
return false
|
|
}
|
|
|
|
// Parse through the script to find the last opcode and any data it might
|
|
// push and treat it as a p2sh redeem script even though it might not
|
|
// actually be one.
|
|
possibleRedeemScript := finalOpcodeDataV0(script)
|
|
if possibleRedeemScript == nil {
|
|
return false
|
|
}
|
|
|
|
// Finally, return if that possible redeem script is a multisig script.
|
|
return IsMultiSigScriptV0(possibleRedeemScript)
|
|
}
|
|
|
|
// MultiSigRedeemScriptFromScriptSigV0 attempts to extract a multi-signature
|
|
// redeem script from a version 0 P2SH-redeeming input. The script is expected
|
|
// to already have been checked to be a version 0 multisignature script prior to
|
|
// calling this function. The results are undefined for other script types.
|
|
func MultiSigRedeemScriptFromScriptSigV0(script []byte) []byte {
|
|
// The redeemScript is always the last item on the stack of the script sig.
|
|
return finalOpcodeDataV0(script)
|
|
}
|
|
|
|
// isCanonicalPushV0 returns whether or not the given version 0 opcode and
|
|
// associated data is a push instruction that uses the smallest instruction to
|
|
// do the job.
|
|
//
|
|
// For example, it is possible to push a value of 1 to the stack as "OP_1",
|
|
// "OP_DATA_1 0x01", "OP_PUSHDATA1 0x01 0x01", and others, however, the first
|
|
// only takes a single byte, while the rest take more. Only the first is
|
|
// considered canonical.
|
|
func isCanonicalPushV0(opcode byte, data []byte) bool {
|
|
dataLen := len(data)
|
|
if opcode > txscript.OP_16 {
|
|
return false
|
|
}
|
|
if opcode < txscript.OP_PUSHDATA1 && opcode > txscript.OP_0 &&
|
|
(dataLen == 1 && data[0] <= 16) {
|
|
return false
|
|
}
|
|
if opcode == txscript.OP_PUSHDATA1 && dataLen < txscript.OP_PUSHDATA1 {
|
|
return false
|
|
}
|
|
if opcode == txscript.OP_PUSHDATA2 && dataLen <= 0xff {
|
|
return false
|
|
}
|
|
if opcode == txscript.OP_PUSHDATA4 && dataLen <= 0xffff {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsNullDataScriptV0 returns whether or not the passed script is a standard
|
|
// version 0 null data script.
|
|
func IsNullDataScriptV0(script []byte) bool {
|
|
// A null script is of the form:
|
|
// OP_RETURN <optional data>
|
|
//
|
|
// Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a
|
|
// canonical data push up to MaxDataCarrierSizeV0 bytes.
|
|
|
|
// The script can't possibly be a null data script if it doesn't start
|
|
// with OP_RETURN. Fail fast to avoid more work below.
|
|
if len(script) < 1 || script[0] != txscript.OP_RETURN {
|
|
return false
|
|
}
|
|
|
|
// Single OP_RETURN.
|
|
if len(script) == 1 {
|
|
return true
|
|
}
|
|
|
|
// OP_RETURN followed by a canonical data push up to MaxDataCarrierSizeV0
|
|
// bytes in length.
|
|
const scriptVersion = 0
|
|
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script[1:])
|
|
return tokenizer.Next() && tokenizer.Done() &&
|
|
len(tokenizer.Data()) <= MaxDataCarrierSizeV0 &&
|
|
isCanonicalPushV0(tokenizer.Opcode(), tokenizer.Data())
|
|
}
|
|
|
|
// extractStakePubKeyHashV0 extracts the public key hash from the passed script
|
|
// if it is a standard version 0 stake-tagged pay-to-pubkey-hash script with the
|
|
// provided stake opcode. It will return nil otherwise.
|
|
func extractStakePubKeyHashV0(script []byte, stakeOpcode byte) []byte {
|
|
// A stake-tagged pay-to-pubkey-hash is of the form:
|
|
// <stake opcode> <standard-pay-to-pubkey-hash script>
|
|
|
|
// The script can't possibly be a stake-tagged pay-to-pubkey-hash if it
|
|
// doesn't start with the given stake opcode. Fail fast to avoid more work
|
|
// below.
|
|
if len(script) < 1 || script[0] != stakeOpcode {
|
|
return nil
|
|
}
|
|
|
|
return ExtractPubKeyHashV0(script[1:])
|
|
}
|
|
|
|
// ExtractStakeSubmissionPubKeyHashV0 extracts the public key hash from the
|
|
// passed script if it is a standard version 0 stake submission
|
|
// pay-to-pubkey-hash script. It will return nil otherwise.
|
|
func ExtractStakeSubmissionPubKeyHashV0(script []byte) []byte {
|
|
// A stake submission pay-to-pubkey-hash script is of the form:
|
|
// OP_SSTX <standard-pay-to-pubkey-hash script>
|
|
const stakeOpcode = txscript.OP_SSTX
|
|
return extractStakePubKeyHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeSubmissionPubKeyHashScriptV0 returns whether or not the passed script
|
|
// is a standard version 0 stake submission pay-to-pubkey-hash script.
|
|
func IsStakeSubmissionPubKeyHashScriptV0(script []byte) bool {
|
|
return ExtractStakeSubmissionPubKeyHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakePubKeyHashV0 extracts the public key hash from the passed script
|
|
// if it is any one of the supported standard version 0 stake-tagged
|
|
// pay-to-pubkey-hash scripts. It will return nil otherwise.
|
|
func ExtractStakePubKeyHashV0(script []byte) []byte {
|
|
if h := ExtractStakeSubmissionPubKeyHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractStakeGenPubKeyHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractStakeRevocationPubKeyHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractStakeChangePubKeyHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractTreasuryGenPubKeyHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// extractStakeScriptHashV0 extracts the script hash from the passed script if
|
|
// it is a standard version 0 stake-tagged pay-to-script-hash script with the
|
|
// provided stake opcode. It will return nil otherwise.
|
|
func extractStakeScriptHashV0(script []byte, stakeOpcode byte) []byte {
|
|
// A stake-tagged pay-to-script-hash is of the form:
|
|
// <stake opcode> <standard-pay-to-script-hash script>
|
|
|
|
// The script can't possibly be a stake-tagged pay-to-script-hash if it
|
|
// doesn't start with the given stake opcode. Fail fast to avoid more work
|
|
// below.
|
|
if len(script) < 1 || script[0] != stakeOpcode {
|
|
return nil
|
|
}
|
|
|
|
return txscript.ExtractScriptHash(script[1:])
|
|
}
|
|
|
|
// ExtractStakeSubmissionScriptHashV0 extracts the script hash from the
|
|
// passed script if it is a standard version 0 stake submission
|
|
// pay-to-script-hash script. It will return nil otherwise.
|
|
func ExtractStakeSubmissionScriptHashV0(script []byte) []byte {
|
|
// A stake submission pay-to-script-hash script is of the form:
|
|
// OP_SSTX <standard-pay-to-script-hash script>
|
|
const stakeOpcode = txscript.OP_SSTX
|
|
return extractStakeScriptHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeSubmissionScriptHashScriptV0 returns whether or not the passed script
|
|
// is a standard version 0 stake submission pay-to-script-hash script.
|
|
func IsStakeSubmissionScriptHashScriptV0(script []byte) bool {
|
|
return ExtractStakeSubmissionScriptHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeGenPubKeyHashV0 extracts the public key hash from the
|
|
// passed script if it is a standard version 0 stake generation
|
|
// pay-to-pubkey-hash script. It will return nil otherwise.
|
|
func ExtractStakeGenPubKeyHashV0(script []byte) []byte {
|
|
// A stake generation pay-to-pubkey-hash script is of the form:
|
|
// OP_SSGEN <standard-pay-to-pubkey-hash script>
|
|
const stakeOpcode = txscript.OP_SSGEN
|
|
return extractStakePubKeyHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeGenPubKeyHashScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 stake generation pay-to-pubkey-hash script.
|
|
func IsStakeGenPubKeyHashScriptV0(script []byte) bool {
|
|
return ExtractStakeGenPubKeyHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeGenScriptHashV0 extracts the script hash from the passed
|
|
// script if it is a standard version 0 stake generation pay-to-script-hash
|
|
// script. It will return nil otherwise.
|
|
func ExtractStakeGenScriptHashV0(script []byte) []byte {
|
|
// A stake generation pay-to-script-hash script is of the form:
|
|
// OP_SSGEN <standard-pay-to-script-hash script>
|
|
const stakeOpcode = txscript.OP_SSGEN
|
|
return extractStakeScriptHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeGenScriptHashScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 stake generation pay-to-script-hash script.
|
|
func IsStakeGenScriptHashScriptV0(script []byte) bool {
|
|
return ExtractStakeGenScriptHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeRevocationPubKeyHashV0 extracts the public key hash from
|
|
// the passed script if it is a standard version 0 stake revocation
|
|
// pay-to-pubkey-hash script. It will return nil otherwise.
|
|
func ExtractStakeRevocationPubKeyHashV0(script []byte) []byte {
|
|
// A stake revocation pay-to-pubkey-hash script is of the form:
|
|
// OP_SSRTX <standard-pay-to-pubkey-hash script>
|
|
const stakeOpcode = txscript.OP_SSRTX
|
|
return extractStakePubKeyHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeRevocationPubKeyHashScriptV0 returns whether or not the passed script
|
|
// is a standard version 0 stake revocation pay-to-pubkey-hash script.
|
|
func IsStakeRevocationPubKeyHashScriptV0(script []byte) bool {
|
|
return ExtractStakeRevocationPubKeyHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeRevocationScriptHashV0 extracts the script hash from the
|
|
// passed script if it is a standard version 0 stake revocation
|
|
// pay-to-script-hash script. It will return nil otherwise.
|
|
func ExtractStakeRevocationScriptHashV0(script []byte) []byte {
|
|
// A stake revocation pay-to-script-hash script is of the form:
|
|
// OP_SSRTX <standard-pay-to-script-hash script>
|
|
const stakeOpcode = txscript.OP_SSRTX
|
|
return extractStakeScriptHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeRevocationScriptHashScriptV0 returns whether or not the passed script
|
|
// is a standard version 0 stake revocation pay-to-script-hash script.
|
|
func IsStakeRevocationScriptHashScriptV0(script []byte) bool {
|
|
return ExtractStakeRevocationScriptHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeChangePubKeyHashV0 extracts the public key hash from the
|
|
// passed script if it is a standard version 0 stake change pay-to-pubkey-hash
|
|
// script. It will return nil otherwise.
|
|
func ExtractStakeChangePubKeyHashV0(script []byte) []byte {
|
|
// A stake change pay-to-pubkey-hash script is of the form:
|
|
// OP_SSTXCHANGE <standard-pay-to-pubkey-hash script>
|
|
const stakeOpcode = txscript.OP_SSTXCHANGE
|
|
return extractStakePubKeyHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeChangePubKeyHashScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 stake change pay-to-pubkey-hash script.
|
|
func IsStakeChangePubKeyHashScriptV0(script []byte) bool {
|
|
return ExtractStakeChangePubKeyHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeChangeScriptHashV0 extracts the script hash from the passed
|
|
// script if it is a standard version 0 stake change pay-to-script-hash
|
|
// script. It will return nil otherwise.
|
|
func ExtractStakeChangeScriptHashV0(script []byte) []byte {
|
|
// A stake change pay-to-script-hash script is of the form:
|
|
// OP_SSTXCHANGE <standard-pay-to-script-hash script>
|
|
const stakeOpcode = txscript.OP_SSTXCHANGE
|
|
return extractStakeScriptHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsStakeChangeScriptHashScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 stake change pay-to-script-hash script.
|
|
func IsStakeChangeScriptHashScriptV0(script []byte) bool {
|
|
return ExtractStakeChangeScriptHashV0(script) != nil
|
|
}
|
|
|
|
// IsTreasuryAddScriptV0 returns whether or not the passed script is a supported
|
|
// version 0 treasury add script.
|
|
func IsTreasuryAddScriptV0(script []byte) bool {
|
|
return len(script) == 1 && script[0] == txscript.OP_TADD
|
|
}
|
|
|
|
// ExtractTreasuryGenPubKeyHashV0 extracts the public key hash from the
|
|
// passed script if it is a standard version 0 treasury generation
|
|
// pay-to-pubkey-hash script. It will return nil otherwise.
|
|
func ExtractTreasuryGenPubKeyHashV0(script []byte) []byte {
|
|
// A treasury generation pay-to-pubkey-hash script is of the form:
|
|
// OP_TGEN <standard-pay-to-pubkey-hash script>
|
|
const stakeOpcode = txscript.OP_TGEN
|
|
return extractStakePubKeyHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsTreasuryGenPubKeyHashScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 treasury generation pay-to-pubkey-hash script.
|
|
func IsTreasuryGenPubKeyHashScriptV0(script []byte) bool {
|
|
return ExtractTreasuryGenPubKeyHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractTreasuryGenScriptHashV0 extracts the script hash from the passed
|
|
// script if it is a standard version 0 treasury generation pay-to-script-hash
|
|
// script. It will return nil otherwise.
|
|
func ExtractTreasuryGenScriptHashV0(script []byte) []byte {
|
|
// A treasury generation pay-to-script-hash script is of the form:
|
|
// OP_TGEN <standard-pay-to-script-hash script>
|
|
const stakeOpcode = txscript.OP_TGEN
|
|
return extractStakeScriptHashV0(script, stakeOpcode)
|
|
}
|
|
|
|
// IsTreasuryGenScriptHashScriptV0 returns whether or not the passed script is a
|
|
// standard version 0 treasury generation pay-to-script-hash script.
|
|
func IsTreasuryGenScriptHashScriptV0(script []byte) bool {
|
|
return ExtractTreasuryGenScriptHashV0(script) != nil
|
|
}
|
|
|
|
// ExtractStakeScriptHashV0 extracts the script hash from the passed script if
|
|
// it is any one of the supported standard version 0 stake-tagged
|
|
// pay-to-script-hash scripts. It will return nil otherwise.
|
|
func ExtractStakeScriptHashV0(script []byte) []byte {
|
|
if h := ExtractStakeSubmissionScriptHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractStakeGenScriptHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractStakeRevocationScriptHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractStakeChangeScriptHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
if h := ExtractTreasuryGenScriptHashV0(script); h != nil {
|
|
return h
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DetermineScriptTypeV0 returns the type of the passed version 0 script for
|
|
// the known standard types. This includes both types that are required by
|
|
// consensus as well as those which are not.
|
|
//
|
|
// STNonStandard will be returned when the script does not parse.
|
|
func DetermineScriptTypeV0(script []byte) ScriptType {
|
|
switch {
|
|
case IsPubKeyScriptV0(script):
|
|
return STPubKeyEcdsaSecp256k1
|
|
case IsPubKeyEd25519ScriptV0(script):
|
|
return STPubKeyEd25519
|
|
case IsPubKeySchnorrSecp256k1ScriptV0(script):
|
|
return STPubKeySchnorrSecp256k1
|
|
case IsPubKeyHashScriptV0(script):
|
|
return STPubKeyHashEcdsaSecp256k1
|
|
case IsPubKeyHashEd25519ScriptV0(script):
|
|
return STPubKeyHashEd25519
|
|
case IsPubKeyHashSchnorrSecp256k1ScriptV0(script):
|
|
return STPubKeyHashSchnorrSecp256k1
|
|
case IsScriptHashScriptV0(script):
|
|
return STScriptHash
|
|
case IsMultiSigScriptV0(script):
|
|
return STMultiSig
|
|
case IsNullDataScriptV0(script):
|
|
return STNullData
|
|
case IsStakeSubmissionPubKeyHashScriptV0(script):
|
|
return STStakeSubmissionPubKeyHash
|
|
case IsStakeSubmissionScriptHashScriptV0(script):
|
|
return STStakeSubmissionScriptHash
|
|
case IsStakeGenPubKeyHashScriptV0(script):
|
|
return STStakeGenPubKeyHash
|
|
case IsStakeGenScriptHashScriptV0(script):
|
|
return STStakeGenScriptHash
|
|
case IsStakeRevocationPubKeyHashScriptV0(script):
|
|
return STStakeRevocationPubKeyHash
|
|
case IsStakeRevocationScriptHashScriptV0(script):
|
|
return STStakeRevocationScriptHash
|
|
case IsStakeChangePubKeyHashScriptV0(script):
|
|
return STStakeChangePubKeyHash
|
|
case IsStakeChangeScriptHashScriptV0(script):
|
|
return STStakeChangeScriptHash
|
|
case IsTreasuryAddScriptV0(script):
|
|
return STTreasuryAdd
|
|
case IsTreasuryGenPubKeyHashScriptV0(script):
|
|
return STTreasuryGenPubKeyHash
|
|
case IsTreasuryGenScriptHashScriptV0(script):
|
|
return STTreasuryGenScriptHash
|
|
}
|
|
|
|
return STNonStandard
|
|
}
|
|
|
|
// DetermineRequiredSigsV0 attempts to identify the number of signatures
|
|
// required by the passed version 0 script for the known standard types.
|
|
//
|
|
// It will return 0 when the script does not parse or is not one of the known
|
|
// standard types.
|
|
func DetermineRequiredSigsV0(script []byte) uint16 {
|
|
scriptType := DetermineScriptTypeV0(script)
|
|
switch scriptType {
|
|
case STPubKeyHashEcdsaSecp256k1, STScriptHash,
|
|
STPubKeyHashEd25519, STPubKeyHashSchnorrSecp256k1,
|
|
STPubKeyEcdsaSecp256k1, STPubKeyEd25519, STPubKeySchnorrSecp256k1,
|
|
STStakeSubmissionPubKeyHash, STStakeSubmissionScriptHash,
|
|
STStakeGenPubKeyHash, STStakeGenScriptHash,
|
|
STStakeRevocationPubKeyHash, STStakeRevocationScriptHash,
|
|
STStakeChangePubKeyHash, STStakeChangeScriptHash,
|
|
STTreasuryGenPubKeyHash, STTreasuryGenScriptHash:
|
|
|
|
return 1
|
|
|
|
case STMultiSig:
|
|
details := ExtractMultiSigScriptDetailsV0(script, false)
|
|
if details.Valid {
|
|
return details.RequiredSigs
|
|
}
|
|
return 0
|
|
|
|
case STNullData, STTreasuryAdd:
|
|
return 0
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// MultiSigScriptV0 returns a valid version 0 script for a multisignature
|
|
// redemption where the specified threshold number of the keys in the given
|
|
// public keys are required to have signed the transaction for success.
|
|
//
|
|
// The provided public keys must be serialized in the compressed format or an
|
|
// error with kind ErrPubKeyType will be returned.
|
|
//
|
|
// An Error with kind ErrTooManyRequiredSigs will be returned if the threshold
|
|
// is larger than the number of keys provided.
|
|
func MultiSigScriptV0(threshold int, pubKeys ...[]byte) ([]byte, error) {
|
|
if threshold < 0 {
|
|
str := fmt.Sprintf("unable to generate multisig script with %d "+
|
|
"required signatures", threshold)
|
|
return nil, makeError(ErrNegativeRequiredSigs, str)
|
|
}
|
|
if len(pubKeys) < threshold {
|
|
str := fmt.Sprintf("unable to generate multisig script with %d "+
|
|
"required signatures when there are only %d public keys available",
|
|
threshold, len(pubKeys))
|
|
return nil, makeError(ErrTooManyRequiredSigs, str)
|
|
}
|
|
|
|
builder := txscript.NewScriptBuilder().AddInt64(int64(threshold))
|
|
for _, pubKey := range pubKeys {
|
|
if !txscript.IsStrictCompressedPubKeyEncoding(pubKey) {
|
|
str := fmt.Sprintf("unable to generate multisig script with "+
|
|
"unsupported public key %x", pubKey)
|
|
return nil, makeError(ErrPubKeyType, str)
|
|
}
|
|
|
|
builder.AddData(pubKey)
|
|
}
|
|
builder.AddInt64(int64(len(pubKeys)))
|
|
builder.AddOp(txscript.OP_CHECKMULTISIG)
|
|
|
|
return builder.Script()
|
|
}
|
|
|
|
// ProvablyPruneableScriptV0 returns a valid version 0 provably-pruneable script
|
|
// which consists of an OP_RETURN followed by the passed data. An Error with
|
|
// kind ErrTooMuchNullData will be returned if the length of the passed data
|
|
// exceeds MaxDataCarrierSizeV0.
|
|
func ProvablyPruneableScriptV0(data []byte) ([]byte, error) {
|
|
if len(data) > MaxDataCarrierSizeV0 {
|
|
str := fmt.Sprintf("data size %d is larger than max allowed size %d",
|
|
len(data), MaxDataCarrierSizeV0)
|
|
return nil, makeError(ErrTooMuchNullData, str)
|
|
}
|
|
|
|
builder := txscript.NewScriptBuilder()
|
|
return builder.AddOp(txscript.OP_RETURN).AddData(data).Script()
|
|
}
|
|
|
|
// AtomicSwapDataPushesV0 houses the data pushes found in hash-based atomic swap
|
|
// contracts using version 0 scripts.
|
|
type AtomicSwapDataPushesV0 struct {
|
|
RecipientHash160 [20]byte
|
|
RefundHash160 [20]byte
|
|
SecretHash [32]byte
|
|
SecretSize int64
|
|
LockTime int64
|
|
}
|
|
|
|
// ExtractAtomicSwapDataPushesV0 returns the data pushes from an atomic swap
|
|
// contract using version 0 scripts if it is one. It will return nil otherwise.
|
|
//
|
|
// NOTE: Atomic swaps are not considered standard script types by the dcrd
|
|
// mempool policy and should be used with P2SH. The atomic swap format is also
|
|
// expected to change to use a more secure hash function in the future.
|
|
func ExtractAtomicSwapDataPushesV0(redeemScript []byte) *AtomicSwapDataPushesV0 {
|
|
// Local constants for convenience.
|
|
const (
|
|
maxMathOpCodeLen = txscript.MathOpCodeMaxScriptNumLen
|
|
maxCltvLen = txscript.CltvMaxScriptNumLen
|
|
)
|
|
|
|
// An atomic swap is of the form:
|
|
// IF
|
|
// SIZE <secret size> EQUALVERIFY
|
|
// SHA256 <32-byte secret hash> EQUALVERIFY DUP
|
|
// HASH160 <20-byte recipient hash>
|
|
// ELSE
|
|
// <locktime> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <20-byte refund hash>
|
|
// ENDIF
|
|
// EQUALVERIFY CHECKSIG
|
|
type templateMatch struct {
|
|
expectCanonicalInt bool
|
|
maxIntBytes int
|
|
opcode byte
|
|
extractedInt int64
|
|
extractedData []byte
|
|
}
|
|
var template = [20]templateMatch{
|
|
{opcode: txscript.OP_IF},
|
|
{opcode: txscript.OP_SIZE},
|
|
{expectCanonicalInt: true, maxIntBytes: maxMathOpCodeLen},
|
|
{opcode: txscript.OP_EQUALVERIFY},
|
|
{opcode: txscript.OP_SHA256},
|
|
{opcode: txscript.OP_DATA_32},
|
|
{opcode: txscript.OP_EQUALVERIFY},
|
|
{opcode: txscript.OP_DUP},
|
|
{opcode: txscript.OP_HASH160},
|
|
{opcode: txscript.OP_DATA_20},
|
|
{opcode: txscript.OP_ELSE},
|
|
{expectCanonicalInt: true, maxIntBytes: maxCltvLen},
|
|
{opcode: txscript.OP_CHECKLOCKTIMEVERIFY},
|
|
{opcode: txscript.OP_DROP},
|
|
{opcode: txscript.OP_DUP},
|
|
{opcode: txscript.OP_HASH160},
|
|
{opcode: txscript.OP_DATA_20},
|
|
{opcode: txscript.OP_ENDIF},
|
|
{opcode: txscript.OP_EQUALVERIFY},
|
|
{opcode: txscript.OP_CHECKSIG},
|
|
}
|
|
|
|
const scriptVersion = 0
|
|
var templateOffset int
|
|
tokenizer := txscript.MakeScriptTokenizer(scriptVersion, redeemScript)
|
|
for tokenizer.Next() {
|
|
// Not an atomic swap script if it has more opcodes than expected in the
|
|
// template.
|
|
if templateOffset >= len(template) {
|
|
return nil
|
|
}
|
|
|
|
op := tokenizer.Opcode()
|
|
data := tokenizer.Data()
|
|
tplEntry := &template[templateOffset]
|
|
if tplEntry.expectCanonicalInt {
|
|
switch {
|
|
case data != nil:
|
|
val, err := txscript.MakeScriptNum(data, tplEntry.maxIntBytes)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
tplEntry.extractedInt = int64(val)
|
|
|
|
case txscript.IsSmallInt(op):
|
|
tplEntry.extractedInt = int64(txscript.AsSmallInt(op))
|
|
|
|
// Not an atomic swap script if the opcode does not push an int.
|
|
default:
|
|
return nil
|
|
}
|
|
} else {
|
|
if op != tplEntry.opcode {
|
|
return nil
|
|
}
|
|
|
|
tplEntry.extractedData = data
|
|
}
|
|
|
|
templateOffset++
|
|
}
|
|
if err := tokenizer.Err(); err != nil {
|
|
return nil
|
|
}
|
|
if !tokenizer.Done() || templateOffset != len(template) {
|
|
return nil
|
|
}
|
|
|
|
// At this point, the script appears to be an atomic swap, so populate and
|
|
// return the extracted data.
|
|
pushes := AtomicSwapDataPushesV0{
|
|
SecretSize: template[2].extractedInt,
|
|
LockTime: template[11].extractedInt,
|
|
}
|
|
copy(pushes.SecretHash[:], template[5].extractedData)
|
|
copy(pushes.RecipientHash160[:], template[9].extractedData)
|
|
copy(pushes.RefundHash160[:], template[16].extractedData)
|
|
return &pushes
|
|
}
|