github.com/btcsuite/btcd@v0.24.0/txscript/pkscript.go (about)

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