Completed
Push — master ( b59571...1ee45e )
by thomas
55:12 queued 52:48
created

Script::countP2shSigOps()   C

Complexity

Conditions 7
Paths 17

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.1429

Importance

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