Completed
Pull Request — master (#541)
by thomas
77:06 queued 04:02
created

Script::isP2SH()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5.2

Importance

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