Bit-Wasp /
bitcoin-php
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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 implements ScriptClassifierInterface |
||
| 12 | { |
||
| 13 | /** |
||
| 14 | * @var \BitWasp\Bitcoin\Script\Parser\Operation[] |
||
| 15 | */ |
||
| 16 | private $decoded; |
||
| 17 | |||
| 18 | /** |
||
| 19 | * @var ScriptInterface |
||
| 20 | 340 | */ |
|
| 21 | private $script; |
||
| 22 | 340 | ||
| 23 | 340 | /** |
|
| 24 | * @param ScriptInterface $script |
||
| 25 | */ |
||
| 26 | public function __construct(ScriptInterface $script) |
||
| 27 | { |
||
| 28 | 202 | $this->script = $script; |
|
| 29 | $this->decoded = $script->getScriptParser()->decode(); |
||
| 30 | 202 | } |
|
| 31 | 160 | ||
| 32 | /** |
||
| 33 | * @param BufferInterface|null $publicKey |
||
| 34 | 60 | * @return bool |
|
| 35 | 60 | */ |
|
| 36 | 42 | public function isPayToPublicKey(BufferInterface & $publicKey = null) |
|
| 37 | 42 | { |
|
| 38 | 42 | if (count($this->decoded) < 1 || !$this->decoded[0]->isPush()) { |
|
| 39 | return false; |
||
| 40 | } |
||
| 41 | |||
| 42 | 18 | $size = $this->decoded[0]->getDataSize(); |
|
| 43 | if ($size === 33 || $size === 65) { |
||
| 44 | $op = $this->decoded[1]; |
||
| 45 | if (!$op->isPush() && $op->getOp() === Opcodes::OP_CHECKSIG) { |
||
| 46 | $publicKey = $this->decoded[0]; |
||
| 47 | return true; |
||
| 48 | 160 | } |
|
| 49 | } |
||
| 50 | 160 | ||
| 51 | 60 | return false; |
|
| 52 | } |
||
| 53 | |||
| 54 | 112 | /** |
|
| 55 | 112 | * @param BufferInterface|null $pubKeyHash |
|
| 56 | 112 | * @return bool |
|
| 57 | 112 | */ |
|
| 58 | 112 | public function isPayToPublicKeyHash(BufferInterface & $pubKeyHash = null) |
|
| 59 | { |
||
| 60 | 112 | if (count($this->decoded) !== 5) { |
|
| 61 | return false; |
||
| 62 | 112 | } |
|
| 63 | 24 | ||
| 64 | $dup = $this->decoded[0]; |
||
| 65 | 112 | $hash = $this->decoded[1]; |
|
| 66 | $buf = $this->decoded[2]; |
||
| 67 | 88 | $eq = $this->decoded[3]; |
|
| 68 | 88 | $checksig = $this->decoded[4]; |
|
| 69 | 88 | ||
| 70 | 88 | foreach ([$dup, $hash, $eq, $checksig] as $op) { |
|
| 71 | 88 | /** @var Operation $op */ |
|
| 72 | if ($op->isPush()) { |
||
| 73 | return false; |
||
| 74 | } |
||
| 75 | } |
||
| 76 | |||
| 77 | 292 | if ($dup->getOp() === Opcodes::OP_DUP |
|
| 78 | && $hash->getOp() === Opcodes::OP_HASH160 |
||
| 79 | 292 | && $buf->isPush() && $buf->getDataSize() === 20 |
|
| 80 | 238 | && $eq->getOp() === Opcodes::OP_EQUALVERIFY |
|
| 81 | && $checksig->getOp() === Opcodes::OP_CHECKSIG) { |
||
| 82 | $pubKeyHash = $this->decoded[2]->getData(); |
||
| 83 | 108 | return true; |
|
| 84 | 108 | } |
|
| 85 | 18 | ||
| 86 | return false; |
||
| 87 | } |
||
| 88 | 90 | ||
| 89 | 90 | /** |
|
| 90 | 6 | * @param BufferInterface|null $scriptHash |
|
| 91 | * @return bool |
||
| 92 | */ |
||
| 93 | 84 | public function isPayToScriptHash(BufferInterface & $scriptHash = null) |
|
| 94 | 84 | { |
|
| 95 | if (count($this->decoded) !== 3) { |
||
| 96 | return false; |
||
| 97 | } |
||
| 98 | |||
| 99 | $hash = $this->decoded[0]; |
||
| 100 | 172 | if ($hash->isPush() || !$hash->getOp() === Opcodes::OP_HASH160) { |
|
| 101 | return false; |
||
| 102 | 172 | } |
|
| 103 | 172 | ||
| 104 | 60 | $buffer = $this->decoded[1]; |
|
| 105 | if (!$buffer->isPush() || $buffer->getDataSize() !== 20) { |
||
| 106 | return false; |
||
| 107 | 112 | } |
|
| 108 | 112 | ||
| 109 | 112 | ||
| 110 | 112 | $eq = $this->decoded[2]; |
|
| 111 | if (!$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL) { |
||
| 112 | $scriptHash = $this->decoded[1]->getData(); |
||
| 113 | return true; |
||
| 114 | } |
||
| 115 | 112 | ||
| 116 | 112 | return false; |
|
| 117 | 112 | } |
|
| 118 | 58 | ||
| 119 | /** |
||
| 120 | 54 | * @param BufferInterface[] $keys |
|
| 121 | * @return bool |
||
| 122 | 54 | */ |
|
| 123 | 54 | public function isMultisig(& $keys = []) |
|
| 124 | 54 | { |
|
| 125 | $count = count($this->decoded); |
||
| 126 | if ($count <= 3) { |
||
| 127 | return false; |
||
| 128 | } |
||
| 129 | |||
| 130 | 102 | $mOp = $this->decoded[0]; |
|
| 131 | $nOp = $this->decoded[$count - 2]; |
||
| 132 | 102 | $checksig = $this->decoded[$count - 1]; |
|
| 133 | 6 | if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) { |
|
| 134 | 96 | return false; |
|
| 135 | 30 | } |
|
| 136 | 78 | ||
| 137 | 18 | /** @var Operation[] $vKeys */ |
|
| 138 | 66 | $vKeys = array_slice($this->decoded, 1, -2); |
|
| 139 | 48 | $solutions = []; |
|
| 140 | foreach ($vKeys as $key) { |
||
| 141 | if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) { |
||
| 142 | 18 | return false; |
|
| 143 | } |
||
| 144 | $solutions[] = $key->getData(); |
||
| 145 | } |
||
| 146 | |||
| 147 | if ($mOp->getOp() >= Opcodes::OP_0 |
||
| 148 | && $nOp->getOp() <= Opcodes::OP_16 |
||
| 149 | && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG) { |
||
| 150 | $keys = $solutions; |
||
| 151 | return true; |
||
| 152 | } |
||
| 153 | |||
| 154 | return false; |
||
| 155 | } |
||
| 156 | |||
| 157 | /** |
||
| 158 | * @param BufferInterface $programHash |
||
| 159 | * @return bool |
||
| 160 | */ |
||
| 161 | public function isWitness(BufferInterface & $programHash = null) |
||
| 162 | { |
||
| 163 | $buffer = $this->script->getBuffer(); |
||
| 164 | $size = $buffer->getSize(); |
||
| 165 | |||
| 166 | if ($size < 4 || $size > 34) { |
||
| 167 | return false; |
||
| 168 | } |
||
| 169 | |||
| 170 | $parser = $this->script->getScriptParser(); |
||
| 171 | $script = $parser->decode(); |
||
| 172 | if (count($script) !== 2 || !$script[1]->isPush()) { |
||
| 173 | return false; |
||
| 174 | } |
||
| 175 | |||
| 176 | $version = $script[0]->getOp(); |
||
| 177 | if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) { |
||
| 178 | return false; |
||
| 179 | } |
||
| 180 | |||
| 181 | $witness = $script[1]; |
||
| 182 | if ($size === $witness->getDataSize() + 2) { |
||
| 183 | $programHash = $witness->getData(); |
||
| 184 | return true; |
||
| 185 | } |
||
| 186 | |||
| 187 | return false; |
||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * @param BufferInterface|BufferInterface[] $solutions |
||
| 192 | * @return string |
||
| 193 | */ |
||
| 194 | public function classify(&$solutions = null) |
||
| 195 | { |
||
| 196 | $type = self::UNKNOWN; |
||
| 197 | $solution = null; |
||
|
0 ignored issues
–
show
|
|||
| 198 | if ($this->isPayToScriptHash($solutions)) { |
||
|
0 ignored issues
–
show
It seems like
$solutions defined by parameter $solutions on line 194 can also be of type array<integer,object<Bit...tools\BufferInterface>>; however, BitWasp\Bitcoin\Script\C...er::isPayToScriptHash() does only seem to accept null|object<BitWasp\Buffertools\BufferInterface>, maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. Loading history...
|
|||
| 199 | /** @var BufferInterface $solution */ |
||
| 200 | $type = self::PAYTOSCRIPTHASH; |
||
| 201 | } elseif ($this->isWitness($solutions)) { |
||
| 202 | /** @var BufferInterface $solution */ |
||
| 203 | if ($solutions->getSize() == 20) { |
||
|
0 ignored issues
–
show
It seems like
$solutions is not always an object, but can also be of type null. Maybe add an additional type check?
If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe: function someFunction(A $objectMaybe = null)
{
if ($objectMaybe instanceof A) {
$objectMaybe->doSomething();
}
}
Loading history...
|
|||
| 204 | $type = ScriptClassifierInterface::WITNESS_V0_KEYHASH; |
||
| 205 | } else { |
||
| 206 | $type = ScriptClassifierInterface::WITNESS_V0_SCRIPTHASH; |
||
| 207 | } |
||
| 208 | } elseif ($this->isPayToPublicKey($solutions)) { |
||
| 209 | /** @var BufferInterface $solution */ |
||
| 210 | return self::PAYTOPUBKEY; |
||
| 211 | } elseif ($this->isPayToPublicKeyHash($solutions)) { |
||
| 212 | /** @var BufferInterface $solution */ |
||
| 213 | return self::PAYTOPUBKEYHASH; |
||
| 214 | } elseif ($this->isMultisig($solutions)) { |
||
|
0 ignored issues
–
show
$solutions is of type null|object<BitWasp\Buffertools\BufferInterface>, but the function expects a array<integer,object<Bit...tools\BufferInterface>>.
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...
|
|||
| 215 | /** @var BufferInterface[] $solution */ |
||
| 216 | return self::MULTISIG; |
||
| 217 | } |
||
| 218 | |||
| 219 | return $type; |
||
| 220 | } |
||
| 221 | } |
||
| 222 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVarassignment in line 1 and the$higherassignment in line 2 are dead. The first because$myVaris never used and the second because$higheris always overwritten for every possible time line.