github.com/lbryio/lbcd@v0.22.119/txscript/pkscript.go (about)

     1  package txscript
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/lbryio/lbcd/btcec"
     9  	"github.com/lbryio/lbcd/chaincfg"
    10  	"github.com/lbryio/lbcd/wire"
    11  	btcutil "github.com/lbryio/lbcutil"
    12  	"golang.org/x/crypto/ripemd160"
    13  )
    14  
    15  const (
    16  	// minPubKeyHashSigScriptLen is the minimum length of a signature script
    17  	// that spends a P2PKH output. The length is composed of the following:
    18  	//   Signature length (1 byte)
    19  	//   Signature (min 8 bytes)
    20  	//   Signature hash type (1 byte)
    21  	//   Public key length (1 byte)
    22  	//   Public key (33 byte)
    23  	minPubKeyHashSigScriptLen = 1 + btcec.MinSigLen + 1 + 1 + 33
    24  
    25  	// maxPubKeyHashSigScriptLen is the maximum length of a signature script
    26  	// that spends a P2PKH output. The length is composed of the following:
    27  	//   Signature length (1 byte)
    28  	//   Signature (max 72 bytes)
    29  	//   Signature hash type (1 byte)
    30  	//   Public key length (1 byte)
    31  	//   Public key (33 byte)
    32  	maxPubKeyHashSigScriptLen = 1 + 72 + 1 + 1 + 33
    33  
    34  	// compressedPubKeyLen is the length in bytes of a compressed public
    35  	// key.
    36  	compressedPubKeyLen = 33
    37  
    38  	// pubKeyHashLen is the length of a P2PKH script.
    39  	pubKeyHashLen = 25
    40  
    41  	// witnessV0PubKeyHashLen is the length of a P2WPKH script.
    42  	witnessV0PubKeyHashLen = 22
    43  
    44  	// scriptHashLen is the length of a P2SH script.
    45  	scriptHashLen = 23
    46  
    47  	// witnessV0ScriptHashLen is the length of a P2WSH script.
    48  	witnessV0ScriptHashLen = 34
    49  
    50  	// maxLen is the maximum script length supported by ParsePkScript.
    51  	maxLen = witnessV0ScriptHashLen
    52  )
    53  
    54  var (
    55  	// ErrUnsupportedScriptType is an error returned when we attempt to
    56  	// parse/re-compute an output script into a PkScript struct.
    57  	ErrUnsupportedScriptType = errors.New("unsupported script type")
    58  )
    59  
    60  // PkScript is a wrapper struct around a byte array, allowing it to be used
    61  // as a map index.
    62  type PkScript struct {
    63  	// class is the type of the script encoded within the byte array. This
    64  	// is used to determine the correct length of the script within the byte
    65  	// array.
    66  	class ScriptClass
    67  
    68  	// script is the script contained within a byte array. If the script is
    69  	// smaller than the length of the byte array, it will be padded with 0s
    70  	// at the end.
    71  	script [maxLen]byte
    72  }
    73  
    74  // ParsePkScript parses an output script into the PkScript struct.
    75  // ErrUnsupportedScriptType is returned when attempting to parse an unsupported
    76  // script type.
    77  func ParsePkScript(pkScript []byte) (PkScript, error) {
    78  	var outputScript PkScript
    79  	scriptClass, _, _, err := ExtractPkScriptAddrs(
    80  		pkScript, &chaincfg.MainNetParams,
    81  	)
    82  	if err != nil {
    83  		return outputScript, fmt.Errorf("unable to parse script type: "+
    84  			"%v", err)
    85  	}
    86  
    87  	if !isSupportedScriptType(scriptClass) {
    88  		return outputScript, ErrUnsupportedScriptType
    89  	}
    90  
    91  	outputScript.class = scriptClass
    92  	copy(outputScript.script[:], pkScript)
    93  
    94  	return outputScript, nil
    95  }
    96  
    97  // isSupportedScriptType determines whether the script type is supported by the
    98  // PkScript struct.
    99  func isSupportedScriptType(class ScriptClass) bool {
   100  	switch class {
   101  	case PubKeyHashTy, WitnessV0PubKeyHashTy, ScriptHashTy,
   102  		WitnessV0ScriptHashTy:
   103  		return true
   104  	default:
   105  		return false
   106  	}
   107  }
   108  
   109  // Class returns the script type.
   110  func (s PkScript) Class() ScriptClass {
   111  	return s.class
   112  }
   113  
   114  // Script returns the script as a byte slice without any padding.
   115  func (s PkScript) Script() []byte {
   116  	var script []byte
   117  
   118  	switch s.class {
   119  	case PubKeyHashTy:
   120  		script = make([]byte, pubKeyHashLen)
   121  		copy(script, s.script[:pubKeyHashLen])
   122  
   123  	case WitnessV0PubKeyHashTy:
   124  		script = make([]byte, witnessV0PubKeyHashLen)
   125  		copy(script, s.script[:witnessV0PubKeyHashLen])
   126  
   127  	case ScriptHashTy:
   128  		script = make([]byte, scriptHashLen)
   129  		copy(script, s.script[:scriptHashLen])
   130  
   131  	case WitnessV0ScriptHashTy:
   132  		script = make([]byte, witnessV0ScriptHashLen)
   133  		copy(script, s.script[:witnessV0ScriptHashLen])
   134  
   135  	default:
   136  		// Unsupported script type.
   137  		return nil
   138  	}
   139  
   140  	return script
   141  }
   142  
   143  // Address encodes the script into an address for the given chain.
   144  func (s PkScript) Address(chainParams *chaincfg.Params) (btcutil.Address, error) {
   145  	_, addrs, _, err := ExtractPkScriptAddrs(s.Script(), chainParams)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("unable to parse address: %v", err)
   148  	}
   149  
   150  	return addrs[0], nil
   151  }
   152  
   153  // String returns a hex-encoded string representation of the script.
   154  func (s PkScript) String() string {
   155  	str, _ := DisasmString(s.Script())
   156  	return str
   157  }
   158  
   159  // ComputePkScript computes the script of an output by looking at the spending
   160  // input's signature script or witness.
   161  //
   162  // NOTE: Only P2PKH, P2SH, P2WSH, and P2WPKH redeem scripts are supported.
   163  func ComputePkScript(sigScript []byte, witness wire.TxWitness) (PkScript, error) {
   164  	switch {
   165  	case len(sigScript) > 0:
   166  		return computeNonWitnessPkScript(sigScript)
   167  	case len(witness) > 0:
   168  		return computeWitnessPkScript(witness)
   169  	default:
   170  		return PkScript{}, ErrUnsupportedScriptType
   171  	}
   172  }
   173  
   174  // computeNonWitnessPkScript computes the script of an output by looking at the
   175  // spending input's signature script.
   176  func computeNonWitnessPkScript(sigScript []byte) (PkScript, error) {
   177  	switch {
   178  	// Since we only support P2PKH and P2SH scripts as the only non-witness
   179  	// script types, we should expect to see a push only script.
   180  	case !IsPushOnlyScript(sigScript):
   181  		return PkScript{}, ErrUnsupportedScriptType
   182  
   183  	// If a signature script is provided with a length long enough to
   184  	// represent a P2PKH script, then we'll attempt to parse the compressed
   185  	// public key from it.
   186  	case len(sigScript) >= minPubKeyHashSigScriptLen &&
   187  		len(sigScript) <= maxPubKeyHashSigScriptLen:
   188  
   189  		// The public key should be found as the last part of the
   190  		// signature script. We'll attempt to parse it to ensure this is
   191  		// a P2PKH redeem script.
   192  		pubKey := sigScript[len(sigScript)-compressedPubKeyLen:]
   193  		if btcec.IsCompressedPubKey(pubKey) {
   194  			pubKeyHash := hash160(pubKey)
   195  			script, err := payToPubKeyHashScript(pubKeyHash)
   196  			if err != nil {
   197  				return PkScript{}, err
   198  			}
   199  
   200  			pkScript := PkScript{class: PubKeyHashTy}
   201  			copy(pkScript.script[:], script)
   202  			return pkScript, nil
   203  		}
   204  
   205  		fallthrough
   206  
   207  	// If we failed to parse a compressed public key from the script in the
   208  	// case above, or if the script length is not that of a P2PKH one, we
   209  	// can assume it's a P2SH signature script.
   210  	default:
   211  		// The redeem script will always be the last data push of the
   212  		// signature script, so we'll parse the script into opcodes to
   213  		// obtain it.
   214  		const scriptVersion = 0
   215  		err := checkScriptParses(scriptVersion, sigScript)
   216  		if err != nil {
   217  			return PkScript{}, err
   218  		}
   219  		redeemScript := finalOpcodeData(scriptVersion, sigScript)
   220  
   221  		scriptHash := hash160(redeemScript)
   222  		script, err := payToScriptHashScript(scriptHash)
   223  		if err != nil {
   224  			return PkScript{}, err
   225  		}
   226  
   227  		pkScript := PkScript{class: ScriptHashTy}
   228  		copy(pkScript.script[:], script)
   229  		return pkScript, nil
   230  	}
   231  }
   232  
   233  // computeWitnessPkScript computes the script of an output by looking at the
   234  // spending input's witness.
   235  func computeWitnessPkScript(witness wire.TxWitness) (PkScript, error) {
   236  	// We'll use the last item of the witness stack to determine the proper
   237  	// witness type.
   238  	lastWitnessItem := witness[len(witness)-1]
   239  
   240  	var pkScript PkScript
   241  	switch {
   242  	// If the witness stack has a size of 2 and its last item is a
   243  	// compressed public key, then this is a P2WPKH witness.
   244  	case len(witness) == 2 && len(lastWitnessItem) == compressedPubKeyLen:
   245  		pubKeyHash := hash160(lastWitnessItem)
   246  		script, err := payToWitnessPubKeyHashScript(pubKeyHash)
   247  		if err != nil {
   248  			return pkScript, err
   249  		}
   250  
   251  		pkScript.class = WitnessV0PubKeyHashTy
   252  		copy(pkScript.script[:], script)
   253  
   254  	// For any other witnesses, we'll assume it's a P2WSH witness.
   255  	default:
   256  		scriptHash := sha256.Sum256(lastWitnessItem)
   257  		script, err := payToWitnessScriptHashScript(scriptHash[:])
   258  		if err != nil {
   259  			return pkScript, err
   260  		}
   261  
   262  		pkScript.class = WitnessV0ScriptHashTy
   263  		copy(pkScript.script[:], script)
   264  	}
   265  
   266  	return pkScript, nil
   267  }
   268  
   269  // hash160 returns the RIPEMD160 hash of the SHA-256 HASH of the given data.
   270  func hash160(data []byte) []byte {
   271  	h := sha256.Sum256(data)
   272  	return ripemd160h(h[:])
   273  }
   274  
   275  // ripemd160h returns the RIPEMD160 hash of the given data.
   276  func ripemd160h(data []byte) []byte {
   277  	h := ripemd160.New()
   278  	h.Write(data)
   279  	return h.Sum(nil)
   280  }