Completed
Pull Request — master (#239)
by thomas
49:01 queued 45:29
created

Script::isWitness()   C

Complexity

Conditions 8
Paths 5

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

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