Completed
Pull Request — master (#398)
by thomas
70:33
created

OutputClassifier::isNullData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 9
ccs 8
cts 8
cp 1
crap 2
rs 9.6666
c 0
b 0
f 0
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
12
{
13
    const PAYTOPUBKEY = 'pubkey';
14
    const PAYTOPUBKEYHASH = 'pubkeyhash';
15
    const PAYTOSCRIPTHASH = 'scripthash';
16
    const WITNESS_V0_KEYHASH = 'witness_v0_keyhash';
17
    const WITNESS_V0_SCRIPTHASH = 'witness_v0_scripthash';
18
    const MULTISIG = 'multisig';
19
    const NULLDATA = 'nulldata';
20
    const UNKNOWN = 'unknown';
21
22
    /**
23
     * @param Operation[] $decoded
24
     * @return false|BufferInterface
25 216
     */
26
    private function decodeP2PK(array $decoded)
27 216
    {
28 162
        if (count($decoded) !== 2 || !$decoded[0]->isPush()) {
29
            return false;
30
        }
31 102
32 102
        $size = $decoded[0]->getDataSize();
33 54
        if ($size === 33 || $size === 65) {
34 54
            $op = $decoded[1];
35 48
            if ($op->getOp() === Opcodes::OP_CHECKSIG) {
36
                return $decoded[0]->getData();
37 4
            }
38
        }
39 60
40
        return false;
41
    }
42
43
    /**
44
     * @param ScriptInterface $script
45
     * @return bool
46 48
     */
47
    public function isPayToPublicKey(ScriptInterface $script)
48
    {
49 48
        try {
50 2
            return $this->decodeP2PK($script->getScriptParser()->decode()) !== false;
51
        } catch (\Exception $e) {
52
            /** Return false later */
53
        }
54
55
        return false;
56
    }
57
58
    /**
59
     * @param Operation[] $decoded
60
     * @return BufferInterface|false
61 180
     */
62
    private function decodeP2PKH(array $decoded)
63 180
    {
64 120
        if (count($decoded) !== 5) {
65
            return false;
66
        }
67 96
68 96
        $dup = $decoded[0];
69 96
        $hash = $decoded[1];
70 96
        $buf = $decoded[2];
71 96
        $eq = $decoded[3];
72
        $checksig = $decoded[4];
73 96
74
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
75 96
            /** @var Operation $op */
76 70
            if ($op->isPush()) {
77
                return false;
78 32
            }
79
        }
80 84
81 84
        if ($dup->getOp() === Opcodes::OP_DUP
82 84
            && $hash->getOp() === Opcodes::OP_HASH160
83 84
            && $buf->isPush() && $buf->getDataSize() === 20
84 84
            && $eq->getOp() === Opcodes::OP_EQUALVERIFY
85 84
            && $checksig->getOp() === Opcodes::OP_CHECKSIG) {
86
            return $decoded[2]->getData();
87
        }
88 6
89
        return false;
90
    }
91
92
    /**
93
     * @param ScriptInterface $script
94
     * @return bool
95 36
     */
96
    public function isPayToPublicKeyHash(ScriptInterface $script)
97
    {
98 36
        try {
99
            return $this->decodeP2PKH($script->getScriptParser()->decode()) !== false;
100
        } catch (\Exception $e) {
101
            /** Return false later */
102
        }
103
104
        return false;
105
    }
106
107
    /**
108
     * @param array $decoded
109
     * @return bool
110 2063
     */
111
    private function decodeP2SH(array $decoded)
112 2063
    {
113 1675
        if (count($decoded) !== 3) {
114
            return false;
115
        }
116 412
117 412
        $op_hash = $decoded[0];
118 230
        if ($op_hash->isPush() || $op_hash->getOp() !== Opcodes::OP_HASH160) {
119
            return false;
120
        }
121 188
122 188
        $buffer = $decoded[1];
123 10
        if (!$buffer->isPush() || $buffer->getOp() !== 20) {
124
            return false;
125
        }
126 184
127 184
        $eq = $decoded[2];
128 184
        if (!$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL) {
129
            return $decoded[1]->getData();
130
        }
131 6
132
        return false;
133
    }
134
135
    /**
136
     * @param ScriptInterface $script
137
     * @return bool
138 2003
     */
139
    public function isPayToScriptHash(ScriptInterface $script)
140
    {
141 2003
        try {
142
            return $this->decodeP2SH($script->getScriptParser()->decode()) !== false;
143
        } catch (\Exception $e) {
144
            /** Return false later */
145
        }
146
147
        return false;
148
    }
149
150
    /**
151
     * @param Operation[] $decoded
152
     * @return bool|BufferInterface[]
153 156
     */
154
    private function decodeMultisig(array $decoded)
155 156
    {
156 156
        $count = count($decoded);
157 120
        if ($count <= 3) {
158
            return false;
159
        }
160 66
161 66
        $mOp = $decoded[0];
162 66
        $nOp = $decoded[$count - 2];
163 66
        $checksig = $decoded[$count - 1];
164 6
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
165
            return false;
166
        }
167
168 66
        /** @var Operation[] $vKeys */
169 66
        $vKeys = array_slice($decoded, 1, -2);
170 66
        $solutions = [];
171 66
        foreach ($vKeys as $key) {
172 12
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
173
                return false;
174 60
            }
175 20
            $solutions[] = $key->getData();
176
        }
177 60
178 60
        if ($mOp->getOp() >= Opcodes::OP_0
179 60
            && $nOp->getOp() <= Opcodes::OP_16
180 60
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG) {
181
            return $solutions;
182
        }
183 6
184
        return false;
185
    }
186
187
    /**
188
     * @param ScriptInterface $script
189
     * @return bool
190 1076
     */
191
    public function isMultisig(ScriptInterface $script)
192
    {
193 54
        try {
194 1040
            return $this->decodeMultisig($script->getScriptParser()->decode()) !== false;
195
        } catch (\Exception $e) {
196
            /** Return false later */
197
        }
198
199
        return false;
200
    }
201
202
    /**
203
     * @param ScriptInterface $script
204
     * @param Operation[] $decoded
205
     * @return false|BufferInterface
206 90
     */
207
    private function decodeWitnessNoLimit(ScriptInterface $script, array $decoded)
208 90
    {
209 90
        $size = $script->getBuffer()->getSize();
210 36
        if ($size < 4 || $size > 40) {
211
            return false;
212 60
        }
213 18
        if (count($decoded) !== 2 || !$decoded[1]->isPush()) {
214
            return false;
215
        }
216 48
217 48
        $version = $decoded[0]->getOp();
218 6
        if ($version !== Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
219
            return false;
220
        }
221 48
222 48
        $witness = $decoded[1];
223 48
        if ($size === $witness->getDataSize() + 2) {
224
            return $witness->getData();
225
        }
226
227
        return false;
228
    }
229
230
    /**
231
     * @param ScriptInterface $script
232
     * @param int $limit
233
     * @param array $decoded
234
     * @return BufferInterface|false
235 60
     */
236
    private function decodeWithLimit(ScriptInterface $script, $limit, array $decoded)
237 60
    {
238 42
        if (($data = $this->decodeWitnessNoLimit($script, $decoded))) {
239 18
            if ($data->getSize() !== $limit) {
240
                return false;
241
            }
242 42
243
            return $data;
244
        }
245 18
246
        return false;
247
    }
248
249
    /**
250
     * @param ScriptInterface $script
251
     * @param Operation[] $decoded
252
     * @return BufferInterface|false
253 36
     */
254
    private function decodeP2WKH(ScriptInterface $script, array $decoded)
255 36
    {
256
        return $this->decodeWithLimit($script, 20, $decoded);
257
    }
258
259
    /**
260
     * @param ScriptInterface $script
261
     * @param Operation[] $decoded
262
     * @return BufferInterface|false
263 60
     */
264
    private function decodeP2WSH(ScriptInterface $script, array $decoded)
265 60
    {
266
        return $this->decodeWithLimit($script, 32, $decoded);
267
    }
268
269
    /**
270
     * @param ScriptInterface $script
271
     * @return bool
272 30
     */
273
    public function isWitness(ScriptInterface $script)
274
    {
275 30
        try {
276 6
            return $this->decodeWitnessNoLimit($script, $script->getScriptParser()->decode())!== false;
277
        } catch (\Exception $e) {
278
            /** Return false later */
279
        }
280 6
281
        return false;
282
    }
283
284
    /**
285
     * @param Operation[] $decoded
286
     * @return false|BufferInterface
287
     */
288 198
    private function decodeNullData(array $decoded)
289
    {
290 198
        if (count($decoded) !== 2) {
291 198
            return false;
292 198
        }
293
294 198
        if ($decoded[0]->getOp() === Opcodes::OP_RETURN && $decoded[1]->isPush()) {
295 36
            return $decoded[1]->getData();
296 36
        }
297 178
298 78
        return false;
299 78
    }
300 136
301 48
    /**
302 48
     * @param ScriptInterface $script
303 104
     * @return bool
304 48
     */
305 48
    public function isNullData(ScriptInterface $script)
306 72
    {
307 24
        try {
308 24
            return $this->decodeNullData($script->getScriptParser()->decode()) !== false;
309 44
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
310 18
        }
311 18
312 6
        return false;
313
    }
314 198
315
    /**
316
     * @param ScriptInterface $script
317
     * @param mixed $solution
318
     * @return string
319
     */
320
    public function classify(ScriptInterface $script, &$solution = null)
321 108
    {
322
        $decoded = $script->getScriptParser()->decode();
323 108
        $type = self::UNKNOWN;
324 108
        $solution = null;
325 108
326
        if (($pubKey = $this->decodeP2PK($decoded))) {
327
            $type = self::PAYTOPUBKEY;
328
            $solution = $pubKey;
329
        } else if (($pubKeyHash = $this->decodeP2PKH($decoded))) {
330
            $type = self::PAYTOPUBKEYHASH;
331
            $solution = $pubKeyHash;
332
        } else if (($multisig = $this->decodeMultisig($decoded))) {
333
            $type = self::MULTISIG;
334
            $solution = $multisig;
335
        } else if (($scriptHash = $this->decodeP2SH($decoded))) {
336
            $type = self::PAYTOSCRIPTHASH;
337
            $solution = $scriptHash;
338
        } else if (($witnessScriptHash = $this->decodeP2WSH($script, $decoded))) {
339
            $type = self::WITNESS_V0_SCRIPTHASH;
340
            $solution = $witnessScriptHash;
341
        } else if (($witnessKeyHash = $this->decodeP2WKH($script, $decoded))) {
342
            $type = self::WITNESS_V0_KEYHASH;
343
            $solution = $witnessKeyHash;
344
        } else if (($nullData = $this->decodeNullData($decoded))) {
345
            $type = self::NULLDATA;
346
            $solution = $nullData;
347
        }
348
349
        return $type;
350
    }
351
352
    /**
353
     * @param ScriptInterface $script
354
     * @return OutputData
355
     */
356
    public function decode(ScriptInterface $script)
357
    {
358
        $solution = null;
359
        $type = $this->classify($script, $solution);
360
        return new OutputData($type, $script, $solution);
361
    }
362
}
363