Completed
Pull Request — master (#451)
by thomas
30:19 queued 02:49
created

Checker   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 79.77%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 285
ccs 71
cts 89
cp 0.7977
rs 7.0627
c 2
b 0
f 0
wmc 39
lcom 1
cbo 16

12 Methods

Rating   Name   Duplication   Size   Complexity  
A checkPublicKeyEncoding() 0 8 3
A __construct() 0 9 3
A isValidSignatureEncoding() 0 11 2
A isLowDerSignature() 0 13 2
A isDefinedHashtype() 0 6 2
A isDefinedHashtypeSignature() 0 9 2
B checkSignatureEncoding() 0 16 8
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
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
    private $adapter;
31
32
    /**
33
     * @var TransactionInterface
34
     */
35
    private $transaction;
36
37
    /**
38
     * @var int
39
     */
40
    private $nInput;
41
42
    /**
43
     * @var int|string
44
     */
45
    private $amount;
46
47
    /**
48
     * @var Hasher
49
     */
50
    private $hasherV0;
51
52
    /**
53
     * @var array
54
     */
55
    private $sigHashCache = [];
56
57
    /**
58
     * @var array
59
     */
60
    private $sigCache = [];
61
62
    /**
63
     * @var TransactionSignatureSerializer
64
     */
65
    private $sigSerializer;
66
67
    /**
68
     * @var PublicKeySerializerInterface
69
     */
70
    private $pubKeySerializer;
71
72
    /**
73
     * Checker constructor.
74
     * @param EcAdapterInterface $ecAdapter
75
     * @param TransactionInterface $transaction
76
     * @param int $nInput
77
     * @param int $amount
78
     * @param TransactionSignatureSerializer|null $sigSerializer
79
     * @param PublicKeySerializerInterface|null $pubKeySerializer
80
     */
81 1328
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
82
    {
83 1328
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, $ecAdapter));
0 ignored issues
show
Documentation introduced by
$ecAdapter is of type object<BitWasp\Bitcoin\C...ter\EcAdapterInterface>, but the function expects a boolean|object<BitWasp\B...\Crypto\EcAdapter\true>.

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...
84 1328
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, $ecAdapter);
0 ignored issues
show
Documentation introduced by
$ecAdapter is of type object<BitWasp\Bitcoin\C...ter\EcAdapterInterface>, but the function expects a boolean|object<BitWasp\B...\Crypto\EcAdapter\true>.

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...
85 1328
        $this->adapter = $ecAdapter;
86 1328
        $this->transaction = $transaction;
87 1328
        $this->nInput = $nInput;
88 1328
        $this->amount = $amount;
89 1328
    }
90
91
    /**
92
     * @param BufferInterface $signature
93
     * @return bool
94
     */
95 127
    public function isValidSignatureEncoding(BufferInterface $signature)
96
    {
97
        try {
98 127
            TransactionSignature::isDERSignature($signature);
99 81
            return true;
100 50
        } catch (SignatureNotCanonical $e) {
101
            /* In any case, we will return false outside this block */
102
        }
103
104 50
        return false;
105
    }
106
107
    /**
108
     * @param BufferInterface $signature
109
     * @return bool
110
     * @throws ScriptRuntimeException
111
     * @throws \Exception
112
     */
113 7
    public function isLowDerSignature(BufferInterface $signature)
114
    {
115 7
        if (!$this->isValidSignatureEncoding($signature)) {
116 2
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
117
        }
118
119 5
        $binary = $signature->getBinary();
120 5
        $nLenR = ord($binary[3]);
121 5
        $nLenS = ord($binary[5 + $nLenR]);
122 5
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
123
124 5
        return $this->adapter->validateSignatureElement($s, true);
125
    }
126
127
    /**
128
     * @param int $hashType
129
     * @return bool
130
     */
131 68
    public function isDefinedHashtype($hashType)
132
    {
133 68
        $nHashType = $hashType & (~(SigHash::ANYONECANPAY));
134
135 68
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
136
    }
137
138
    /**
139
     * Determine whether the sighash byte appended to the signature encodes
140
     * a valid sighash type.
141
     *
142
     * @param BufferInterface $signature
143
     * @return bool
144
     */
145 18
    public function isDefinedHashtypeSignature(BufferInterface $signature)
146
    {
147 18
        if ($signature->getSize() === 0) {
148 2
            return false;
149
        }
150
151 16
        $binary = $signature->getBinary();
152 16
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
153
    }
154
155
    /**
156
     * @param BufferInterface $signature
157
     * @param int $flags
158
     * @return $this
159
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
160
     */
161 227
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
162
    {
163 227
        if ($signature->getSize() === 0) {
164 44
            return $this;
165
        }
166
167 195
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
168 48
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
169 151
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
170 3
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
171 148
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
172 4
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
173
        }
174
175 144
        return $this;
176
    }
177
178
    /**
179
     * @param BufferInterface $publicKey
180
     * @param int $flags
181
     * @return $this
182
     * @throws \Exception
183
     */
184 172
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
185
    {
186 172
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
187 7
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
188
        }
189
190 165
        return $this;
191
    }
192
193
    /**
194
     * @param ScriptInterface $script
195
     * @param int $sigHashType
196
     * @param int $sigVersion
197
     * @return BufferInterface
198
     */
199 118
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
200
    {
201 118
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
202 118
        if (!isset($this->sigHashCache[$cacheCheck])) {
203 118
            if ($sigVersion === 1) {
204 34
                $hasher = new V1Hasher($this->transaction, $this->amount);
205
            } else {
206 86
                if ($this->hasherV0) {
207
                    $hasher = $this->hasherV0;
208
                } else {
209 86
                    $hasher = $this->hasherV0 = new Hasher($this->transaction, new TransactionSerializer());
210
                }
211
            }
212
213 118
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
214 118
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
215
        } else {
216 6
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
217
        }
218
219 118
        return $hash;
220
    }
221
222
    /**
223
     * @param ScriptInterface $script
224
     * @param BufferInterface $sigBuf
225
     * @param BufferInterface $keyBuf
226
     * @param int $sigVersion
227
     * @param int $flags
228
     * @return bool
229
     * @throws ScriptRuntimeException
230
     */
231 213
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
232
    {
233
        $this
234 213
            ->checkSignatureEncoding($sigBuf, $flags)
235 168
            ->checkPublicKeyEncoding($keyBuf, $flags);
236
237
        try {
238 163
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
239 163
            if (!isset($this->sigCache[$cacheCheck])) {
240 163
                $txSignature = $this->sigSerializer->parse($sigBuf);
0 ignored issues
show
Documentation introduced by
$sigBuf is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a string.

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...
241 126
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
242
243 118
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
244 118
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
245
            } else {
246 12
                $result = $this->sigCache[$cacheCheck];
247
            }
248
249 118
            return $result;
250 57
        } catch (\Exception $e) {
251 57
            return false;
252
        }
253
    }
254
255
    /**
256
     * @param int $txLockTime
257
     * @param int $nThreshold
258
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
259
     * @return bool
260
     */
261
    private function verifyLockTime($txLockTime, $nThreshold, \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
262
    {
263
        $nTime = $lockTime->getInt();
264
        if (($txLockTime < $nThreshold && $nTime < $nThreshold) ||
265
            ($txLockTime >= $nThreshold && $nTime >= $nThreshold)
266
        ) {
267
            return false;
268
        }
269
270
        return $nTime >= $txLockTime;
271
    }
272
273
    /**
274
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
275
     * @return bool
276
     */
277
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
278
    {
279
        if ($this->transaction->getInput($this->nInput)->isFinal()) {
280
            return false;
281
        }
282
283
        return $this->verifyLockTime($this->transaction->getLockTime(), Locktime::BLOCK_MAX, $lockTime);
284
    }
285
286
287
    /**
288
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
289
     * @return bool
290
     */
291 2
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
292
    {
293 2
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
294 2
        if ($this->transaction->getVersion() < 2) {
295 2
            return false;
296
        }
297
298
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
299
            return true;
300
        }
301
302
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
303
        return $this->verifyLockTime(
304
            $txSequence & $mask,
305
            TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG,
306
            Number::int($sequence->getInt() & $mask)
307
        );
308
    }
309
}
310