Completed
Pull Request — master (#277)
by thomas
73:48
created

Script::countP2shSigOps()   C

Complexity

Conditions 7
Paths 17

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7

Importance

Changes 3
Bugs 0 Features 2
Metric Value
dl 0
loc 27
ccs 13
cts 13
cp 1
rs 6.7272
c 3
b 0
f 2
cc 7
eloc 15
nc 17
nop 1
crap 7
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 48
        try {
84 36
            foreach ($parser as $exec) {
85
                $op = $exec->getOp();
86
87 36
                // None of these are pushdatas, so just an opcode
88 18
                if ($op === Opcodes::OP_CHECKSIG || $op === Opcodes::OP_CHECKSIGVERIFY) {
89 36
                    $count++;
90 30
                } elseif ($op === Opcodes::OP_CHECKMULTISIG || $op === Opcodes::OP_CHECKMULTISIGVERIFY) {
91 24
                    if ($accurate && ($lastOp >= Opcodes::OP_1 && $lastOp <= Opcodes::OP_16)) {
92 24
                        $count += decodeOpN($lastOp);
93 12
                    } else {
94
                        $count += 20;
95 30
                    }
96
                }
97 36
98 48
                $lastOp = $op;
99
            }
100 48
        } catch (\Exception $e) {
101
            /* Script parsing failures don't count, and terminate the loop */
102
        }
103
104
        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 18
    /**
160
     * @param ScriptInterface $scriptSig
161 18
     * @return int
162 6
     */
163
    public function countP2shSigOps(ScriptInterface $scriptSig)
164
    {
165 18
        if (!ScriptFactory::scriptPubKey()->classify($this)->isPayToScriptHash()) {
166
            return $this->countSigOps(true);
167 18
        }
168 18
169 18
        try {
170 6
            $data = null;
171
            foreach ($scriptSig->getScriptParser() as $exec) {
172
                if ($exec->getOp() > Opcodes::OP_16) {
173 12
                    return 0;
174 12
                }
175 12
176 18
                if ($exec->isPush()) {
177
                    $data = $exec->getData();
178 18
                }
179 6
            }
180
181
            if (!$data instanceof BufferInterface) {
182 12
                return 0;
183
            }
184
185
            return (new Script($data))->countSigOps(true);
186
        } catch (\Exception $e) {
187
            return 0;
188 138
        }
189
    }
190 84
191 84
    /**
192 12
     * @return bool
193
     */
194 138
    public function isPushOnly()
195
    {
196 72
        foreach ($this->getScriptParser()->decode() as $entity) {
197
            if ($entity->getOp() > Opcodes::OP_16) {
198
                return false;
199
            }
200
        }
201
202
        return true;
203 108
    }
204
205 108
    /**
206 108
     * @param WitnessProgram|null $program
207 108
     * @return bool
208 36
     */
209
    public function isWitness(WitnessProgram & $program = null)
210
    {
211 84
        $buffer = $this->getBuffer();
212 84
        $size = $buffer->getSize();
213 6
        if ($size < 4 || $size > 34) {
214
            return false;
215
        }
216 78
217 78
        $script = $this->getScriptParser()->decode();
218 42
        if (!isset($script[0]) || !isset($script[1])) {
219
            return false;
220
        }
221 48
222 48
        $version = $script[0]->getOp();
223 42
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
224 42
            return false;
225
        }
226
227 6
        $witness = $script[1];
228
        if ($script[1]->isPush() && $size === $witness->getDataSize() + 2) {
229
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
230
            return true;
231
        }
232
233
        return false;
234
    }
235
}
236