Completed
Pull Request — master (#241)
by thomas
133:23 queued 63:06
created

OutputClassifier::isWitness()   D

Complexity

Conditions 9
Paths 5

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 9

Importance

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