Completed
Pull Request — master (#398)
by thomas
76:00 queued 73:20
created

OutputClassifier   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 352
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 92.52%

Importance

Changes 0
Metric Value
dl 0
loc 352
ccs 136
cts 147
cp 0.9252
rs 5.5244
c 0
b 0
f 0
wmc 74
lcom 1
cbo 7

17 Methods

Rating   Name   Duplication   Size   Complexity  
B decodeP2PK() 0 16 6
A isPayToPublicKey() 0 10 2
D decodeP2PKH() 0 29 10
A isPayToPublicKeyHash() 0 10 2
C decodeP2SH() 0 23 8
A isPayToScriptHash() 0 10 2
C decodeMultisig() 0 32 11
A isMultisig() 0 10 2
C decodeWitnessNoLimit() 0 22 9
A decodeWithLimit() 0 12 3
A decodeP2WKH() 0 4 1
A decodeP2WSH() 0 4 1
A isWitness() 0 10 2
A decodeNullData() 0 12 4
A isNullData() 0 9 2
C classify() 0 31 8
A decode() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like OutputClassifier often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OutputClassifier, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Classifier;
4
5
use BitWasp\Bitcoin\Script\Parser\Operation;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
7
use BitWasp\Bitcoin\Script\Opcodes;
8
use BitWasp\Bitcoin\Script\ScriptInterface;
9
use BitWasp\Buffertools\BufferInterface;
10
11
class OutputClassifier
12
{
13
    const PAYTOPUBKEY = 'pubkey';
14
    const PAYTOPUBKEYHASH = 'pubkeyhash';
15
    const PAYTOSCRIPTHASH = 'scripthash';
16
    const WITNESS_V0_KEYHASH = 'witness_v0_keyhash';
17
    const WITNESS_V0_SCRIPTHASH = 'witness_v0_scripthash';
18
    const MULTISIG = 'multisig';
19
    const NULLDATA = 'nulldata';
20
    const UNKNOWN = 'unknown';
21
22
    /**
23
     * @param Operation[] $decoded
24
     * @return false|BufferInterface
25
     */
26 222
    private function decodeP2PK(array $decoded)
27
    {
28 222
        if (count($decoded) !== 2 || !$decoded[0]->isPush()) {
29 168
            return false;
30
        }
31
32 102
        $size = $decoded[0]->getDataSize();
33 102
        if ($size === 33 || $size === 65) {
34 54
            $op = $decoded[1];
35 54
            if ($op->getOp() === Opcodes::OP_CHECKSIG) {
36 48
                return $decoded[0]->getData();
37
            }
38 4
        }
39
40 60
        return false;
41
    }
42
43
    /**
44
     * @param ScriptInterface $script
45
     * @return bool
46
     */
47 50
    public function isPayToPublicKey(ScriptInterface $script)
48
    {
49
        try {
50 50
            return $this->decodeP2PK($script->getScriptParser()->decode()) !== false;
51
        } catch (\Exception $e) {
52
            /** Return false later */
53
        }
54
55
        return false;
56
    }
57
58
    /**
59
     * @param Operation[] $decoded
60
     * @return BufferInterface|false
61
     */
62 186
    private function decodeP2PKH(array $decoded)
63
    {
64 186
        if (count($decoded) !== 5) {
65 126
            return false;
66
        }
67
68 96
        $dup = $decoded[0];
69 96
        $hash = $decoded[1];
70 96
        $buf = $decoded[2];
71 96
        $eq = $decoded[3];
72 96
        $checksig = $decoded[4];
73
74 96
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
75
            /** @var Operation $op */
76 96
            if ($op->isPush()) {
77 70
                return false;
78
            }
79 32
        }
80
81 84
        if ($dup->getOp() === Opcodes::OP_DUP
82 84
            && $hash->getOp() === Opcodes::OP_HASH160
83 84
            && $buf->isPush() && $buf->getDataSize() === 20
84 84
            && $eq->getOp() === Opcodes::OP_EQUALVERIFY
85 84
            && $checksig->getOp() === Opcodes::OP_CHECKSIG) {
86 84
            return $decoded[2]->getData();
87
        }
88
89 6
        return false;
90
    }
91
92
    /**
93
     * @param ScriptInterface $script
94
     * @return bool
95
     */
96 36
    public function isPayToPublicKeyHash(ScriptInterface $script)
97
    {
98
        try {
99 36
            return $this->decodeP2PKH($script->getScriptParser()->decode()) !== false;
100
        } catch (\Exception $e) {
101
            /** Return false later */
102
        }
103
104
        return false;
105
    }
106
107
    /**
108
     * @param array $decoded
109
     * @return bool
110
     */
111 2069
    private function decodeP2SH(array $decoded)
112
    {
113 2069
        if (count($decoded) !== 3) {
114 1681
            return false;
115
        }
116
117 412
        $op_hash = $decoded[0];
118 412
        if ($op_hash->isPush() || $op_hash->getOp() !== Opcodes::OP_HASH160) {
119 230
            return false;
120
        }
121
122 188
        $buffer = $decoded[1];
123 188
        if (!$buffer->isPush() || $buffer->getOp() !== 20) {
124 10
            return false;
125
        }
126
127 184
        $eq = $decoded[2];
128 184
        if (!$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL) {
129 184
            return $decoded[1]->getData();
130
        }
131
132 6
        return false;
133
    }
134
135
    /**
136
     * @param ScriptInterface $script
137
     * @return bool
138
     */
139 2003
    public function isPayToScriptHash(ScriptInterface $script)
140
    {
141
        try {
142 2003
            return $this->decodeP2SH($script->getScriptParser()->decode()) !== false;
143
        } catch (\Exception $e) {
144
            /** Return false later */
145
        }
146
147
        return false;
148
    }
149
150
    /**
151
     * @param Operation[] $decoded
152
     * @return bool|BufferInterface[]
153
     */
154 162
    private function decodeMultisig(array $decoded)
155
    {
156 162
        $count = count($decoded);
157 162
        if ($count <= 3) {
158 126
            return false;
159
        }
160
161 66
        $mOp = $decoded[0];
162 66
        $nOp = $decoded[$count - 2];
163 66
        $checksig = $decoded[$count - 1];
164 66
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
165 6
            return false;
166
        }
167
168
        /** @var Operation[] $vKeys */
169 66
        $vKeys = array_slice($decoded, 1, -2);
170 66
        $solutions = [];
171 66
        foreach ($vKeys as $key) {
172 66
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
173 12
                return false;
174
            }
175 60
            $solutions[] = $key->getData();
176 20
        }
177
178 60
        if ($mOp->getOp() >= Opcodes::OP_0
179 60
            && $nOp->getOp() <= Opcodes::OP_16
180 60
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG) {
181 60
            return $solutions;
182
        }
183
184 6
        return false;
185
    }
186
187
    /**
188
     * @param ScriptInterface $script
189
     * @return bool
190
     */
191 1078
    public function isMultisig(ScriptInterface $script)
192
    {
193
        try {
194 1078
            return $this->decodeMultisig($script->getScriptParser()->decode()) !== false;
195
        } catch (\Exception $e) {
196
            /** Return false later */
197
        }
198
199
        return false;
200
    }
201
202
    /**
203
     * @param ScriptInterface $script
204
     * @param Operation[] $decoded
205
     * @return false|BufferInterface
206
     */
207 96
    private function decodeWitnessNoLimit(ScriptInterface $script, array $decoded)
208
    {
209 96
        $size = $script->getBuffer()->getSize();
210 96
        if ($size < 4 || $size > 40) {
211 42
            return false;
212
        }
213 60
        if (count($decoded) !== 2 || !$decoded[1]->isPush()) {
214 18
            return false;
215
        }
216
217 48
        $version = $decoded[0]->getOp();
218 48
        if ($version !== Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
219 6
            return false;
220
        }
221
222 48
        $witness = $decoded[1];
223 48
        if ($size === $witness->getDataSize() + 2) {
224 48
            return $witness->getData();
225
        }
226
227
        return false;
228
    }
229
230
    /**
231
     * @param ScriptInterface $script
232
     * @param int $limit
233
     * @param array $decoded
234
     * @return BufferInterface|false
235
     */
236 66
    private function decodeWithLimit(ScriptInterface $script, $limit, array $decoded)
237
    {
238 66
        if (($data = $this->decodeWitnessNoLimit($script, $decoded))) {
239 42
            if ($data->getSize() !== $limit) {
240 18
                return false;
241
            }
242
243 42
            return $data;
244
        }
245
246 24
        return false;
247
    }
248
249
    /**
250
     * @param ScriptInterface $script
251
     * @param Operation[] $decoded
252
     * @return BufferInterface|false
253
     */
254 42
    private function decodeP2WKH(ScriptInterface $script, array $decoded)
255
    {
256 42
        return $this->decodeWithLimit($script, 20, $decoded);
257
    }
258
259
    /**
260
     * @param ScriptInterface $script
261
     * @param Operation[] $decoded
262
     * @return BufferInterface|false
263
     */
264 66
    private function decodeP2WSH(ScriptInterface $script, array $decoded)
265
    {
266 66
        return $this->decodeWithLimit($script, 32, $decoded);
267
    }
268
269
    /**
270
     * @param ScriptInterface $script
271
     * @return bool
272
     */
273 30
    public function isWitness(ScriptInterface $script)
274
    {
275
        try {
276 30
            return $this->decodeWitnessNoLimit($script, $script->getScriptParser()->decode())!== false;
277 6
        } catch (\Exception $e) {
278
            /** Return false later */
279
        }
280
281 6
        return false;
282
    }
283
284
    /**
285
     * @param Operation[] $decoded
286
     * @return false|BufferInterface
287
     */
288 24
    private function decodeNullData(array $decoded)
289
    {
290 24
        if (count($decoded) !== 2) {
291 18
            return false;
292
        }
293
294 12
        if ($decoded[0]->getOp() === Opcodes::OP_RETURN && $decoded[1]->isPush()) {
295 6
            return $decoded[1]->getData();
296
        }
297
298 6
        return false;
299
    }
300
301
    /**
302
     * @param ScriptInterface $script
303
     * @return bool
304
     */
305 6
    public function isNullData(ScriptInterface $script)
306
    {
307
        try {
308 6
            return $this->decodeNullData($script->getScriptParser()->decode()) !== false;
309
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
310
        }
311
312
        return false;
313
    }
314
315
    /**
316
     * @param ScriptInterface $script
317
     * @param mixed $solution
318
     * @return string
319
     */
320 204
    public function classify(ScriptInterface $script, &$solution = null)
321
    {
322 204
        $decoded = $script->getScriptParser()->decode();
323 204
        $type = self::UNKNOWN;
324 204
        $solution = null;
325
326 204
        if (($pubKey = $this->decodeP2PK($decoded))) {
327 36
            $type = self::PAYTOPUBKEY;
328 36
            $solution = $pubKey;
329 184
        } else if (($pubKeyHash = $this->decodeP2PKH($decoded))) {
330 78
            $type = self::PAYTOPUBKEYHASH;
331 78
            $solution = $pubKeyHash;
332 142
        } else if (($multisig = $this->decodeMultisig($decoded))) {
333 48
            $type = self::MULTISIG;
334 48
            $solution = $multisig;
335 110
        } else if (($scriptHash = $this->decodeP2SH($decoded))) {
336 48
            $type = self::PAYTOSCRIPTHASH;
337 48
            $solution = $scriptHash;
338 78
        } else if (($witnessScriptHash = $this->decodeP2WSH($script, $decoded))) {
339 24
            $type = self::WITNESS_V0_SCRIPTHASH;
340 24
            $solution = $witnessScriptHash;
341 50
        } else if (($witnessKeyHash = $this->decodeP2WKH($script, $decoded))) {
342 18
            $type = self::WITNESS_V0_KEYHASH;
343 18
            $solution = $witnessKeyHash;
344 30
        } else if (($nullData = $this->decodeNullData($decoded))) {
345 6
            $type = self::NULLDATA;
346 6
            $solution = $nullData;
347 2
        }
348
349 204
        return $type;
350
    }
351
352
    /**
353
     * @param ScriptInterface $script
354
     * @return OutputData
355
     */
356 108
    public function decode(ScriptInterface $script)
357
    {
358 108
        $solution = null;
359 108
        $type = $this->classify($script, $solution);
360 108
        return new OutputData($type, $script, $solution);
361
    }
362
}
363