Completed
Pull Request — master (#392)
by thomas
24:31
created

OutputClassifier::decodeP2PKH()   D

Complexity

Conditions 10
Paths 6

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 18
nc 6
nop 1
dl 0
loc 29
ccs 19
cts 19
cp 1
crap 10
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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