Completed
Pull Request — master (#248)
by thomas
58:32 queued 33:01
created

OutputClassifier   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 91.67%

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 53
c 5
b 0
f 1
lcom 1
cbo 6
dl 0
loc 222
ccs 99
cts 108
cp 0.9167
rs 7.4757

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B isPayToPublicKey() 0 17 7
D isPayToPublicKeyHash() 0 30 10
C isPayToScriptHash() 0 25 8
C isMultisig() 0 33 11
D isWitness() 0 28 9
C classify() 0 29 7

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 UNKNOWN = 'unknown';
20
    const NONSTANDARD = 'nonstandard';
21
22
    /**
23
     * @var \BitWasp\Bitcoin\Script\Parser\Operation[]
24
     */
25
    private $decoded;
26
27
    /**
28
     * @var ScriptInterface
29
     */
30
    private $script;
31
32
    /**
33
     * @param ScriptInterface $script
34
     */
35 297
    public function __construct(ScriptInterface $script)
36
    {
37 297
        $this->script = $script;
38 297
        $this->decoded = $script->getScriptParser()->decode();
39 297
    }
40
41
    /**
42
     * @param BufferInterface|null $publicKey
43
     * @return bool
44
     */
45 243
    public function isPayToPublicKey(& $publicKey = null)
46
    {
47 243
        if (count($this->decoded) < 1 || !$this->decoded[0]->isPush()) {
48 195
            return false;
49
        }
50
51 66
        $size = $this->decoded[0]->getDataSize();
52 66
        if ($size === 33 || $size === 65) {
53 48
            $op = $this->decoded[1];
54 48
            if (!$op->isPush() && $op->getOp() === Opcodes::OP_CHECKSIG) {
55 48
                $publicKey = $this->decoded[0]->getData();
56 48
                return true;
57
            }
58
        }
59
60 18
        return false;
61
    }
62
63
    /**
64
     * @param BufferInterface|null $pubKeyHash
65
     * @return bool
66
     */
67 195
    public function isPayToPublicKeyHash(& $pubKeyHash = null)
68
    {
69 195
        if (count($this->decoded) !== 5) {
70 72
            return false;
71
        }
72
73 129
        $dup = $this->decoded[0];
74 129
        $hash = $this->decoded[1];
75 129
        $buf = $this->decoded[2];
76 129
        $eq = $this->decoded[3];
77 129
        $checksig = $this->decoded[4];
78
79 129
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
80
            /** @var Operation $op */
81 129
            if ($op->isPush()) {
82 24
                return false;
83
            }
84 129
        }
85
86 105
        if ($dup->getOp() === Opcodes::OP_DUP
87 105
        && $hash->getOp() === Opcodes::OP_HASH160
88 105
        && $buf->isPush() && $buf->getDataSize() === 20
89 105
        && $eq->getOp() === Opcodes::OP_EQUALVERIFY
90 105
        && $checksig->getOp() === Opcodes::OP_CHECKSIG) {
91 105
            $pubKeyHash = $this->decoded[2]->getData();
92 105
            return true;
93
        }
94
95
        return false;
96
    }
97
98
    /**
99
     * @param BufferInterface|null $scriptHash
100
     * @return bool
101
     */
102 273
    public function isPayToScriptHash(& $scriptHash = null)
103
    {
104 273
        if (count($this->decoded) !== 3) {
105 237
            return false;
106
        }
107
108 114
        $hash = $this->decoded[0];
109 114
        if ($hash->isPush() || !$hash->getOp() === Opcodes::OP_HASH160) {
110
            return false;
111
        }
112
113 114
        $buffer = $this->decoded[1];
114 114
        if (!$buffer->isPush() || $buffer->getDataSize() !== 20) {
115
            return false;
116
        }
117
118
119 114
        $eq = $this->decoded[2];
120 114
        if (!$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL) {
121 114
            $scriptHash = $this->decoded[1]->getData();
122 114
            return true;
123
        }
124
125
        return false;
126
    }
127
128
    /**
129
     * @param BufferInterface[] $keys
130
     * @return bool
131
     */
132 195
    public function isMultisig(& $keys = [])
133
    {
134 195
        $count = count($this->decoded);
135 195
        if ($count <= 3) {
136 60
            return false;
137
        }
138
139 135
        $mOp = $this->decoded[0];
140 135
        $nOp = $this->decoded[$count - 2];
141 135
        $checksig = $this->decoded[$count - 1];
142 135
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
143
            return false;
144
        }
145
146
        /** @var Operation[] $vKeys */
147 135
        $vKeys = array_slice($this->decoded, 1, -2);
148 135
        $solutions = [];
149 135
        foreach ($vKeys as $key) {
150 135
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
151 57
                return false;
152
            }
153 78
            $solutions[] = $key->getData();
154 78
        }
155
156 78
        if ($mOp->getOp() >= Opcodes::OP_0
157 78
            && $nOp->getOp() <= Opcodes::OP_16
158 78
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG) {
159 78
            $keys = $solutions;
160 78
            return true;
161
        }
162
163
        return false;
164
    }
165
166
    /**
167
     * @param BufferInterface $programHash
168
     * @return bool
169
     */
170 297
    public function isWitness(& $programHash = null)
171
    {
172 144
        $buffer = $this->script->getBuffer();
173 144
        $size = $buffer->getSize();
174
175 144
        if ($size < 4 || $size > 34) {
176 102
            return false;
177
        }
178
179 60
        $parser = $this->script->getScriptParser();
180 60
        $script = $parser->decode();
181 60
        if (count($script) !== 2 || !$script[1]->isPush()) {
182 48
            return false;
183
        }
184
185 30
        $version = $script[0]->getOp();
186 30
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
187
            return false;
188
        }
189
190 30
        $witness = $script[1];
191 30
        if ($size === $witness->getDataSize() + 2) {
192 30
            $programHash = $witness->getData();
193 30
            return true;
194 297
        }
195
196
        return false;
197
    }
198
199
    /**
200
     * @param BufferInterface|BufferInterface[] $solutions
201
     * @return string
202
     */
203 150
    public function classify(&$solutions = null)
204
    {
205 150
        $type = self::UNKNOWN;
206 150
        $solution = null;
207 150
        if ($this->isPayToScriptHash($solution)) {
208
            /** @var BufferInterface $solution */
209 36
            $type = self::PAYTOSCRIPTHASH;
210 150
        } elseif ($this->isWitness($solution)) {
211
            /** @var BufferInterface $solution */
212 30
            if ($solution->getSize() == 20) {
213 12
                $type = self::WITNESS_V0_KEYHASH;
214 12
            } else {
215 18
                $type = self::WITNESS_V0_SCRIPTHASH;
216
            }
217 144
        } elseif ($this->isPayToPublicKey($solution)) {
218
            /** @var BufferInterface $solution */
219 12
            $type = self::PAYTOPUBKEY;
220 144
        } elseif ($this->isPayToPublicKeyHash($solution)) {
221
            /** @var BufferInterface $solution */
222 48
            $type = self::PAYTOPUBKEYHASH;
223 132
        } elseif ($this->isMultisig($solution)) {
0 ignored issues
show
Documentation introduced by
$solution is of type object<BitWasp\Buffertools\BufferInterface>|null, but the function expects a array<integer,object<Bit...tools\BufferInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
224
            /** @var BufferInterface[] $solution */
225 72
            $type = self::MULTISIG;
226 72
        }
227
228 150
        $solutions = $solution;
229
230 150
        return $type;
231
    }
232
}
233