Completed
Pull Request — master (#240)
by thomas
73:04
created

OutputClassifier::isWitness()   D

Complexity

Conditions 9
Paths 5

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 28
rs 4.909
ccs 0
cts 0
cp 0
cc 9
eloc 17
nc 5
nop 1
crap 90
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Classifier;
4
5
use BitWasp\Bitcoin\Script\Parser\Operation;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
7
use BitWasp\Bitcoin\Script\Opcodes;
8
use BitWasp\Bitcoin\Script\ScriptInterface;
9
use BitWasp\Buffertools\BufferInterface;
10
11
class OutputClassifier implements ScriptClassifierInterface
12
{
13
    /**
14
     * @var \BitWasp\Bitcoin\Script\Parser\Operation[]
15
     */
16
    private $decoded;
17
18
    /**
19
     * @var ScriptInterface
20 340
     */
21
    private $script;
22 340
23 340
    /**
24
     * @param ScriptInterface $script
25
     */
26
    public function __construct(ScriptInterface $script)
27
    {
28 202
        $this->script = $script;
29
        $this->decoded = $script->getScriptParser()->decode();
30 202
    }
31 160
32
    /**
33
     * @param BufferInterface|null $publicKey
34 60
     * @return bool
35 60
     */
36 42
    public function isPayToPublicKey(BufferInterface & $publicKey = null)
37 42
    {
38 42
        if (count($this->decoded) < 1 || !$this->decoded[0]->isPush()) {
39
            return false;
40
        }
41
42 18
        $size = $this->decoded[0]->getDataSize();
43
        if ($size === 33 || $size === 65) {
44
            $op = $this->decoded[1];
45
            if (!$op->isPush() && $op->getOp() === Opcodes::OP_CHECKSIG) {
46
                $publicKey = $this->decoded[0];
47
                return true;
48 160
            }
49
        }
50 160
51 60
        return false;
52
    }
53
54 112
    /**
55 112
     * @param BufferInterface|null $pubKeyHash
56 112
     * @return bool
57 112
     */
58 112
    public function isPayToPublicKeyHash(BufferInterface & $pubKeyHash = null)
59
    {
60 112
        if (count($this->decoded) !== 5) {
61
            return false;
62 112
        }
63 24
64
        $dup = $this->decoded[0];
65 112
        $hash = $this->decoded[1];
66
        $buf = $this->decoded[2];
67 88
        $eq = $this->decoded[3];
68 88
        $checksig = $this->decoded[4];
69 88
70 88
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
71 88
            /** @var Operation $op */
72
            if ($op->isPush()) {
73
                return false;
74
            }
75
        }
76
77 292
        if ($dup->getOp() === Opcodes::OP_DUP
78
        && $hash->getOp() === Opcodes::OP_HASH160
79 292
        && $buf->isPush() && $buf->getDataSize() === 20
80 238
        && $eq->getOp() === Opcodes::OP_EQUALVERIFY
81
        && $checksig->getOp() === Opcodes::OP_CHECKSIG) {
82
            $pubKeyHash = $this->decoded[2]->getData();
83 108
            return true;
84 108
        }
85 18
86
        return false;
87
    }
88 90
89 90
    /**
90 6
     * @param BufferInterface|null $scriptHash
91
     * @return bool
92
     */
93 84
    public function isPayToScriptHash(BufferInterface & $scriptHash = null)
94 84
    {
95
        if (count($this->decoded) !== 3) {
96
            return false;
97
        }
98
99
        $hash = $this->decoded[0];
100 172
        if ($hash->isPush() || !$hash->getOp() === Opcodes::OP_HASH160) {
101
            return false;
102 172
        }
103 172
104 60
        $buffer = $this->decoded[1];
105
        if (!$buffer->isPush() || $buffer->getDataSize() !== 20) {
106
            return false;
107 112
        }
108 112
109 112
110 112
        $eq = $this->decoded[2];
111
        if (!$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL) {
112
            $scriptHash = $this->decoded[1]->getData();
113
            return true;
114
        }
115 112
116 112
        return false;
117 112
    }
118 58
119
    /**
120 54
     * @param BufferInterface[] $keys
121
     * @return bool
122 54
     */
123 54
    public function isMultisig(& $keys = [])
124 54
    {
125
        $count = count($this->decoded);
126
        if ($count <= 3) {
127
            return false;
128
        }
129
130 102
        $mOp = $this->decoded[0];
131
        $nOp = $this->decoded[$count - 2];
132 102
        $checksig = $this->decoded[$count - 1];
133 6
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
134 96
            return false;
135 30
        }
136 78
137 18
        /** @var Operation[] $vKeys */
138 66
        $vKeys = array_slice($this->decoded, 1, -2);
139 48
        $solutions = [];
140
        foreach ($vKeys as $key) {
141
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
142 18
                return false;
143
            }
144
            $solutions[] = $key->getData();
145
        }
146
147
        if ($mOp->getOp() >= Opcodes::OP_0
148
            && $nOp->getOp() <= Opcodes::OP_16
149
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG) {
150
            $keys = $solutions;
151
            return true;
152
        }
153
154
        return false;
155
    }
156
157
    /**
158
     * @param BufferInterface $programHash
159
     * @return bool
160
     */
161
    public function isWitness(BufferInterface & $programHash = null)
162
    {
163
        $buffer = $this->script->getBuffer();
164
        $size = $buffer->getSize();
165
166
        if ($size < 4 || $size > 34) {
167
            return false;
168
        }
169
170
        $parser = $this->script->getScriptParser();
171
        $script = $parser->decode();
172
        if (count($script) !== 2 || !$script[1]->isPush()) {
173
            return false;
174
        }
175
176
        $version = $script[0]->getOp();
177
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
178
            return false;
179
        }
180
181
        $witness = $script[1];
182
        if ($size === $witness->getDataSize() + 2) {
183
            $programHash = $witness->getData();
184
            return true;
185
        }
186
187
        return false;
188
    }
189
190
    /**
191
     * @param BufferInterface|BufferInterface[] $solutions
192
     * @return string
193
     */
194
    public function classify(&$solutions = null)
195
    {
196
        $type = self::UNKNOWN;
197
        $solution = null;
0 ignored issues
show
Unused Code introduced by
$solution is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
198
        if ($this->isPayToScriptHash($solutions)) {
0 ignored issues
show
Bug introduced by
It seems like $solutions defined by parameter $solutions on line 194 can also be of type array<integer,object<Bit...tools\BufferInterface>>; however, BitWasp\Bitcoin\Script\C...er::isPayToScriptHash() does only seem to accept null|object<BitWasp\Buffertools\BufferInterface>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
199
            /** @var BufferInterface $solution */
200
            $type = self::PAYTOSCRIPTHASH;
201
        } elseif ($this->isWitness($solutions)) {
202
            /** @var BufferInterface $solution */
203
            if ($solutions->getSize() == 20) {
0 ignored issues
show
Bug introduced by
It seems like $solutions is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
204
                $type = ScriptClassifierInterface::WITNESS_V0_KEYHASH;
205
            } else {
206
                $type = ScriptClassifierInterface::WITNESS_V0_SCRIPTHASH;
207
            }
208
        } elseif ($this->isPayToPublicKey($solutions)) {
209
            /** @var BufferInterface $solution */
210
            return self::PAYTOPUBKEY;
211
        } elseif ($this->isPayToPublicKeyHash($solutions)) {
212
            /** @var BufferInterface $solution */
213
            return self::PAYTOPUBKEYHASH;
214
        } elseif ($this->isMultisig($solutions)) {
0 ignored issues
show
Documentation introduced by
$solutions is of type null|object<BitWasp\Buffertools\BufferInterface>, but the function expects a array<integer,object<Bit...tools\BufferInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
215
            /** @var BufferInterface[] $solution */
216
            return self::MULTISIG;
217
        }
218
219
        return $type;
220
    }
221
}
222