Completed
Pull Request — master (#255)
by thomas
90:53 queued 19:51
created

Script::isWitness()   D

Complexity

Conditions 10
Paths 5

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10.0244

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 0
loc 26
ccs 15
cts 16
cp 0.9375
rs 4.8196
cc 10
eloc 16
nc 5
nop 1
crap 10.0244

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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