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 }