Completed
Pull Request — master (#241)
by thomas
133:23 queued 63:06
created

Script::countWitnessSigOps()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 21
rs 8.7624
ccs 12
cts 12
cp 1
cc 5
eloc 12
nc 5
nop 3
crap 5
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)
114 18
    {
115 18
        if ($program->getVersion() == 0) {
116 18
            return $program->getOutputScript()->countSigOps(true);
117 6
        }
118
119
        return 0;
120 18
    }
121
122 18
    /**
123 18
     * @param ScriptInterface $scriptSig
124 18
     * @param ScriptWitnessInterface $scriptWitness
125 6
     * @param int $flags
126
     * @return int
127
     */
128 12
    public function countWitnessSigOps(ScriptInterface $scriptSig, ScriptWitnessInterface $scriptWitness, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $scriptWitness is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
129 12
    {
130 12
        if ($flags & InterpreterInterface::VERIFY_WITNESS === 0) {
131 18
            return 0;
132
        }
133 18
134 6
        $program = null;
135
        if ($this->isWitness($program)) {
136
            return $this->witnessSigOps($program);
0 ignored issues
show
Bug introduced by
It seems like $program defined by null on line 134 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...
137 12
        }
138
139
        if ((new OutputClassifier($this))->isPayToScriptHash()) {
140
            $parsed = $scriptSig->getScriptParser()->decode();
141
            $subscript = new Script(end($parsed)->getData());
142
            if ($subscript->isWitness($program)) {
143 18
                return $this->witnessSigOps($program);
0 ignored issues
show
Bug introduced by
It seems like $program defined by null on line 134 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...
144
            }
145 18
        }
146 18
147 18
        return 0;
148 18
    }
149 18
150
    /**
151
     * @param ScriptInterface $scriptSig
152
     * @return int
153
     */
154
    public function countP2shSigOps(ScriptInterface $scriptSig)
155
    {
156
        if (ScriptFactory::scriptPubKey()
157
            ->classify($this)
158
            ->isPayToScriptHash() === false
159
        ) {
160
            return $this->countSigOps(true);
161
        }
162
163
        $parser = $scriptSig->getScriptParser();
164
165
        $data = null;
166
        foreach ($parser as $exec) {
167
            if ($exec->getOp() > Opcodes::OP_16) {
168
                return 0;
169
            }
170
171
            if ($exec->isPush()) {
172
                $data = $exec->getData();
173
            }
174
        }
175
176
        if (!$data instanceof BufferInterface) {
177
            return 0;
178
        }
179
180
        return (new Script($data))->countSigOps(true);
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    public function isPushOnly()
187
    {
188
        $pushOnly = true;
189
        foreach ($this->getScriptParser()->decode() as $entity) {
190
            $pushOnly &= $entity->isPush();
191
        }
192
        return $pushOnly;
193
    }
194
195
    /**
196
     * @param WitnessProgram|null $program
197
     * @return bool
198
     */
199
    public function isWitness(WitnessProgram & $program = null)
200
    {
201
        $buffer = $this->getBuffer();
202
        $size = $buffer->getSize();
203
        if ($size < 4 || $size > 34) {
204
            return false;
205
        }
206
207
        $parser = $this->getScriptParser();
208
        $script = $parser->decode();
209
        if (!$script[1]->isPush()) {
210
            return false;
211
        }
212
213
        $version = $script[0]->getOp();
214
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
215
            return false;
216
        }
217
218
        $witness = $script[1];
219
        if ($size === $witness->getDataSize() + 2) {
220
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
221
            return true;
222
        }
223
224
        return false;
225
    }
226
}
227