github.com/decred/dcrlnd@v0.7.6/chainscan/pkscript.go (about)

     1  // Adapted from the upstream decred/dcrd file contained in the txscript
     2  // package.
     3  
     4  package chainscan
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    12  	"github.com/decred/dcrd/txscript/v4/stdscript"
    13  
    14  	"github.com/decred/dcrd/chaincfg/v3"
    15  
    16  	"github.com/decred/dcrd/dcrutil/v4"
    17  	"github.com/decred/dcrd/txscript/v4"
    18  )
    19  
    20  const (
    21  	// minSigLen is the minimum length of a signature data push (a
    22  	// DER-encoded ECDSA signature) in a p2pkh sigScript.
    23  	minSigLen = 8
    24  
    25  	// maxSigLen is the maximum length of a signature data push (a
    26  	// DER-encoded ECDSA signature) in a p2pkh sigScript.
    27  	maxSigLen = 72
    28  
    29  	// compressedPubKeyLen is the length in bytes of a compressed public
    30  	// key.
    31  	compressedPubKeyLen = 33
    32  
    33  	// pubKeyHashLen is the length of a P2PKH script.
    34  	pubKeyHashLen = 25
    35  
    36  	// scriptHashLen is the length of a P2SH script.
    37  	scriptHashLen = 23
    38  
    39  	// maxLen is the maximum script length supported by ParsePkScript.
    40  	maxLen = pubKeyHashLen
    41  )
    42  
    43  var (
    44  	// ErrUnsupportedScriptType is an error returned when we attempt to
    45  	// parse/re-compute an output script into a PkScript struct.
    46  	ErrUnsupportedScriptType = errors.New("unsupported script type")
    47  )
    48  
    49  // PkScript is a wrapper struct around a byte array, allowing it to be used
    50  // as a map index.
    51  type PkScript struct {
    52  	// class is the type of the script encoded within the byte array. This
    53  	// is used to determine the correct length of the script within the byte
    54  	// array.
    55  	class stdscript.ScriptType
    56  
    57  	// script is the script contained within a byte array. If the script is
    58  	// smaller than the length of the byte array, it will be padded with 0s
    59  	// at the end.
    60  	script [maxLen]byte
    61  
    62  	// scriptVersion is the script version of the given pkscript. Given
    63  	// this is _not_ embedded in the pkscript itself, it must be provided
    64  	// externally.
    65  	scriptVersion uint16
    66  }
    67  
    68  // ParsePkScript parses an output script into the PkScript struct.
    69  // ErrUnsupportedScriptType is returned when attempting to parse an unsupported
    70  // script type.
    71  func ParsePkScript(scriptVersion uint16, pkScript []byte) (PkScript, error) {
    72  	if scriptVersion != 0 {
    73  		return PkScript{}, fmt.Errorf("unsupported script version %d "+
    74  			"(only supports version 0)", scriptVersion)
    75  	}
    76  
    77  	outputScript := PkScript{scriptVersion: scriptVersion}
    78  	scriptClass, _ := stdscript.ExtractAddrs(
    79  		scriptVersion, pkScript, chaincfg.MainNetParams(),
    80  	)
    81  	if !isSupportedScriptType(scriptClass) {
    82  		return outputScript, ErrUnsupportedScriptType
    83  	}
    84  
    85  	outputScript.class = scriptClass
    86  	copy(outputScript.script[:], pkScript)
    87  
    88  	return outputScript, nil
    89  }
    90  
    91  // isSupportedScriptType determines whether the script type is supported by the
    92  // PkScript struct.
    93  func isSupportedScriptType(class stdscript.ScriptType) bool {
    94  	switch class {
    95  	case stdscript.STPubKeyHashEcdsaSecp256k1, stdscript.STScriptHash:
    96  		return true
    97  	default:
    98  		return false
    99  	}
   100  }
   101  
   102  // Class returns the script type.
   103  func (s PkScript) Class() stdscript.ScriptType {
   104  	return s.class
   105  }
   106  
   107  // Script returns the script as a byte slice without any padding. This is a
   108  // copy of the original script, therefore it's safe for modification.
   109  func (s PkScript) Script() []byte {
   110  	var script []byte
   111  
   112  	switch s.class {
   113  	case stdscript.STPubKeyHashEcdsaSecp256k1:
   114  		script = make([]byte, pubKeyHashLen)
   115  		copy(script, s.script[:pubKeyHashLen])
   116  
   117  	case stdscript.STScriptHash:
   118  		script = make([]byte, scriptHashLen)
   119  		copy(script, s.script[:scriptHashLen])
   120  
   121  	default:
   122  		// Unsupported script type.
   123  		return nil
   124  	}
   125  
   126  	return script
   127  }
   128  
   129  // Address encodes the script into an address for the given chain.
   130  func (s PkScript) Address(chainParams *chaincfg.Params) (stdaddr.Address, error) {
   131  	var (
   132  		address stdaddr.Address
   133  		err     error
   134  	)
   135  
   136  	switch s.class {
   137  	case stdscript.STPubKeyHashEcdsaSecp256k1:
   138  		scriptHash := s.script[3:23]
   139  		address, err = stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(
   140  			scriptHash, chainParams,
   141  		)
   142  	case stdscript.STScriptHash:
   143  		scriptHash := s.script[1:21]
   144  		address, err = stdaddr.NewAddressScriptHashV0FromHash(
   145  			scriptHash, chainParams,
   146  		)
   147  	default:
   148  		err = ErrUnsupportedScriptType
   149  	}
   150  
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	return address, nil
   155  }
   156  
   157  // String returns a hex-encoded string representation of the script.
   158  func (s PkScript) String() string {
   159  	str, _ := txscript.DisasmString(s.Script())
   160  	return str
   161  }
   162  
   163  // ScriptVersion returns the recorded script version of the pkscript.
   164  func (s PkScript) ScriptVersion() uint16 {
   165  	return s.scriptVersion
   166  }
   167  
   168  // Equal returns true if the other pkscript is equal to this one (has the same
   169  // values).
   170  func (s PkScript) Equal(o *PkScript) bool {
   171  	var slen int
   172  
   173  	switch s.class {
   174  	case stdscript.STPubKeyHashEcdsaSecp256k1:
   175  		slen = pubKeyHashLen
   176  	case stdscript.STScriptHash:
   177  		slen = scriptHashLen
   178  	default:
   179  		slen = maxLen
   180  	}
   181  
   182  	return s.class == o.class &&
   183  		s.scriptVersion == o.scriptVersion &&
   184  		bytes.Equal(s.script[:slen], o.script[:slen])
   185  }
   186  
   187  // ComputePkScript computes the pkScript of an transaction output by looking at
   188  // the transaction input's signature script.
   189  //
   190  // NOTE: Only P2PKH and P2SH redeem scripts are supported. Only the standard
   191  // secp256k1 keys are supported (alternative suites are not).
   192  func ComputePkScript(scriptVersion uint16, sigScript []byte) (PkScript, error) {
   193  
   194  	var pkScript PkScript
   195  
   196  	if scriptVersion != 0 {
   197  		return pkScript, fmt.Errorf("unsupported script version %d "+
   198  			"(only supports version 0)", scriptVersion)
   199  	}
   200  
   201  	// Ensure that either an input's signature script or a witness was
   202  	// provided.
   203  	if len(sigScript) == 0 {
   204  		return pkScript, ErrUnsupportedScriptType
   205  	}
   206  
   207  	// Create a tokenizer and decode up to the last opcode. Store the first
   208  	// data as well, to check for the correct p2kh sig script style.
   209  	tokenizer := txscript.MakeScriptTokenizer(
   210  		scriptVersion, sigScript,
   211  	)
   212  	var opcodeCount int
   213  	var firstData []byte
   214  	for tokenizer.Next() {
   215  		if tokenizer.Opcode() > txscript.OP_16 {
   216  			return pkScript, ErrUnsupportedScriptType
   217  		}
   218  		if opcodeCount == 0 {
   219  			firstData = tokenizer.Data()
   220  		}
   221  		opcodeCount++
   222  	}
   223  	if tokenizer.Err() != nil {
   224  		return pkScript, tokenizer.Err()
   225  	}
   226  
   227  	var scriptClass stdscript.ScriptType
   228  	var script [maxLen]byte
   229  
   230  	// The last opcode of a sigscript will either be a pubkey (for p2kh
   231  	// pkscripts) or a redeem script (for p2sh pkscripts). Further, a
   232  	// standard p2pkh will only have an extra signature data push.
   233  	lastData := tokenizer.Data()
   234  	lastDataHash := dcrutil.Hash160(lastData)
   235  	firstDataIsSigLen := len(firstData) >= minSigLen && len(firstData) <= maxSigLen
   236  	lastDataIsPubkeyLen := len(lastData) == compressedPubKeyLen
   237  	if opcodeCount == 2 && firstDataIsSigLen && lastDataIsPubkeyLen {
   238  		// The sigScript has the correct structure for spending a
   239  		// p2pkh, therefore assume it is one.
   240  		scriptClass = stdscript.STPubKeyHashEcdsaSecp256k1
   241  		script = [maxLen]byte{
   242  			0: txscript.OP_DUP,
   243  			1: txscript.OP_HASH160,
   244  			2: txscript.OP_DATA_20,
   245  			// 3-23: pubkey hash
   246  			23: txscript.OP_EQUALVERIFY,
   247  			24: txscript.OP_CHECKSIG,
   248  		}
   249  		copy(script[3:23], lastDataHash)
   250  	} else {
   251  		// Assume it's a p2sh.
   252  		scriptClass = stdscript.STScriptHash
   253  		script = [maxLen]byte{
   254  			0: txscript.OP_HASH160,
   255  			1: txscript.OP_DATA_20,
   256  			// 2-22: script hash
   257  			22: txscript.OP_EQUAL,
   258  		}
   259  		copy(script[2:22], lastDataHash)
   260  	}
   261  
   262  	return PkScript{
   263  		class:         scriptClass,
   264  		scriptVersion: scriptVersion,
   265  		script:        script,
   266  	}, nil
   267  }