Completed
Pull Request — master (#524)
by thomas
40:24 queued 38:13
created

Checker::isDefinedHashtype()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 2
rs 9.4285
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 2502
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
87
    {
88 2502
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
89 2502
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
90 2502
        $this->adapter = $ecAdapter;
91 2502
        $this->transaction = $transaction;
92 2502
        $this->nInput = $nInput;
93 2502
        $this->amount = $amount;
94 2502
    }
95
96
    /**
97
     * @param BufferInterface $signature
98
     * @return bool
99
     */
100 132
    public function isValidSignatureEncoding(BufferInterface $signature)
101
    {
102
        try {
103 132
            TransactionSignature::isDERSignature($signature);
104 84
            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 116
    public function isDefinedHashtype($hashType)
137
    {
138 116
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
139
140 116
        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 350
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
167
    {
168 350
        if ($signature->getSize() === 0) {
169 48
            return $this;
170
        }
171
172 310
        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 264
        } 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 260
        } 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 254
        return $this;
181
    }
182
183
    /**
184
     * @param BufferInterface $publicKey
185
     * @param int $flags
186
     * @return $this
187
     * @throws \Exception
188
     */
189 290
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
190
    {
191 290
        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 278
        return $this;
196
    }
197
198
    /**
199
     * @param ScriptInterface $script
200
     * @param int $sigHashType
201
     * @param int $sigVersion
202
     * @return BufferInterface
203
     */
204 212
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
205
    {
206 212
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
207 212
        if (!isset($this->sigHashCache[$cacheCheck])) {
208 212
            if (SigHash::V1 === $sigVersion) {
209 46
                $hasher = new V1Hasher($this->transaction, $this->amount);
210
            } else {
211 168
                if ($this->hasherV0) {
212
                    $hasher = $this->hasherV0;
213
                } else {
214 168
                    $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 212
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
219 212
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
220
        } else {
221 32
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
222
        }
223
224 212
        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 336
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
237
    {
238
        $this
239 336
            ->checkSignatureEncoding($sigBuf, $flags)
240 286
            ->checkPublicKeyEncoding($keyBuf, $flags);
241
242
        try {
243 276
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
244 276
            if (!isset($this->sigCache[$cacheCheck])) {
245 276
                $txSignature = $this->sigSerializer->parse($sigBuf);
246 222
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
247
248 212
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
249 212
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
250
            } else {
251 4
                $result = $this->sigCache[$cacheCheck];
252
            }
253
254 212
            return $result;
255 72
        } catch (\Exception $e) {
256 72
            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