Script   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Test Coverage

Coverage 86.32%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 112
c 2
b 0
f 0
dl 0
loc 308
ccs 101
cts 117
cp 0.8632
rs 3.44
wmc 62

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getWitnessScriptHash() 0 7 2
A getScriptParser() 0 3 1
B countSigOps() 0 28 10
A __construct() 0 4 3
A witnessSigOps() 0 15 5
A getScriptHash() 0 7 2
A getBuffer() 0 3 1
A getOpCodes() 0 3 1
A isP2SH() 0 12 5
B isWitness() 0 25 10
B countP2shSigOps() 0 25 7
A isPushOnly() 0 23 6
A equals() 0 3 1
A countWitnessSigOps() 0 25 6
A __debugInfo() 0 10 2

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.

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
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Script;
6
7
use BitWasp\Bitcoin\Bitcoin;
8
use BitWasp\Bitcoin\Crypto\Hash;
9
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
10
use BitWasp\Bitcoin\Script\Interpreter\InterpreterInterface;
11
use BitWasp\Bitcoin\Script\Interpreter\Number;
12
use BitWasp\Bitcoin\Script\Parser\Parser;
13
use BitWasp\Bitcoin\Serializable;
14
use BitWasp\Buffertools\Buffer;
15
use BitWasp\Buffertools\BufferInterface;
16
17
class Script extends Serializable implements ScriptInterface
18
{
19
20
    /**
21
     * @var Opcodes
22
     */
23
    protected $opCodes;
24
25
    /**
26
     * @var string
27
     */
28
    protected $script;
29
30
    /**
31
     * @var BufferInterface|null
32
     */
33
    protected $scriptHash;
34
35
    /**
36
     * @var BufferInterface|null
37
     */
38
    protected $witnessScriptHash;
39
40
    /**
41
     * @param BufferInterface $script
42
     * @param Opcodes|null $opCodes
43
     */
44 5498
    public function __construct(BufferInterface $script = null, Opcodes $opCodes = null)
45
    {
46 5498
        $this->script = $script instanceof BufferInterface ? $script->getBinary() : '';
47 5498
        $this->opCodes = $opCodes ?: new Opcodes();
48 5498
    }
49
50
    /**
51
     * @return BufferInterface
52
     */
53 5516
    public function getBuffer(): BufferInterface
54
    {
55 5516
        return new Buffer($this->script);
56
    }
57
58
    /**
59
     * @return Parser
60
     */
61 4960
    public function getScriptParser(): Parser
62
    {
63 4960
        return new Parser(Bitcoin::getMath(), $this);
64
    }
65
66
    /**
67
     * Get all opcodes
68
     *
69
     * @return Opcodes
70
     */
71 5
    public function getOpCodes(): Opcodes
72
    {
73 5
        return $this->opCodes;
74
    }
75
76
    /**
77
     * Return a buffer containing the HASH160 of this script.
78
     *
79
     * @return BufferInterface
80
     */
81 82
    public function getScriptHash(): BufferInterface
82
    {
83 82
        if (null === $this->scriptHash) {
84 80
            $this->scriptHash = Hash::sha256ripe160($this->getBuffer());
85
        }
86
87 82
        return $this->scriptHash;
88
    }
89
90
    /**
91
     * Return a buffer containing the SHA256 of this script.
92
     *
93
     * @return BufferInterface
94
     */
95 284
    public function getWitnessScriptHash(): BufferInterface
96
    {
97 284
        if (null === $this->witnessScriptHash) {
98 284
            $this->witnessScriptHash = Hash::sha256($this->getBuffer());
99
        }
100
101 284
        return $this->witnessScriptHash;
102
    }
103
104
    /**
105
     * @param bool|true $accurate
106
     * @return int
107
     */
108 9
    public function countSigOps(bool $accurate = true): int
109
    {
110 9
        $count = 0;
111 9
        $parser = $this->getScriptParser();
112
113 9
        $lastOp = 0xff;
114
        try {
115 9
            foreach ($parser as $exec) {
116 6
                $op = $exec->getOp();
117
118
                // None of these are pushdatas, so just an opcode
119 6
                if ($op === Opcodes::OP_CHECKSIG || $op === Opcodes::OP_CHECKSIGVERIFY) {
120 3
                    $count++;
121 6
                } elseif ($op === Opcodes::OP_CHECKMULTISIG || $op === Opcodes::OP_CHECKMULTISIGVERIFY) {
122 5
                    if ($accurate && ($lastOp >= Opcodes::OP_1 && $lastOp <= Opcodes::OP_16)) {
123 4
                        $count += decodeOpN($lastOp);
124
                    } else {
125 2
                        $count += 20;
126
                    }
127
                }
128
129 6
                $lastOp = $op;
130
            }
131 1
        } catch (\Exception $e) {
132
            /* Script parsing failures don't count, and terminate the loop */
133
        }
134
135 9
        return $count;
136
    }
137
138
    /**
139
     * @param WitnessProgram $program
140
     * @param ScriptWitnessInterface $scriptWitness
141
     * @return int
142
     */
143 1
    private function witnessSigOps(WitnessProgram $program, ScriptWitnessInterface $scriptWitness): int
144
    {
145 1
        if ($program->getVersion() === 0) {
146 1
            $size = $program->getProgram()->getSize();
147 1
            if ($size === 32 && count($scriptWitness) > 0) {
148 1
                $script = new Script($scriptWitness->bottom());
149 1
                return $script->countSigOps(true);
150
            }
151
152
            if ($size === 20) {
153
                return 1;
154
            }
155
        }
156
157
        return 0;
158
    }
159
160
    /**
161
     * @param ScriptInterface $scriptSig
162
     * @param ScriptWitnessInterface $scriptWitness
163
     * @param int $flags
164
     * @return int
165
     */
166 1
    public function countWitnessSigOps(ScriptInterface $scriptSig, ScriptWitnessInterface $scriptWitness, int $flags): int
167
    {
168 1
        if (($flags & InterpreterInterface::VERIFY_WITNESS) === 0) {
169
            return 0;
170
        }
171
172 1
        $program = null;
173 1
        if ($this->isWitness($program)) {
174
            /** @var WitnessProgram $program */
175 1
            return $this->witnessSigOps($program, $scriptWitness);
176
        }
177
178
        if ((new OutputClassifier())->isPayToScriptHash($this)) {
179
            $parsed = $scriptSig->getScriptParser()->decode();
180
            $count = count($parsed);
181
            if ($count > 0) {
182
                $subscript = new Script($parsed[$count - 1]->getData());
183
                if ($subscript->isWitness($program)) {
184
                    /** @var WitnessProgram $program */
185
                    return $this->witnessSigOps($program, $scriptWitness);
186
                }
187
            }
188
        }
189
190
        return 0;
191
    }
192
193
    /**
194
     * @param ScriptInterface $scriptSig
195
     * @return int
196
     */
197 4
    public function countP2shSigOps(ScriptInterface $scriptSig): int
198
    {
199 4
        if (!(new OutputClassifier())->isPayToScriptHash($this)) {
200 2
            return $this->countSigOps(true);
201
        }
202
203
        try {
204 4
            $data = null;
205 4
            foreach ($scriptSig->getScriptParser() as $exec) {
206 4
                if ($exec->getOp() > Opcodes::OP_16) {
207 1
                    return 0;
208
                }
209
210 3
                if ($exec->isPush()) {
211 3
                    $data = $exec->getData();
212
                }
213
            }
214
215 4
            if (!$data instanceof BufferInterface) {
216 1
                return 0;
217
            }
218
219 3
            return (new Script($data))->countSigOps(true);
220
        } catch (\Exception $e) {
221
            return 0;
222
        }
223
    }
224
225
    /**
226
     * @param array|null $ops
227
     * @return bool
228
     */
229 392
    public function isPushOnly(array&$ops = null): bool
230
    {
231 392
        $decoded = $this->getScriptParser()->decode();
232 392
        $data = [];
233 392
        foreach ($decoded as $entity) {
234 336
            if ($entity->getOp() > Opcodes::OP_16) {
235 22
                return false;
236
            }
237
238 326
            if ($entity->getOp() === 0) {
239 64
                $data[] = new Buffer();
240 64
                continue;
241
            }
242
243 324
            $op = $entity->getOp();
244 324
            if ($op >= Opcodes::OP_1 && $op <= Opcodes::OP_16) {
245 39
                $data[] = Number::int(decodeOpN($op))->getBuffer();
246
            } else {
247 323
                $data[] = $entity->getData();
248
            }
249
        }
250 370
        $ops = $data;
251 370
        return true;
252
    }
253
254
    /**
255
     * @param WitnessProgram|null $program
256
     * @return bool
257
     */
258 405
    public function isWitness(& $program = null): bool
259
    {
260 405
        $buffer = $this->getBuffer();
261 405
        $size = $buffer->getSize();
262 405
        if ($size < 4 || $size > 42) {
263 46
            return false;
264
        }
265
266 379
        $script = $this->getScriptParser()->decode();
267 379
        if (!isset($script[0]) || !isset($script[1])) {
268 1
            return false;
269
        }
270
271 378
        $version = $script[0]->getOp();
272 378
        if ($version !== Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
273 207
            return false;
274
        }
275
276 295
        $witness = $script[1];
277 295
        if ($script[1]->isPush() && $size === $witness->getDataSize() + 2) {
278 294
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
279 294
            return true;
280
        }
281
282 1
        return false;
283
    }
284
285
    /**
286
     * @param BufferInterface $scriptHash
287
     * @return bool
288
     */
289 7
    public function isP2SH(& $scriptHash): bool
290
    {
291 7
        if (strlen($this->script) === 23
292 1
            && $this->script[0] = Opcodes::OP_HASH160
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $this->script[0] = (BitW...ript\Opcodes::OP_EQUAL), Probably Intended Meaning: ($this->script[0] = BitW...cript\Opcodes::OP_EQUAL
Loading history...
293 1
            && $this->script[1] = 20
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $this->script[1] = (20 &...ript\Opcodes::OP_EQUAL), Probably Intended Meaning: ($this->script[1] = 20) ...cript\Opcodes::OP_EQUAL
Loading history...
294 7
            && $this->script[22] = Opcodes::OP_EQUAL
295
        ) {
296 1
            $scriptHash = new Buffer(substr($this->script, 2, 20));
297 1
            return true;
298
        }
299
300 6
        return false;
301
    }
302
303
    /**
304
     * @param ScriptInterface $script
305
     * @return bool
306
     */
307 255
    public function equals(ScriptInterface $script): bool
308
    {
309 255
        return strcmp($this->script, $script->getBinary()) === 0;
310
    }
311
312
    /**
313
     * @return string
314
     */
315 1
    public function __debugInfo()
316
    {
317
        try {
318 1
            $decoded = $this->getScriptParser()->getHumanReadable();
319
        } catch (\Exception $e) {
320
            $decoded = 'decode failed';
321
        }
322
        return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('hex' => bi...pt), 'asm' => $decoded) returns the type array<string,string> which is incompatible with the documented return type string.
Loading history...
323 1
            'hex' => bin2hex($this->script),
324 1
            'asm' => $decoded
325
        ];
326
    }
327
}
328