Completed
Pull Request — master (#241)
by thomas
133:23 queued 63:06
created

OutputClassifier   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 97.06%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 49
c 2
b 0
f 1
lcom 1
cbo 5
dl 0
loc 174
ccs 66
cts 68
cp 0.9706
rs 8.5454

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B isPayToPublicKey() 0 16 7
D isPayToPublicKeyHash() 0 25 9
B isPayToScriptHash() 0 19 7
D isMultisig() 0 26 10
D isWitness() 0 27 9
B classify() 0 16 6

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
10
class OutputClassifier implements ScriptClassifierInterface
11
{
12
    /**
13
     * @var \BitWasp\Bitcoin\Script\Parser\Operation[]
14
     */
15
    private $decoded;
16
17
    /**
18
     * @var ScriptInterface
19
     */
20 340
    private $script;
21
22 340
    /**
23 340
     * @param ScriptInterface $script
24
     */
25
    public function __construct(ScriptInterface $script)
26
    {
27
        $this->script = $script;
28 202
        $this->decoded = $script->getScriptParser()->decode();
29
    }
30 202
31 160
    /**
32
     * @return bool
33
     */
34 60
    public function isPayToPublicKey()
35 60
    {
36 42
        if (count($this->decoded) < 1 || !$this->decoded[0]->isPush()) {
37 42
            return false;
38 42
        }
39
40
        $size = $this->decoded[0]->getDataSize();
41
        if ($size === 33 || $size === 65) {
42 18
            $op = $this->decoded[1];
43
            if (!$op->isPush() && $op->getOp() === Opcodes::OP_CHECKSIG) {
44
                return true;
45
            }
46
        }
47
48 160
        return false;
49
    }
50 160
51 60
    /**
52
     * @return bool
53
     */
54 112
    public function isPayToPublicKeyHash()
55 112
    {
56 112
        if (count($this->decoded) !== 5) {
57 112
            return false;
58 112
        }
59
60 112
        $dup = $this->decoded[0];
61
        $hash = $this->decoded[1];
62 112
        $buf = $this->decoded[2];
63 24
        $eq = $this->decoded[3];
64
        $checksig = $this->decoded[4];
65 112
66
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
67 88
            /** @var Operation $op */
68 88
            if ($op->isPush()) {
69 88
                return false;
70 88
            }
71 88
        }
72
73
        return $dup->getOp() === Opcodes::OP_DUP
74
        && $hash->getOp() === Opcodes::OP_HASH160
75
        && $buf->isPush() && $buf->getDataSize() === 20
76
        && $eq->getOp() === Opcodes::OP_EQUALVERIFY
77 292
        && $checksig->getOp() === Opcodes::OP_CHECKSIG;
78
    }
79 292
80 238
    /**
81
     * @return bool
82
     */
83 108
    public function isPayToScriptHash()
84 108
    {
85 18
        if (count($this->decoded) !== 3) {
86
            return false;
87
        }
88 90
89 90
        $hash = $this->decoded[0];
90 6
        if ($hash->isPush() || !$hash->getOp() === Opcodes::OP_HASH160) {
91
            return false;
92
        }
93 84
94 84
        $buffer = $this->decoded[1];
95
        if (!$buffer->isPush() || $buffer->getDataSize() !== 20) {
96
            return false;
97
        }
98
99
        $eq = $this->decoded[2];
100 172
        return !$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL;
101
    }
102 172
103 172
    /**
104 60
     * @return bool
105
     */
106
    public function isMultisig()
107 112
    {
108 112
        $count = count($this->decoded);
109 112
        if ($count <= 3) {
110 112
            return false;
111
        }
112
113
        $mOp = $this->decoded[0];
114
        $nOp = $this->decoded[$count - 2];
115 112
        $checksig = $this->decoded[$count - 1];
116 112
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
117 112
            return false;
118 58
        }
119
120 54
        /** @var Operation[] $vKeys */
121
        $vKeys = array_slice($this->decoded, 1, -2);
122 54
        foreach ($vKeys as $key) {
123 54
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
124 54
                return false;
125
            }
126
        }
127
128
        return $mOp->getOp() >= Opcodes::OP_0
129
            && $nOp->getOp() <= Opcodes::OP_16
130 102
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG;
131
    }
132 102
133 6
    /**
134 96
     * @return bool
135 30
     */
136 78
    public function isWitness()
137 18
    {
138 66
        $buffer = $this->script->getBuffer();
139 48
        $size = $buffer->getSize();
140
141
        if ($size < 4 || $size > 34) {
142 18
            return false;
143
        }
144
145
        $parser = $this->script->getScriptParser();
146
        $script = $parser->decode();
147
        if (count($script) !== 2 || !$script[1]->isPush()) {
148
            return false;
149
        }
150
151
        $version = $script[0]->getOp();
152
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
153
            return false;
154
        }
155
156
        $witness = $script[1];
157
        if ($size === $witness->getDataSize() + 2) {
158
            return true;
159
        }
160
161
        return false;
162
    }
163
164
    /**
165
     * @return string
166
     */
167
    public function classify()
168
    {
169
        if ($this->isPayToScriptHash()) {
170
            return self::PAYTOSCRIPTHASH;
171
        } elseif ($this->isWitness()) {
172
            return self::WITNESS;
173
        } elseif ($this->isPayToPublicKey()) {
174
            return self::PAYTOPUBKEY;
175
        } elseif ($this->isPayToPublicKeyHash()) {
176
            return self::PAYTOPUBKEYHASH;
177
        } elseif ($this->isMultisig()) {
178
            return self::MULTISIG;
179
        }
180
181
        return self::UNKNOWN;
182
    }
183
}
184