Completed
Push — master ( 6a5739...b59571 )
by thomas
103:17 queued 33:16
created

Script::getScriptHash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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