Completed
Push — master ( ce605d...b98633 )
by thomas
24:11 queued 20:21
created

Script   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 74.16%

Importance

Changes 9
Bugs 0 Features 4
Metric Value
wmc 45
c 9
b 0
f 4
lcom 1
cbo 14
dl 0
loc 216
ccs 66
cts 89
cp 0.7416
rs 8.3673

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getBuffer() 0 4 1
A getScriptParser() 0 4 1
A getOpCodes() 0 4 1
A getScriptHash() 0 4 1
A __construct() 0 5 3
D countSigOps() 0 25 9
B witnessSigOps() 0 16 5
B countWitnessSigOps() 0 23 5
B countP2shSigOps() 0 25 6
A isPushOnly() 0 10 3
D isWitness() 0 26 10

How to fix   Complexity   

Complex Class

Complex classes like Script 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 Script, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BitWasp\Bitcoin\Script;
4
5
use BitWasp\Bitcoin\Bitcoin;
6
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
7
use BitWasp\Bitcoin\Script\Interpreter\InterpreterInterface;
8
use BitWasp\Bitcoin\Script\Parser\Parser;
9
use BitWasp\Buffertools\Buffer;
10
use BitWasp\Bitcoin\Crypto\Hash;
11
use BitWasp\Bitcoin\Serializable;
12
use BitWasp\Buffertools\BufferInterface;
13
14
class Script extends Serializable implements ScriptInterface
15
{
16
17
    /**
18
     * @var Opcodes
19
     */
20
    protected $opCodes;
21
22
    /**
23
     * @var string
24
     */
25
    protected $script;
26
27
    /**
28
     * @param BufferInterface $script
29
     * @param Opcodes|null $opCodes
30
     */
31 1731
    public function __construct(BufferInterface $script = null, Opcodes $opCodes = null)
32
    {
33 1731
        $this->script = $script instanceof BufferInterface ? $script->getBinary() : '';
34 1731
        $this->opCodes = $opCodes ?: new Opcodes();
35 1731
    }
36
37
    /**
38
     * @return BufferInterface
39
     */
40 1575
    public function getBuffer()
41
    {
42 1575
        return new Buffer($this->script);
43
    }
44
45
    /**
46
     * @return Parser
47
     */
48 1239
    public function getScriptParser()
49
    {
50 1239
        return new Parser(Bitcoin::getMath(), $this);
51
    }
52
53
    /**
54
     * Get all opcodes
55
     *
56
     * @return Opcodes
57
     */
58 696
    public function getOpCodes()
59
    {
60 696
        return $this->opCodes;
61
    }
62
63
    /**
64
     * Return a buffer containing the hash of this script.
65
     *
66
     * @return BufferInterface
67
     */
68 198
    public function getScriptHash()
69
    {
70 198
        return Hash::sha256ripe160($this->getBuffer());
71
    }
72
73
    /**
74
     * @param bool|true $accurate
75
     * @return int
76
     */
77 48
    public function countSigOps($accurate = true)
78
    {
79 48
        $count = 0;
80 48
        $parser = $this->getScriptParser();
81
82 48
        $lastOp = 0xff;
83 48
        foreach ($parser as $exec) {
84 36
            $op = $exec->getOp();
85
86
            // None of these are pushdatas, so just an opcode
87 36
            if ($op === Opcodes::OP_CHECKSIG || $op === Opcodes::OP_CHECKSIGVERIFY) {
88 18
                $count++;
89 36
            } elseif ($op === Opcodes::OP_CHECKMULTISIG || $op === Opcodes::OP_CHECKMULTISIGVERIFY) {
90 30
                if ($accurate && ($lastOp >= Opcodes::OP_1 && $lastOp <= Opcodes::OP_16)) {
91 24
                    $count += decodeOpN($lastOp);
92 24
                } else {
93 12
                    $count += 20;
94
                }
95 30
            }
96
97 36
            $lastOp = $op;
98 48
        }
99
100 48
        return $count;
101
    }
102
103
    /**
104
     * @param WitnessProgram $program
105
     * @param ScriptWitnessInterface $scriptWitness
106
     * @return int
107
     */
108
    private function witnessSigOps(WitnessProgram $program, ScriptWitnessInterface $scriptWitness)
109
    {
110
        if ($program->getVersion() == 0) {
111
            $size = $program->getProgram()->getSize();
112
            if ($size === 32 && count($scriptWitness) > 0) {
113
                $script = new Script($scriptWitness->bottom());
114
                return $script->countSigOps(true);
115
            }
116
117
            if ($size === 20) {
118
                return 1;
119
            }
120
        }
121
122
        return 0;
123
    }
124
125
    /**
126
     * @param ScriptInterface $scriptSig
127
     * @param ScriptWitnessInterface $scriptWitness
128
     * @param int $flags
129
     * @return int
130
     */
131
    public function countWitnessSigOps(ScriptInterface $scriptSig, ScriptWitnessInterface $scriptWitness, $flags)
132
    {
133
        if ($flags & InterpreterInterface::VERIFY_WITNESS === 0) {
134
            return 0;
135
        }
136
137
        $program = null;
138
        if ($this->isWitness($program)) {
139
            /** @var WitnessProgram $program */
140
            return $this->witnessSigOps($program, $scriptWitness);
141
        }
142
143
        if ((new OutputClassifier($this))->isPayToScriptHash()) {
144
            $parsed = $scriptSig->getScriptParser()->decode();
145
            $subscript = new Script(end($parsed)->getData());
146
            if ($subscript->isWitness($program)) {
147
                /** @var WitnessProgram $program */
148
                return $this->witnessSigOps($program, $scriptWitness);
149
            }
150
        }
151
152
        return 0;
153
    }
154
155
    /**
156
     * @param ScriptInterface $scriptSig
157
     * @return int
158
     */
159 18
    public function countP2shSigOps(ScriptInterface $scriptSig)
160
    {
161 18
        if (!ScriptFactory::scriptPubKey()->classify($this)->isPayToScriptHash()) {
162 6
            return $this->countSigOps(true);
163
        }
164
165 18
        $parser = $scriptSig->getScriptParser();
166
167 18
        $data = null;
168 18
        foreach ($parser as $exec) {
169 18
            if ($exec->getOp() > Opcodes::OP_16) {
170 6
                return 0;
171
            }
172
173 12
            if ($exec->isPush()) {
174 12
                $data = $exec->getData();
175 12
            }
176 18
        }
177
178 18
        if (!$data instanceof BufferInterface) {
179 6
            return 0;
180
        }
181
182 12
        return (new Script($data))->countSigOps(true);
183
    }
184
185
    /**
186
     * @return bool
187
     */
188 138
    public function isPushOnly()
189
    {
190 84
        foreach ($this->getScriptParser()->decode() as $entity) {
191 84
            if ($entity->getOp() > Opcodes::OP_16) {
192 12
                return false;
193
            }
194 138
        }
195
196 72
        return true;
197
    }
198
199
    /**
200
     * @param WitnessProgram|null $program
201
     * @return bool
202
     */
203 108
    public function isWitness(WitnessProgram & $program = null)
204
    {
205 108
        $buffer = $this->getBuffer();
206 108
        $size = $buffer->getSize();
207 108
        if ($size < 4 || $size > 34) {
208 36
            return false;
209
        }
210
211 84
        $script = $this->getScriptParser()->decode();
212 84
        if (!isset($script[0]) || !isset($script[1])) {
213 6
            return false;
214
        }
215
216 78
        $version = $script[0]->getOp();
217 78
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
218 42
            return false;
219
        }
220
221 48
        $witness = $script[1];
222 48
        if ($script[1]->isPush() && $size === $witness->getDataSize() + 2) {
223 42
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
224 42
            return true;
225
        }
226
227 6
        return false;
228
    }
229
}
230