Completed
Push — master ( f7b67d...189a61 )
by thomas
53:21 queued 33:10
created

OutputClassifier::isMultisig()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

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