Completed
Pull Request — master (#192)
by thomas
19:54
created

OutputClassifier::isPayToPublicKeyHash()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 25
ccs 17
cts 17
cp 1
rs 4.9091
cc 9
eloc 16
nc 14
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
     * @param ScriptInterface $script
19
     */
20 645
    public function __construct(ScriptInterface $script)
21
    {
22 645
        $this->decoded = $script->getScriptParser()->decode();
23 645
    }
24
25
    /**
26
     * @return bool
27
     */
28 201
    public function isPayToPublicKey()
29
    {
30 201
        if (count($this->decoded) < 1 || !$this->decoded[0]->isPush()) {
31 159
            return false;
32
        }
33
34 60
        $size = $this->decoded[0]->getDataSize();
35 60
        if ($size === 33 || $size === 65) {
36 42
            $op = $this->decoded[1];
37 42
            if (!$op->isPush() && $op->getOp() === Opcodes::OP_CHECKSIG) {
38 42
                return true;
39
            }
40
        }
41
42 18
        return false;
43
    }
44
45
    /**
46
     * @return bool
47
     */
48 159
    public function isPayToPublicKeyHash()
49
    {
50 159
        if (count($this->decoded) !== 5) {
51 60
            return false;
52
        }
53
54 111
        $dup = $this->decoded[0];
55 111
        $hash = $this->decoded[1];
56 111
        $buf = $this->decoded[2];
57 111
        $eq = $this->decoded[3];
58 111
        $checksig = $this->decoded[4];
59
60 111
        foreach ([$dup, $hash, $eq, $checksig] as $op) {
61
            /** @var Operation $op */
62 111
            if ($op->isPush()) {
63 24
                return false;
64
            }
65 111
        }
66
67 87
        return $dup->getOp() === Opcodes::OP_DUP
68 87
        && $hash->getOp() === Opcodes::OP_HASH160
69 87
        && $buf->isPush() && $buf->getDataSize() === 20
70 87
        && $eq->getOp() === Opcodes::OP_EQUALVERIFY
71 87
        && $checksig->getOp() === Opcodes::OP_CHECKSIG;
72
    }
73
74
    /**
75
     * @return bool
76
     */
77 291
    public function isPayToScriptHash()
78
    {
79 291
        if (count($this->decoded) !== 3) {
80 237
            return false;
81
        }
82
83 108
        $hash = $this->decoded[0];
84 108
        if ($hash->isPush() || !$hash->getOp() === Opcodes::OP_HASH160) {
85 18
            return false;
86
        }
87
88 90
        $buffer = $this->decoded[1];
89 90
        if (!$buffer->isPush() || $buffer->getDataSize() !== 20) {
90 6
            return false;
91
        }
92
93 84
        $eq = $this->decoded[2];
94 84
        return !$eq->isPush() && $eq->getOp() === Opcodes::OP_EQUAL;
95
    }
96
97
    /**
98
     * @return bool
99
     */
100 171
    public function isMultisig()
101
    {
102 171
        $count = count($this->decoded);
103 171
        if ($count <= 3) {
104 60
            return false;
105
        }
106
107 111
        $mOp = $this->decoded[0];
108 111
        $nOp = $this->decoded[$count - 2];
109 111
        $checksig = $this->decoded[$count - 1];
110 111
        if ($mOp->isPush() || $nOp->isPush() || $checksig->isPush()) {
111
            return false;
112
        }
113
114
        /** @var Operation[] $vKeys */
115 111
        $vKeys = array_slice($this->decoded, 1, -2);
116 111
        foreach ($vKeys as $key) {
117 111
            if (!$key->isPush() || !PublicKey::isCompressedOrUncompressed($key->getData())) {
0 ignored issues
show
Bug introduced by
It seems like $key->getData() can be null; however, isCompressedOrUncompressed() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
118 57
                return false;
119
            }
120 54
        }
121
122 54
        return $mOp->getOp() >= Opcodes::OP_0
123 54
            && $nOp->getOp() <= Opcodes::OP_16
124 54
            && $checksig->getOp() === Opcodes::OP_CHECKMULTISIG;
125
    }
126
127
    /**
128
     * @return string
129
     */
130 102
    public function classify()
131
    {
132 102
        if ($this->isPayToPublicKey()) {
133 6
            return self::PAYTOPUBKEY;
134 96
        } elseif ($this->isPayToPublicKeyHash()) {
135 30
            return self::PAYTOPUBKEYHASH;
136 78
        } elseif ($this->isPayToScriptHash()) {
137 18
            return self::PAYTOSCRIPTHASH;
138 66
        } elseif ($this->isMultisig()) {
139 48
            return self::MULTISIG;
140
        }
141
142 18
        return self::UNKNOWN;
143
    }
144
}
145