Completed
Pull Request — master (#278)
by thomas
73:26 queued 01:49
created

Script::getScriptHash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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