Completed
Push — master ( eabe8c...61f277 )
by thomas
25:26
created

Script::countP2shSigOps()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.1429

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 17
nop 1
dl 0
loc 25
ccs 12
cts 14
cp 0.8571
crap 7.1429
rs 8.8333
c 0
b 0
f 0
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 5499
    public function __construct(BufferInterface $script = null, Opcodes $opCodes = null)
45
    {
46 5499
        $this->script = $script instanceof BufferInterface ? $script->getBinary() : '';
47 5499
        $this->opCodes = $opCodes ?: new Opcodes();
48 5499
    }
49
50
    /**
51
     * @return BufferInterface
52
     */
53 5517
    public function getBuffer(): BufferInterface
54
    {
55 5517
        return new Buffer($this->script);
56
    }
57
58
    /**
59
     * @return Parser
60
     */
61 4961
    public function getScriptParser(): Parser
62
    {
63 4961
        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 8
                $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
            $subscript = new Script(end($parsed)->getData());
181
            if ($subscript->isWitness($program)) {
182
                /** @var WitnessProgram $program */
183
                return $this->witnessSigOps($program, $scriptWitness);
184
            }
185
        }
186
187
        return 0;
188
    }
189
190
    /**
191
     * @param ScriptInterface $scriptSig
192
     * @return int
193
     */
194 4
    public function countP2shSigOps(ScriptInterface $scriptSig): int
195
    {
196 4
        if (!(new OutputClassifier())->isPayToScriptHash($this)) {
197 2
            return $this->countSigOps(true);
198
        }
199
200
        try {
201 4
            $data = null;
202 4
            foreach ($scriptSig->getScriptParser() as $exec) {
203 4
                if ($exec->getOp() > Opcodes::OP_16) {
204 1
                    return 0;
205
                }
206
207 3
                if ($exec->isPush()) {
208 3
                    $data = $exec->getData();
209
                }
210
            }
211
212 4
            if (!$data instanceof BufferInterface) {
213 1
                return 0;
214
            }
215
216 3
            return (new Script($data))->countSigOps(true);
217
        } catch (\Exception $e) {
218
            return 0;
219
        }
220
    }
221
222
    /**
223
     * @param array|null $ops
224
     * @return bool
225
     */
226 393
    public function isPushOnly(array&$ops = null): bool
227
    {
228 393
        $decoded = $this->getScriptParser()->decode();
229 393
        $data = [];
230 393
        foreach ($decoded as $entity) {
231 336
            if ($entity->getOp() > Opcodes::OP_16) {
232 22
                return false;
233
            }
234
235 326
            if ($entity->getOp() === 0) {
236 64
                $data[] = new Buffer();
237 64
                continue;
238
            }
239
240 324
            $op = $entity->getOp();
241 324
            if ($op >= Opcodes::OP_1 && $op <= Opcodes::OP_16) {
242 39
                $data[] = Number::int(decodeOpN($op))->getBuffer();
243
            } else {
244 324
                $data[] = $entity->getData();
245
            }
246
        }
247 371
        $ops = $data;
248 371
        return true;
249
    }
250
251
    /**
252
     * @param WitnessProgram|null $program
253
     * @return bool
254
     */
255 405
    public function isWitness(& $program = null): bool
256
    {
257 405
        $buffer = $this->getBuffer();
258 405
        $size = $buffer->getSize();
259 405
        if ($size < 4 || $size > 42) {
260 46
            return false;
261
        }
262
263 379
        $script = $this->getScriptParser()->decode();
264 379
        if (!isset($script[0]) || !isset($script[1])) {
265 1
            return false;
266
        }
267
268 378
        $version = $script[0]->getOp();
269 378
        if ($version !== Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
270 207
            return false;
271
        }
272
273 295
        $witness = $script[1];
274 295
        if ($script[1]->isPush() && $size === $witness->getDataSize() + 2) {
275 294
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
276 294
            return true;
277
        }
278
279 1
        return false;
280
    }
281
282
    /**
283
     * @param BufferInterface $scriptHash
284
     * @return bool
285
     */
286 7
    public function isP2SH(& $scriptHash): bool
287
    {
288 7
        if (strlen($this->script) === 23
289 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...
290 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...
291 7
            && $this->script[22] = Opcodes::OP_EQUAL
292
        ) {
293 1
            $scriptHash = new Buffer(substr($this->script, 2, 20));
294 1
            return true;
295
        }
296
297 6
        return false;
298
    }
299
300
    /**
301
     * @param ScriptInterface $script
302
     * @return bool
303
     */
304 255
    public function equals(ScriptInterface $script): bool
305
    {
306 255
        return strcmp($this->script, $script->getBinary()) === 0;
307
    }
308
309
    /**
310
     * @return string
311
     */
312 1
    public function __debugInfo()
313
    {
314
        try {
315 1
            $decoded = $this->getScriptParser()->getHumanReadable();
316
        } catch (\Exception $e) {
317
            $decoded = 'decode failed';
318
        }
319
        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...
320 1
            'hex' => bin2hex($this->script),
321 1
            'asm' => $decoded
322
        ];
323
    }
324
}
325