Completed
Push — master ( 7ac9fb...3b1821 )
by thomas
28:43
created

Checker::checkSequence()   C

Complexity

Conditions 8
Paths 5

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8.0189

Importance

Changes 0
Metric Value
cc 8
eloc 15
nc 5
nop 1
dl 0
loc 27
ccs 14
cts 15
cp 0.9333
crap 8.0189
rs 5.3846
c 0
b 0
f 0
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\Signature\TransactionSignature;
16
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
17
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
18
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
19
use BitWasp\Bitcoin\Transaction\TransactionInput;
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 2560
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
87
    {
88 2560
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
89 2560
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
90 2560
        $this->adapter = $ecAdapter;
91 2560
        $this->transaction = $transaction;
92 2560
        $this->nInput = $nInput;
93 2560
        $this->amount = $amount;
94 2560
    }
95
96
    /**
97
     * @param BufferInterface $signature
98
     * @return bool
99
     */
100 166
    public function isValidSignatureEncoding(BufferInterface $signature)
101
    {
102
        try {
103 166
            TransactionSignature::isDERSignature($signature);
104 118
            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 154
    public function isDefinedHashtype($hashType)
137
    {
138 154
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
139
140 154
        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 384
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
167
    {
168 384
        if ($signature->getSize() === 0) {
169 48
            return $this;
170
        }
171
172 344
        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 298
        } 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 294
        } 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 288
        return $this;
181
    }
182
183
    /**
184
     * @param BufferInterface $publicKey
185
     * @param int $flags
186
     * @return $this
187
     * @throws \Exception
188
     */
189 324
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
190
    {
191 324
        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 312
        return $this;
196
    }
197
198
    /**
199
     * @param ScriptInterface $script
200
     * @param int $sigHashType
201
     * @param int $sigVersion
202
     * @return BufferInterface
203
     */
204 250
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
205
    {
206 250
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
207 250
        if (!isset($this->sigHashCache[$cacheCheck])) {
208 250
            if (SigHash::V1 === $sigVersion) {
209 70
                $hasher = new V1Hasher($this->transaction, $this->amount);
210
            } else {
211 182
                if ($this->hasherV0) {
212
                    $hasher = $this->hasherV0;
213
                } else {
214 182
                    $hasher = $this->hasherV0 = new Hasher($this->transaction);
215
                }
216
            }
217
218 250
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
219 250
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
220
        } else {
221 56
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
222
        }
223
224 250
        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 370
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
237
    {
238
        $this
239 370
            ->checkSignatureEncoding($sigBuf, $flags)
240 320
            ->checkPublicKeyEncoding($keyBuf, $flags);
241
242
        try {
243 310
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
244 310
            if (!isset($this->sigCache[$cacheCheck])) {
245 310
                $txSignature = $this->sigSerializer->parse($sigBuf);
246 256
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
247
248 246
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
249 246
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
250
            } else {
251 4
                $result = $this->sigCache[$cacheCheck];
252
            }
253
254 246
            return $result;
255 72
        } catch (\Exception $e) {
256 72
            return false;
257
        }
258
    }
259
260
    /**
261
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
262
     * @return bool
263
     */
264 16
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime)
265
    {
266 16
        $input = $this->transaction->getInput($this->nInput);
267 16
        $nLockTime = $scriptLockTime->getInt();
268 16
        $txLockTime = $this->transaction->getLockTime();
269
270 16
        if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) ||
271 16
            ($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX))
272
        ) {
273 4
            return false;
274
        }
275
276 12
        if ($nLockTime > $txLockTime) {
277 4
            return false;
278
        }
279
280 8
        if ($input->isFinal()) {
281 2
            return false;
282
        }
283
284 6
        return true;
285
    }
286
287
    /**
288
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
289
     * @return bool
290
     */
291 18
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
292
    {
293 18
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
294 18
        if ($this->transaction->getVersion() < 2) {
295 8
            return false;
296
        }
297
298 10
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
299 2
            return false;
300
        }
301
302 8
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
303
304 8
        $txToSequenceMasked = $txSequence & $mask;
305 8
        $nSequenceMasked = $sequence->getInt() & $mask;
306 8
        if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
307 8
            ($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG))
308
        ) {
309 4
            return false;
310
        }
311
312 4
        if ($nSequenceMasked > $txToSequenceMasked) {
313
            return false;
314
        }
315
316 4
        return true;
317
    }
318
}
319