Completed
Pull Request — master (#277)
by thomas
73:48
created

Script   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 74.16%

Importance

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

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 29 10
B witnessSigOps() 0 16 5
B countWitnessSigOps() 0 23 5
C countP2shSigOps() 0 27 7
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 1656
    public function __construct(BufferInterface $script = null, Opcodes $opCodes = null)
32
    {
33 1656
        $this->script = $script instanceof BufferInterface ? $script->getBinary() : '';
34 1656
        $this->opCodes = $opCodes ?: new Opcodes();
35 1656
    }
36
37
    /**
38
     * @return BufferInterface
39
     */
40 1476
    public function getBuffer()
41
    {
42 1476
        return new Buffer($this->script);
43
    }
44
45
    /**
46
     * @return Parser
47
     */
48 1140
    public function getScriptParser()
49
    {
50 1140
        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 156
    public function getScriptHash()
69
    {
70 156
        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
        try {
84 36
            foreach ($parser as $exec) {
85
                $op = $exec->getOp();
86
87 36
                // None of these are pushdatas, so just an opcode
88 18
                if ($op === Opcodes::OP_CHECKSIG || $op === Opcodes::OP_CHECKSIGVERIFY) {
89 36
                    $count++;
90 30
                } elseif ($op === Opcodes::OP_CHECKMULTISIG || $op === Opcodes::OP_CHECKMULTISIGVERIFY) {
91 24
                    if ($accurate && ($lastOp >= Opcodes::OP_1 && $lastOp <= Opcodes::OP_16)) {
92 24
                        $count += decodeOpN($lastOp);
93 12
                    } else {
94
                        $count += 20;
95 30
                    }
96
                }
97 36
98 48
                $lastOp = $op;
99
            }
100 48
        } catch (\Exception $e) {
101
            /* Script parsing failures don't count, and terminate the loop */
102
        }
103
104
        return $count;
105
    }
106
107
    /**
108
     * @param WitnessProgram $program
109
     * @param ScriptWitnessInterface $scriptWitness
110
     * @return int
111
     */
112
    private function witnessSigOps(WitnessProgram $program, ScriptWitnessInterface $scriptWitness)
113
    {
114
        if ($program->getVersion() == 0) {
115
            $size = $program->getProgram()->getSize();
116
            if ($size === 32 && count($scriptWitness) > 0) {
117
                $script = new Script($scriptWitness->bottom());
118
                return $script->countSigOps(true);
119
            }
120
121
            if ($size === 20) {
122
                return 1;
123
            }
124
        }
125
126
        return 0;
127
    }
128
129
    /**
130
     * @param ScriptInterface $scriptSig
131
     * @param ScriptWitnessInterface $scriptWitness
132
     * @param int $flags
133
     * @return int
134
     */
135
    public function countWitnessSigOps(ScriptInterface $scriptSig, ScriptWitnessInterface $scriptWitness, $flags)
136
    {
137
        if ($flags & InterpreterInterface::VERIFY_WITNESS === 0) {
138
            return 0;
139
        }
140
141
        $program = null;
142
        if ($this->isWitness($program)) {
143
            /** @var WitnessProgram $program */
144
            return $this->witnessSigOps($program, $scriptWitness);
145
        }
146
147
        if ((new OutputClassifier($this))->isPayToScriptHash()) {
148
            $parsed = $scriptSig->getScriptParser()->decode();
149
            $subscript = new Script(end($parsed)->getData());
150
            if ($subscript->isWitness($program)) {
151
                /** @var WitnessProgram $program */
152
                return $this->witnessSigOps($program, $scriptWitness);
153
            }
154
        }
155
156
        return 0;
157
    }
158
159 18
    /**
160
     * @param ScriptInterface $scriptSig
161 18
     * @return int
162 6
     */
163
    public function countP2shSigOps(ScriptInterface $scriptSig)
164
    {
165 18
        if (!ScriptFactory::scriptPubKey()->classify($this)->isPayToScriptHash()) {
166
            return $this->countSigOps(true);
167 18
        }
168 18
169 18
        try {
170 6
            $data = null;
171
            foreach ($scriptSig->getScriptParser() as $exec) {
172
                if ($exec->getOp() > Opcodes::OP_16) {
173 12
                    return 0;
174 12
                }
175 12
176 18
                if ($exec->isPush()) {
177
                    $data = $exec->getData();
178 18
                }
179 6
            }
180
181
            if (!$data instanceof BufferInterface) {
182 12
                return 0;
183
            }
184
185
            return (new Script($data))->countSigOps(true);
186
        } catch (\Exception $e) {
187
            return 0;
188 138
        }
189
    }
190 84
191 84
    /**
192 12
     * @return bool
193
     */
194 138
    public function isPushOnly()
195
    {
196 72
        foreach ($this->getScriptParser()->decode() as $entity) {
197
            if ($entity->getOp() > Opcodes::OP_16) {
198
                return false;
199
            }
200
        }
201
202
        return true;
203 108
    }
204
205 108
    /**
206 108
     * @param WitnessProgram|null $program
207 108
     * @return bool
208 36
     */
209
    public function isWitness(WitnessProgram & $program = null)
210
    {
211 84
        $buffer = $this->getBuffer();
212 84
        $size = $buffer->getSize();
213 6
        if ($size < 4 || $size > 34) {
214
            return false;
215
        }
216 78
217 78
        $script = $this->getScriptParser()->decode();
218 42
        if (!isset($script[0]) || !isset($script[1])) {
219
            return false;
220
        }
221 48
222 48
        $version = $script[0]->getOp();
223 42
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
224 42
            return false;
225
        }
226
227 6
        $witness = $script[1];
228
        if ($script[1]->isPush() && $size === $witness->getDataSize() + 2) {
229
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
230
            return true;
231
        }
232
233
        return false;
234
    }
235
}
236