Completed
Pull Request — master (#392)
by thomas
190:09 queued 117:15
created

OutputClassifier::classifyold()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

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