Completed
Pull Request — master (#446)
by thomas
105:00 queued 101:56
created

Script::__debugInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

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