Completed
Pull Request — master (#248)
by thomas
58:32 queued 33:01
created

Script::isPushOnly()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 2
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
            return $this->witnessSigOps($program, $scriptWitness);
0 ignored issues
show
Bug introduced by
It seems like $program defined by null on line 142 can be null; however, BitWasp\Bitcoin\Script\Script::witnessSigOps() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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
                return $this->witnessSigOps($program, $scriptWitness);
0 ignored issues
show
Bug introduced by
It seems like $program defined by null on line 142 can be null; however, BitWasp\Bitcoin\Script\Script::witnessSigOps() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
152
            }
153
        }
154
155
        return 0;
156
    }
157
158
    /**
159
     * @param ScriptInterface $scriptSig
160
     * @return int
161
     */
162 18
    public function countP2shSigOps(ScriptInterface $scriptSig)
163
    {
164 18
        if (ScriptFactory::scriptPubKey()
165 18
            ->classify($this)
166 18
            ->isPayToScriptHash() === false
167 18
        ) {
168 6
            return $this->countSigOps(true);
169
        }
170
171 18
        $parser = $scriptSig->getScriptParser();
172
173 18
        $data = null;
174 18
        foreach ($parser as $exec) {
175 18
            if ($exec->getOp() > Opcodes::OP_16) {
176 6
                return 0;
177
            }
178
179 12
            if ($exec->isPush()) {
180 12
                $data = $exec->getData();
181 12
            }
182 18
        }
183
184 18
        if (!$data instanceof BufferInterface) {
185 6
            return 0;
186
        }
187
188 12
        return (new Script($data))->countSigOps(true);
189
    }
190
191
    /**
192
     * @return bool
193
     */
194 60
    public function isPushOnly()
195
    {
196 60
        $pushOnly = true;
197 60
        foreach ($this->getScriptParser()->decode() as $entity) {
198 60
            $pushOnly &= $entity->isPush();
199 60
        }
200 60
        return $pushOnly;
201
    }
202
203
    /**
204
     * @param WitnessProgram|null $program
205
     * @return bool
206
     */
207 72
    public function isWitness(WitnessProgram & $program = null)
208
    {
209 72
        $buffer = $this->getBuffer();
210 72
        $size = $buffer->getSize();
211 72
        if ($size < 4 || $size > 34) {
212 30
            return false;
213
        }
214
215 54
        $parser = $this->getScriptParser();
216 54
        $script = $parser->decode();
217 54
        if (!$script[1]->isPush()) {
218 12
            return false;
219
        }
220
221 42
        $version = $script[0]->getOp();
222 42
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
223 24
            return false;
224
        }
225
226 30
        $witness = $script[1];
227 30
        if ($size === $witness->getDataSize() + 2) {
228 30
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
229 30
            return true;
230
        }
231
232
        return false;
233
    }
234
}
235