CheckerBase   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Test Coverage

Coverage 98.8%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 85
c 1
b 0
f 0
dl 0
loc 261
ccs 82
cts 83
cp 0.988
rs 9.2
wmc 40

10 Methods

Rating   Name   Duplication   Size   Complexity  
A checkSig() 0 21 3
A isDefinedHashtypeSignature() 0 8 2
A isLowDerSignature() 0 12 2
A __construct() 0 8 3
A checkPublicKeyEncoding() 0 7 3
A isDefinedHashtype() 0 5 2
B checkSequence() 0 26 8
B checkSignatureEncoding() 0 15 8
B checkLockTime() 0 21 7
A isValidSignatureEncoding() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like CheckerBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CheckerBase, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Script\Interpreter;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
12
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
13
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
14
use BitWasp\Bitcoin\Locktime;
15
use BitWasp\Bitcoin\Script\ScriptInterface;
16
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
17
use BitWasp\Bitcoin\Signature\TransactionSignature;
18
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
19
use BitWasp\Bitcoin\Transaction\TransactionInput;
20
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
21
use BitWasp\Bitcoin\Transaction\TransactionInterface;
22
use BitWasp\Buffertools\BufferInterface;
23
24
abstract class CheckerBase
25
{
26
    /**
27
     * @var EcAdapterInterface
28
     */
29
    protected $adapter;
30
31
    /**
32
     * @var TransactionInterface
33
     */
34
    protected $transaction;
35
36
    /**
37
     * @var int
38
     */
39
    protected $nInput;
40
41
    /**
42
     * @var int
43
     */
44
    protected $amount;
45
46
    /**
47
     * @var array
48
     */
49
    protected $sigCache = [];
50
51
    /**
52
     * @var TransactionSignatureSerializer
53
     */
54
    private $sigSerializer;
55
56
    /**
57
     * @var PublicKeySerializerInterface
58
     */
59
    private $pubKeySerializer;
60
61
    /**
62
     * @var int
63
     */
64
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
65
66
    /**
67
     * Checker constructor.
68
     * @param EcAdapterInterface $ecAdapter
69
     * @param TransactionInterface $transaction
70
     * @param int $nInput
71
     * @param int $amount
72
     * @param TransactionSignatureSerializer|null $sigSerializer
73
     * @param PublicKeySerializerInterface|null $pubKeySerializer
74
     */
75 4809
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, int $nInput, int $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
76
    {
77 4809
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
78 4809
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
79 4809
        $this->adapter = $ecAdapter;
80 4809
        $this->transaction = $transaction;
81 4809
        $this->nInput = $nInput;
82 4809
        $this->amount = $amount;
83 4809
    }
84
85
    /**
86
     * @param ScriptInterface $script
87
     * @param int $hashType
88
     * @param int $sigVersion
89
     * @return BufferInterface
90
     */
91
    abstract public function getSigHash(ScriptInterface $script, int $hashType, int $sigVersion): BufferInterface;
92
93
    /**
94
     * @param BufferInterface $signature
95
     * @return bool
96
     */
97 195
    public function isValidSignatureEncoding(BufferInterface $signature): bool
98
    {
99
        try {
100 195
            TransactionSignature::isDERSignature($signature);
101 105
            return true;
102 98
        } catch (SignatureNotCanonical $e) {
103
            /* In any case, we will return false outside this block */
104
        }
105
106 98
        return false;
107
    }
108
109
    /**
110
     * @param BufferInterface $signature
111
     * @return bool
112
     * @throws ScriptRuntimeException
113
     * @throws \Exception
114
     */
115 7
    public function isLowDerSignature(BufferInterface $signature): bool
116
    {
117 7
        if (!$this->isValidSignatureEncoding($signature)) {
118 1
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
119
        }
120
121 6
        $binary = $signature->getBinary();
122 6
        $nLenR = ord($binary[3]);
123 6
        $nLenS = ord($binary[5 + $nLenR]);
124 6
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
125
126 6
        return $this->adapter->validateSignatureElement($s, true);
127
    }
128
129
    /**
130
     * @param int $hashType
131
     * @return bool
132
     */
133 137
    public function isDefinedHashtype(int $hashType): bool
134
    {
135 137
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
136
137 137
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
138
    }
139
140
    /**
141
     * Determine whether the sighash byte appended to the signature encodes
142
     * a valid sighash type.
143
     *
144
     * @param BufferInterface $signature
145
     * @return bool
146
     */
147 44
    public function isDefinedHashtypeSignature(BufferInterface $signature): bool
148
    {
149 44
        if ($signature->getSize() === 0) {
150 1
            return false;
151
        }
152
153 43
        $binary = $signature->getBinary();
154 43
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
155
    }
156
157
    /**
158
     * @param BufferInterface $signature
159
     * @param int $flags
160
     * @return $this
161
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
162
     */
163 578
    public function checkSignatureEncoding(BufferInterface $signature, int $flags)
164
    {
165 578
        if ($signature->getSize() === 0) {
166 85
            return $this;
167
        }
168
169 501
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
170 97
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
171 412
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
172 5
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
173 407
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
174 9
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
175
        }
176
177 398
        return $this;
178
    }
179
180
    /**
181
     * @param BufferInterface $publicKey
182
     * @param int $flags
183
     * @return $this
184
     * @throws \Exception
185
     */
186 473
    public function checkPublicKeyEncoding(BufferInterface $publicKey, int $flags)
187
    {
188 473
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
189 21
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
190
        }
191
192 452
        return $this;
193
    }
194
195
    /**
196
     * @param ScriptInterface $script
197
     * @param BufferInterface $sigBuf
198
     * @param BufferInterface $keyBuf
199
     * @param int $sigVersion
200
     * @param int $flags
201
     * @return bool
202
     * @throws ScriptRuntimeException
203
     */
204 571
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, int $sigVersion, int $flags)
205
    {
206
        $this
207 571
            ->checkSignatureEncoding($sigBuf, $flags)
208 471
            ->checkPublicKeyEncoding($keyBuf, $flags);
209
210
        try {
211 451
            $cacheCheck = "{$script->getBinary()}{$sigVersion}{$keyBuf->getBinary()}{$sigBuf->getBinary()}";
212 451
            if (!isset($this->sigCache[$cacheCheck])) {
213 451
                $txSignature = $this->sigSerializer->parse($sigBuf);
214 339
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
215
216 323
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
217 323
                $result = $this->sigCache[$cacheCheck] = $publicKey->verify($hash, $txSignature->getSignature());
218
            } else {
219 8
                $result = $this->sigCache[$cacheCheck];
220
            }
221
222 323
            return $result;
223 136
        } catch (\Exception $e) {
224 136
            return false;
225
        }
226
    }
227
228
    /**
229
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
230
     * @return bool
231
     */
232 8
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime): bool
233
    {
234 8
        $input = $this->transaction->getInput($this->nInput);
235 8
        $nLockTime = $scriptLockTime->getInt();
236 8
        $txLockTime = $this->transaction->getLockTime();
237
238 8
        if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) ||
239 8
            ($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX))
240
        ) {
241 2
            return false;
242
        }
243
244 6
        if ($nLockTime > $txLockTime) {
245 2
            return false;
246
        }
247
248 4
        if ($input->isFinal()) {
249 1
            return false;
250
        }
251
252 3
        return true;
253
    }
254
255
    /**
256
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
257
     * @return bool
258
     */
259 15
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence): bool
260
    {
261 15
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
262 15
        if ($this->transaction->getVersion() < 2) {
263 10
            return false;
264
        }
265
266 5
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
267 1
            return false;
268
        }
269
270 4
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
271
272 4
        $txToSequenceMasked = $txSequence & $mask;
273 4
        $nSequenceMasked = $sequence->getInt() & $mask;
274 4
        if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
275 4
            ($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG))
276
        ) {
277 2
            return false;
278
        }
279
280 2
        if ($nSequenceMasked > $txToSequenceMasked) {
281
            return false;
282
        }
283
284 2
        return true;
285
    }
286
}
287