123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- package txscript
- import (
- sha256 "github.com/minio/sha256-simd"
- "github.com/pkt-cash/pktd/btcutil/er"
- "github.com/pkt-cash/pktd/txscript/parsescript"
- "github.com/pkt-cash/pktd/btcec"
- "github.com/pkt-cash/pktd/btcutil"
- "github.com/pkt-cash/pktd/chaincfg"
- "github.com/pkt-cash/pktd/wire"
- )
- const (
- // minPubKeyHashSigScriptLen is the minimum length of a signature script
- // that spends a P2PKH output. The length is composed of the following:
- // Signature length (1 byte)
- // Signature (min 8 bytes)
- // Signature hash type (1 byte)
- // Public key length (1 byte)
- // Public key (33 byte)
- minPubKeyHashSigScriptLen = 1 + btcec.MinSigLen + 1 + 1 + 33
- // maxPubKeyHashSigScriptLen is the maximum length of a signature script
- // that spends a P2PKH output. The length is composed of the following:
- // Signature length (1 byte)
- // Signature (max 72 bytes)
- // Signature hash type (1 byte)
- // Public key length (1 byte)
- // Public key (33 byte)
- maxPubKeyHashSigScriptLen = 1 + 72 + 1 + 1 + 33
- // compressedPubKeyLen is the length in bytes of a compressed public
- // key.
- compressedPubKeyLen = 33
- // pubKeyHashLen is the length of a P2PKH script.
- pubKeyHashLen = 25
- // witnessV0PubKeyHashLen is the length of a P2WPKH script.
- witnessV0PubKeyHashLen = 22
- // scriptHashLen is the length of a P2SH script.
- scriptHashLen = 23
- // witnessV0ScriptHashLen is the length of a P2WSH script.
- witnessV0ScriptHashLen = 34
- // maxLen is the maximum script length supported by ParsePkScript.
- maxLen = witnessV0ScriptHashLen
- )
- // ErrUnsupportedScriptType is an error returned when we attempt to
- // parse/re-compute an output script into a PkScript struct.
- var ErrUnsupportedScriptType = er.GenericErrorType.CodeWithDetail("ErrUnsupportedScriptType",
- "unsupported script type")
- // PkScript is a wrapper struct around a byte array, allowing it to be used
- // as a map index.
- type PkScript struct {
- // class is the type of the script encoded within the byte array. This
- // is used to determine the correct length of the script within the byte
- // array.
- class ScriptClass
- // script is the script contained within a byte array. If the script is
- // smaller than the length of the byte array, it will be padded with 0s
- // at the end.
- script [maxLen]byte
- }
- // ParsePkScript parses an output script into the PkScript struct.
- // ErrUnsupportedScriptType is returned when attempting to parse an unsupported
- // script type.
- func ParsePkScript(pkScript []byte) (PkScript, er.R) {
- var outputScript PkScript
- scriptClass, _, _, err := ExtractPkScriptAddrs(
- pkScript, &chaincfg.MainNetParams,
- )
- if err != nil {
- return outputScript, er.Errorf("unable to parse script type: "+
- "%v", err)
- }
- if !isSupportedScriptType(scriptClass) {
- return outputScript, ErrUnsupportedScriptType.Default()
- }
- outputScript.class = scriptClass
- copy(outputScript.script[:], pkScript)
- return outputScript, nil
- }
- // isSupportedScriptType determines whether the script type is supported by the
- // PkScript struct.
- func isSupportedScriptType(class ScriptClass) bool {
- switch class {
- case PubKeyHashTy, WitnessV0PubKeyHashTy, ScriptHashTy,
- WitnessV0ScriptHashTy:
- return true
- default:
- return false
- }
- }
- // Class returns the script type.
- func (s PkScript) Class() ScriptClass {
- return s.class
- }
- // Script returns the script as a byte slice without any padding.
- func (s PkScript) Script() []byte {
- var script []byte
- switch s.class {
- case PubKeyHashTy:
- script = make([]byte, pubKeyHashLen)
- copy(script, s.script[:pubKeyHashLen])
- case WitnessV0PubKeyHashTy:
- script = make([]byte, witnessV0PubKeyHashLen)
- copy(script, s.script[:witnessV0PubKeyHashLen])
- case ScriptHashTy:
- script = make([]byte, scriptHashLen)
- copy(script, s.script[:scriptHashLen])
- case WitnessV0ScriptHashTy:
- script = make([]byte, witnessV0ScriptHashLen)
- copy(script, s.script[:witnessV0ScriptHashLen])
- default:
- // Unsupported script type.
- return nil
- }
- return script
- }
- // Address encodes the script into an address for the given chain.
- func (s PkScript) Address(chainParams *chaincfg.Params) (btcutil.Address, er.R) {
- _, addrs, _, err := ExtractPkScriptAddrs(s.Script(), chainParams)
- if err != nil {
- return nil, er.Errorf("unable to parse address: %v", err)
- }
- return addrs[0], nil
- }
- // String returns a hex-encoded string representation of the script.
- func (s PkScript) String() string {
- str, _ := DisasmString(s.Script())
- return str
- }
- // ComputePkScript computes the script of an output by looking at the spending
- // input's signature script or witness.
- //
- // NOTE: Only P2PKH, P2SH, P2WSH, and P2WPKH redeem scripts are supported.
- func ComputePkScript(sigScript []byte, witness wire.TxWitness) (PkScript, er.R) {
- switch {
- case len(sigScript) > 0:
- return computeNonWitnessPkScript(sigScript)
- case len(witness) > 0:
- return computeWitnessPkScript(witness)
- default:
- return PkScript{}, ErrUnsupportedScriptType.Default()
- }
- }
- // computeNonWitnessPkScript computes the script of an output by looking at the
- // spending input's signature script.
- func computeNonWitnessPkScript(sigScript []byte) (PkScript, er.R) {
- switch {
- // Since we only support P2PKH and P2SH scripts as the only non-witness
- // script types, we should expect to see a push only script.
- case !IsPushOnlyScript(sigScript):
- return PkScript{}, ErrUnsupportedScriptType.Default()
- // If a signature script is provided with a length long enough to
- // represent a P2PKH script, then we'll attempt to parse the compressed
- // public key from it.
- case len(sigScript) >= minPubKeyHashSigScriptLen &&
- len(sigScript) <= maxPubKeyHashSigScriptLen:
- // The public key should be found as the last part of the
- // signature script. We'll attempt to parse it to ensure this is
- // a P2PKH redeem script.
- pubKey := sigScript[len(sigScript)-compressedPubKeyLen:]
- if btcec.IsCompressedPubKey(pubKey) {
- pubKeyHash := btcutil.Hash160(pubKey)
- script, err := payToPubKeyHashScript(pubKeyHash)
- if err != nil {
- return PkScript{}, err
- }
- pkScript := PkScript{class: PubKeyHashTy}
- copy(pkScript.script[:], script)
- return pkScript, nil
- }
- fallthrough
- // If we failed to parse a compressed public key from the script in the
- // case above, or if the script length is not that of a P2PKH one, we
- // can assume it's a P2SH signature script.
- default:
- // The redeem script will always be the last data push of the
- // signature script, so we'll parse the script into opcodes to
- // obtain it.
- parsedOpcodes, err := parsescript.ParseScript(sigScript)
- if err != nil {
- return PkScript{}, err
- }
- redeemScript := parsedOpcodes[len(parsedOpcodes)-1].Data
- scriptHash := btcutil.Hash160(redeemScript)
- script, err := payToScriptHashScript(scriptHash)
- if err != nil {
- return PkScript{}, err
- }
- pkScript := PkScript{class: ScriptHashTy}
- copy(pkScript.script[:], script)
- return pkScript, nil
- }
- }
- // computeWitnessPkScript computes the script of an output by looking at the
- // spending input's witness.
- func computeWitnessPkScript(witness wire.TxWitness) (PkScript, er.R) {
- // We'll use the last item of the witness stack to determine the proper
- // witness type.
- lastWitnessItem := witness[len(witness)-1]
- var pkScript PkScript
- switch {
- // If the witness stack has a size of 2 and its last item is a
- // compressed public key, then this is a P2WPKH witness.
- case len(witness) == 2 && len(lastWitnessItem) == compressedPubKeyLen:
- pubKeyHash := btcutil.Hash160(lastWitnessItem)
- script, err := payToWitnessPubKeyHashScript(pubKeyHash)
- if err != nil {
- return pkScript, err
- }
- pkScript.class = WitnessV0PubKeyHashTy
- copy(pkScript.script[:], script)
- // For any other witnesses, we'll assume it's a P2WSH witness.
- default:
- scriptHash := sha256.Sum256(lastWitnessItem)
- script, err := payToWitnessScriptHashScript(scriptHash[:])
- if err != nil {
- return pkScript, err
- }
- pkScript.class = WitnessV0ScriptHashTy
- copy(pkScript.script[:], script)
- }
- return pkScript, nil
- }
|