Completed
Pull Request — master (#286)
by thomas
23:49
created

OutputClassifier::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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
    const NONSTANDARD = 'nonstandard';
21
22
    /**
23
     * @param ScriptInterface $script
24
     * @param BufferInterface $publicKey
25
     * @return bool
26
     */
27 162
    public function isPayToPublicKey(ScriptInterface $script, & $publicKey = null)
28
    {
29 162
        $decoded = $script->getScriptParser()->decode();
30 162
        if (count($decoded) !== 2 || $decoded[0]->isPush() === false || $decoded[1]->isPush() === true) {
31 132
            return false;
32
        }
33
34 48
        $size = $decoded[0]->getDataSize();
35 48
        if ($size === 33 || $size === 65) {
36 42
            $op = $decoded[1];
37 42
            if ($op->getOp() === Opcodes::OP_CHECKSIG) {
38 36
                $publicKey = $decoded[0]->getData();
39 36
                return true;
40
            }
41 6
        }
42
43 12
        return false;
44
    }
45
46
    /**
47
     * @param ScriptInterface $script
48
     * @param null $pubKeyHash
49
     * @return bool
50
     */
51 126
    public function isPayToPublicKeyHash(ScriptInterface $script, & $pubKeyHash = null)
52
    {
53 126
        $decoded = $script->getScriptParser()->decode();
54 126
        if (count($decoded) !== 5) {
55 54
            return false;
56
        }
57
58 78
        $dup = $decoded[0];
59 78
        $hash = $decoded[1];
60 78
        $buf = $decoded[2];
61 78
        $eq = $decoded[3];
62 78
        $checksig = $decoded[4];
63
64 78
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
65
            /** @var Operation $op */
66 78
            if ($op->isPush()) {
67 6
                return false;
68
            }
69 78
        }
70
71 72
        if ($dup->getOp() === Opcodes::OP_DUP
72 72
        && $hash->getOp() === Opcodes::OP_HASH160
73 72
        && $buf->isPush() && $buf->getDataSize() === 20
74 72
        && $eq->getOp() === Opcodes::OP_EQUALVERIFY
75 72
        && $checksig->getOp() === Opcodes::OP_CHECKSIG) {
76 72
            $pubKeyHash = $decoded[2]->getData();
77 72
            return true;
78
        }
79
80
        return false;
81
    }
82
83
    /**
84
     * @param ScriptInterface $script
85
     * @param null $scriptHash
86
     * @return bool
87
     */
88 186
    public function isPayToScriptHash(ScriptInterface $script, & $scriptHash = null)
89
    {
90 186
        $decoded = $script->getScriptParser()->decode();
91 186
        if (count($decoded) !== 3) {
92 156
            return false;
93
        }
94
95 72
        $hash = $decoded[0];
96 72
        if ($hash->isPush() || !$hash->getOp() === Opcodes::OP_HASH160) {
97
            return false;
98
        }
99
100 72
        $buffer = $decoded[1];
101 72
        if (!$buffer->isPush() || $buffer->getDataSize() !== 20) {
102
            return false;
103
        }
104
105 72
        $eq = $decoded[2];
106 72
        if (!$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL) {
107 72
            $scriptHash = $decoded[1]->getData();
108 72
            return true;
109
        }
110
111
        return false;
112
    }
113
114
    /**
115
     * @param ScriptInterface $script
116
     * @param array $keys
117
     * @return bool
118
     */
119 84
    public function isMultisig(ScriptInterface $script, & $keys = [])
120
    {
121 84
        $decoded = $script->getScriptParser()->decode();
122 84
        $count = count($decoded);
123 84
        if ($count <= 3) {
124 30
            return false;
125
        }
126
127 54
        $mOp = $decoded[0];
128 54
        $nOp = $decoded[$count - 2];
129 54
        $checksig = $decoded[$count - 1];
130 54
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
131
            return false;
132
        }
133
134
        /** @var Operation[] $vKeys */
135 54
        $vKeys = array_slice($decoded, 1, -2);
136 54
        $solutions = [];
137 54
        foreach ($vKeys as $key) {
138 54
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
139
                return false;
140
            }
141 54
            $solutions[] = $key->getData();
142 54
        }
143
144 54
        if ($mOp->getOp() >= Opcodes::OP_0
145 54
            && $nOp->getOp() <= Opcodes::OP_16
146 54
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG) {
147 54
            $keys = $solutions;
148 54
            return true;
149
        }
150
151
        return false;
152
    }
153
154
    /**
155
     * @param ScriptInterface $script
156
     * @param null $programHash
157
     * @return bool
158
     */
159 150
    public function isWitness(ScriptInterface $script, & $programHash = null)
160
    {
161 150
        $decoded = $script->getScriptParser()->decode();
162 150
        $size = $script->getBuffer()->getSize();
163 150
        if ($size < 4 || $size > 34) {
164 84
            return false;
165
        }
166
167 84
        if (count($decoded) !== 2 || !$decoded[1]->isPush()) {
168 72
            return false;
169
        }
170
171 30
        $version = $decoded[0]->getOp();
172 30
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
173
            return false;
174
        }
175
176 30
        $witness = $decoded[1];
177 30
        if ($size === $witness->getDataSize() + 2) {
178 30
            $programHash = $witness->getData();
179 30
            return true;
180
        }
181
182
        return false;
183
    }
184
185
    /**
186
     * @param ScriptInterface $script
187
     * @param null $solution
188
     * @return string
189
     */
190 216
    public function classify(ScriptInterface $script, &$solution = null)
191
    {
192 156
        $type = self::UNKNOWN;
193 156
        $solution = null;
194 216
        if ($this->isPayToScriptHash($script, $solution)) {
195
            /** @var BufferInterface $solution */
196 42
            $type = self::PAYTOSCRIPTHASH;
197 156
        } elseif ($this->isWitness($script, $solution)) {
198
            /** @var BufferInterface $solution */
199 30
            if ($solution->getSize() == 20) {
200 12
                $type = self::WITNESS_V0_KEYHASH;
201 12
            } else {
202 18
                $type = self::WITNESS_V0_SCRIPTHASH;
203
            }
204 150
        } elseif ($this->isPayToPublicKey($script, $solution)) {
205
            /** @var BufferInterface $solution */
206 24
            $type = self::PAYTOPUBKEY;
207 150
        } elseif ($this->isPayToPublicKeyHash($script, $solution)) {
0 ignored issues
show
Bug introduced by
It seems like $solution can also be of type object<BitWasp\Buffertools\BufferInterface>; however, BitWasp\Bitcoin\Script\C...:isPayToPublicKeyHash() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
208
            /** @var BufferInterface $solution */
209 72
            $type = self::PAYTOPUBKEYHASH;
210 126
        } elseif ($this->isMultisig($script, $solution)) {
0 ignored issues
show
Documentation introduced by
$solution is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
211
            /** @var BufferInterface[] $solution */
212 42
            $type = self::MULTISIG;
213 42
        }
214
215 156
        return $type;
216
    }
217
}
218