Completed
Pull Request — master (#524)
by thomas
71:45
created

Checker   C

Complexity

Total Complexity 39

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 79.77%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 290
ccs 71
cts 89
cp 0.7977
rs 6.5773
c 3
b 0
f 0
wmc 39
lcom 1
cbo 17

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 3
A isValidSignatureEncoding() 0 11 2
A isLowDerSignature() 0 13 2
A isDefinedHashtypeSignature() 0 9 2
B checkSignatureEncoding() 0 16 8
A checkPublicKeyEncoding() 0 8 3
B getSigHash() 0 22 4
A checkSig() 0 23 3
B verifyLockTime() 0 11 5
A checkLockTime() 0 8 2
A checkSequence() 0 18 3
A isDefinedHashtype() 0 6 2
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Interpreter;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
10
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
11
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
12
use BitWasp\Bitcoin\Locktime;
13
use BitWasp\Bitcoin\Script\ScriptInterface;
14
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
15
use BitWasp\Bitcoin\Serializer\Transaction\TransactionSerializer;
16
use BitWasp\Bitcoin\Signature\TransactionSignature;
17
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
18
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
19
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
20
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
21
use BitWasp\Bitcoin\Transaction\TransactionInterface;
22
use BitWasp\Buffertools\Buffer;
23
use BitWasp\Buffertools\BufferInterface;
24
25
class Checker
26
{
27
    /**
28
     * @var EcAdapterInterface
29
     */
30
    protected $adapter;
31
32
    /**
33
     * @var TransactionInterface
34
     */
35
    protected $transaction;
36
37
    /**
38
     * @var int
39
     */
40
    protected $nInput;
41
42
    /**
43
     * @var int|string
44
     */
45
    protected $amount;
46
47
    /**
48
     * @var Hasher
49
     */
50
    protected $hasherV0;
51
52
    /**
53
     * @var array
54
     */
55
    protected $sigHashCache = [];
56
57
    /**
58
     * @var array
59
     */
60
    protected $sigCache = [];
61
62
    /**
63
     * @var TransactionSignatureSerializer
64
     */
65
    private $sigSerializer;
66
67
    /**
68
     * @var PublicKeySerializerInterface
69
     */
70
    private $pubKeySerializer;
71
72
    /**
73
     * @var int
74
     */
75
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
76
77
    /**
78
     * Checker constructor.
79
     * @param EcAdapterInterface $ecAdapter
80
     * @param TransactionInterface $transaction
81
     * @param int $nInput
82
     * @param int $amount
83
     * @param TransactionSignatureSerializer|null $sigSerializer
84
     * @param PublicKeySerializerInterface|null $pubKeySerializer
85
     */
86 2466
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
87
    {
88 2466
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
89 2466
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
90 2466
        $this->adapter = $ecAdapter;
91 2466
        $this->transaction = $transaction;
92 2466
        $this->nInput = $nInput;
93 2466
        $this->amount = $amount;
94 2466
    }
95
96
    /**
97
     * @param BufferInterface $signature
98
     * @return bool
99
     */
100 94
    public function isValidSignatureEncoding(BufferInterface $signature)
101
    {
102
        try {
103 94
            TransactionSignature::isDERSignature($signature);
104 46
            return true;
105 52
        } catch (SignatureNotCanonical $e) {
106
            /* In any case, we will return false outside this block */
107
        }
108
109 52
        return false;
110
    }
111
112
    /**
113
     * @param BufferInterface $signature
114
     * @return bool
115
     * @throws ScriptRuntimeException
116
     * @throws \Exception
117
     */
118 8
    public function isLowDerSignature(BufferInterface $signature)
119
    {
120 8
        if (!$this->isValidSignatureEncoding($signature)) {
121 2
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
122
        }
123
124 6
        $binary = $signature->getBinary();
125 6
        $nLenR = ord($binary[3]);
126 6
        $nLenS = ord($binary[5 + $nLenR]);
127 6
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
128
129 6
        return $this->adapter->validateSignatureElement($s, true);
130
    }
131
132
    /**
133
     * @param int $hashType
134
     * @return bool
135
     */
136 80
    public function isDefinedHashtype($hashType)
137
    {
138 80
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
139
140 80
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
141
    }
142
143
    /**
144
     * Determine whether the sighash byte appended to the signature encodes
145
     * a valid sighash type.
146
     *
147
     * @param BufferInterface $signature
148
     * @return bool
149
     */
150 28
    public function isDefinedHashtypeSignature(BufferInterface $signature)
151
    {
152 28
        if ($signature->getSize() === 0) {
153 2
            return false;
154
        }
155
156 26
        $binary = $signature->getBinary();
157 26
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
158
    }
159
160
    /**
161
     * @param BufferInterface $signature
162
     * @param int $flags
163
     * @return $this
164
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
165
     */
166 312
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
167
    {
168 312
        if ($signature->getSize() === 0) {
169 54
            return $this;
170
        }
171
172 272
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
173 50
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
174 226
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
175 4
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
176 222
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
177 6
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
178
        }
179
180 216
        return $this;
181
    }
182
183
    /**
184
     * @param BufferInterface $publicKey
185
     * @param int $flags
186
     * @return $this
187
     * @throws \Exception
188
     */
189 252
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
190
    {
191 252
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
192 12
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
193
        }
194
195 240
        return $this;
196
    }
197
198
    /**
199
     * @param ScriptInterface $script
200
     * @param int $sigHashType
201
     * @param int $sigVersion
202
     * @return BufferInterface
203
     */
204 174
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
205
    {
206 174
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
207 174
        if (!isset($this->sigHashCache[$cacheCheck])) {
208 174
            if (SigHash::V1 === $sigVersion) {
209 46
                $hasher = new V1Hasher($this->transaction, $this->amount);
210
            } else {
211 130
                if ($this->hasherV0) {
212
                    $hasher = $this->hasherV0;
213
                } else {
214 130
                    $hasher = $this->hasherV0 = new Hasher($this->transaction, new TransactionSerializer());
0 ignored issues
show
Unused Code introduced by
The call to Hasher::__construct() has too many arguments starting with new \BitWasp\Bitcoin\Ser...TransactionSerializer().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
215
                }
216
            }
217
218 174
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
219 174
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
220
        } else {
221 12
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
222
        }
223
224 174
        return $hash;
225
    }
226
227
    /**
228
     * @param ScriptInterface $script
229
     * @param BufferInterface $sigBuf
230
     * @param BufferInterface $keyBuf
231
     * @param int $sigVersion
232
     * @param int $flags
233
     * @return bool
234
     * @throws ScriptRuntimeException
235
     */
236 298
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
237
    {
238
        $this
239 298
            ->checkSignatureEncoding($sigBuf, $flags)
240 248
            ->checkPublicKeyEncoding($keyBuf, $flags);
241
242
        try {
243 238
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
244 238
            if (!isset($this->sigCache[$cacheCheck])) {
245 238
                $txSignature = $this->sigSerializer->parse($sigBuf);
246 184
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
247
248 174
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
249 174
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
250
            } else {
251 14
                $result = $this->sigCache[$cacheCheck];
252
            }
253
254 174
            return $result;
255 78
        } catch (\Exception $e) {
256 78
            return false;
257
        }
258
    }
259
260
    /**
261
     * @param int $txLockTime
262
     * @param int $nThreshold
263
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
264
     * @return bool
265
     */
266
    private function verifyLockTime($txLockTime, $nThreshold, \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
267
    {
268
        $nTime = $lockTime->getInt();
269
        if (($txLockTime < $nThreshold && $nTime < $nThreshold) ||
270
            ($txLockTime >= $nThreshold && $nTime >= $nThreshold)
271
        ) {
272
            return false;
273
        }
274
275
        return $nTime >= $txLockTime;
276
    }
277
278
    /**
279
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
280
     * @return bool
281
     */
282
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
283
    {
284
        if ($this->transaction->getInput($this->nInput)->isFinal()) {
285
            return false;
286
        }
287
288
        return $this->verifyLockTime($this->transaction->getLockTime(), Locktime::BLOCK_MAX, $lockTime);
289
    }
290
291
292
    /**
293
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
294
     * @return bool
295
     */
296 4
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
297
    {
298 4
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
299 4
        if ($this->transaction->getVersion() < 2) {
300 4
            return false;
301
        }
302
303
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
304
            return true;
305
        }
306
307
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
308
        return $this->verifyLockTime(
309
            $txSequence & $mask,
310
            TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG,
311
            Number::int($sequence->getInt() & $mask)
312
        );
313
    }
314
}
315