github.com/palcoin-project/palcd@v1.0.0/txscript/pkscript.go (about) 1 package txscript 2 3 import ( 4 "crypto/sha256" 5 "errors" 6 "fmt" 7 8 "github.com/palcoin-project/palcd/btcec" 9 "github.com/palcoin-project/palcd/chaincfg" 10 "github.com/palcoin-project/palcd/wire" 11 "github.com/palcoin-project/palcutil" 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) (palcutil.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 parsedOpcodes, err := parseScript(sigScript) 215 if err != nil { 216 return PkScript{}, err 217 } 218 redeemScript := parsedOpcodes[len(parsedOpcodes)-1].data 219 220 scriptHash := hash160(redeemScript) 221 script, err := payToScriptHashScript(scriptHash) 222 if err != nil { 223 return PkScript{}, err 224 } 225 226 pkScript := PkScript{class: ScriptHashTy} 227 copy(pkScript.script[:], script) 228 return pkScript, nil 229 } 230 } 231 232 // computeWitnessPkScript computes the script of an output by looking at the 233 // spending input's witness. 234 func computeWitnessPkScript(witness wire.TxWitness) (PkScript, error) { 235 // We'll use the last item of the witness stack to determine the proper 236 // witness type. 237 lastWitnessItem := witness[len(witness)-1] 238 239 var pkScript PkScript 240 switch { 241 // If the witness stack has a size of 2 and its last item is a 242 // compressed public key, then this is a P2WPKH witness. 243 case len(witness) == 2 && len(lastWitnessItem) == compressedPubKeyLen: 244 pubKeyHash := hash160(lastWitnessItem) 245 script, err := payToWitnessPubKeyHashScript(pubKeyHash) 246 if err != nil { 247 return pkScript, err 248 } 249 250 pkScript.class = WitnessV0PubKeyHashTy 251 copy(pkScript.script[:], script) 252 253 // For any other witnesses, we'll assume it's a P2WSH witness. 254 default: 255 scriptHash := sha256.Sum256(lastWitnessItem) 256 script, err := payToWitnessScriptHashScript(scriptHash[:]) 257 if err != nil { 258 return pkScript, err 259 } 260 261 pkScript.class = WitnessV0ScriptHashTy 262 copy(pkScript.script[:], script) 263 } 264 265 return pkScript, nil 266 } 267 268 // hash160 returns the RIPEMD160 hash of the SHA-256 HASH of the given data. 269 func hash160(data []byte) []byte { 270 h := sha256.Sum256(data) 271 return ripemd160h(h[:]) 272 } 273 274 // ripemd160h returns the RIPEMD160 hash of the given data. 275 func ripemd160h(data []byte) []byte { 276 h := ripemd160.New() 277 h.Write(data) 278 return h.Sum(nil) 279 }