Completed
Push — 0.0.35 ( b2fc74...d8d049 )
by thomas
28:12 queued 09:10
created

CheckerBase::checkLockTime()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 4
nop 1
dl 0
loc 22
ccs 0
cts 12
cp 0
crap 56
rs 6.9811
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\SigHash;
17
use BitWasp\Bitcoin\Transaction\TransactionInput;
18
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
19
use BitWasp\Bitcoin\Transaction\TransactionInterface;
20
use BitWasp\Buffertools\BufferInterface;
21
22
abstract class CheckerBase
23
{
24
    /**
25
     * @var EcAdapterInterface
26
     */
27
    protected $adapter;
28
29
    /**
30
     * @var TransactionInterface
31
     */
32
    protected $transaction;
33
34
    /**
35
     * @var int
36
     */
37
    protected $nInput;
38
39
    /**
40
     * @var int
41
     */
42
    protected $amount;
43
44
    /**
45
     * @var array
46
     */
47
    protected $sigCache = [];
48
49
    /**
50
     * @var TransactionSignatureSerializer
51
     */
52
    private $sigSerializer;
53
54
    /**
55
     * @var PublicKeySerializerInterface
56
     */
57
    private $pubKeySerializer;
58
59
    /**
60
     * @var int
61
     */
62
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
63
64
    /**
65
     * Checker constructor.
66
     * @param EcAdapterInterface $ecAdapter
67
     * @param TransactionInterface $transaction
68
     * @param int $nInput
69
     * @param int $amount
70
     * @param TransactionSignatureSerializer|null $sigSerializer
71
     * @param PublicKeySerializerInterface|null $pubKeySerializer
72
     */
73 4755
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
74
    {
75 4755
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
76 4755
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
77 4755
        $this->adapter = $ecAdapter;
78 4755
        $this->transaction = $transaction;
79 4755
        $this->nInput = $nInput;
80 4755
        $this->amount = $amount;
81 4755
    }
82
83
    /**
84
     * @param ScriptInterface $script
85
     * @param int $hashType
86
     * @param int $sigVersion
87
     * @return BufferInterface
88
     */
89
    abstract public function getSigHash(ScriptInterface $script, $hashType, $sigVersion);
90
91
    /**
92
     * @param BufferInterface $signature
93
     * @return bool
94
     */
95 164
    public function isValidSignatureEncoding(BufferInterface $signature)
96
    {
97
        try {
98 164
            TransactionSignature::isDERSignature($signature);
99 74
            return true;
100 98
        } catch (SignatureNotCanonical $e) {
101
            /* In any case, we will return false outside this block */
102
        }
103
104 98
        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 1
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
117
        }
118
119 6
        $binary = $signature->getBinary();
120 6
        $nLenR = ord($binary[3]);
121 6
        $nLenS = ord($binary[5 + $nLenR]);
122 6
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
123
124 6
        return $this->adapter->validateSignatureElement($s, true);
125
    }
126
127
    /**
128
     * @param int $hashType
129
     * @return bool
130
     */
131 94
    public function isDefinedHashtype($hashType)
132
    {
133 94
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
134
135 94
        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 44
    public function isDefinedHashtypeSignature(BufferInterface $signature)
146
    {
147 44
        if ($signature->getSize() === 0) {
148 1
            return false;
149
        }
150
151 43
        $binary = $signature->getBinary();
152 43
        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 547
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
162
    {
163 547
        if ($signature->getSize() === 0) {
164 85
            return $this;
165
        }
166
167 470
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
168 97
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
169 381
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
170 5
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
171 376
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
172 9
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
173
        }
174
175 367
        return $this;
176
    }
177
178
    /**
179
     * @param BufferInterface $publicKey
180
     * @param int $flags
181
     * @return $this
182
     * @throws \Exception
183
     */
184 442
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
185
    {
186 442
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
187 21
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
188
        }
189
190 421
        return $this;
191
    }
192
193
    /**
194
     * @param ScriptInterface $script
195
     * @param BufferInterface $sigBuf
196
     * @param BufferInterface $keyBuf
197
     * @param int $sigVersion
198
     * @param int $flags
199
     * @return bool
200
     * @throws ScriptRuntimeException
201
     */
202 540
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
203
    {
204
        $this
205 540
            ->checkSignatureEncoding($sigBuf, $flags)
206 440
            ->checkPublicKeyEncoding($keyBuf, $flags);
207
208
        try {
209 420
            $cacheCheck = "{$script->getBinary()}{$sigVersion}{$keyBuf->getBinary()}{$sigBuf->getBinary()}";
210 420
            if (!isset($this->sigCache[$cacheCheck])) {
211 420
                $txSignature = $this->sigSerializer->parse($sigBuf);
212 312
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
213
214 292
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
215 292
                $result = $this->sigCache[$cacheCheck] = $publicKey->verify($hash, $txSignature->getSignature());
216
            } else {
217 18
                $result = $this->sigCache[$cacheCheck];
218
            }
219
220 292
            return $result;
221 136
        } catch (\Exception $e) {
222 136
            return false;
223
        }
224
    }
225
226
    /**
227
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
228
     * @return bool
229
     */
230
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime)
231
    {
232
        $input = $this->transaction->getInput($this->nInput);
233
        $nLockTime = $scriptLockTime->getInt();
234
        $txLockTime = $this->transaction->getLockTime();
235
236
        if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) ||
237
            ($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX))
238
        ) {
239
            return false;
240
        }
241
242
        if ($nLockTime > $txLockTime) {
243
            return false;
244
        }
245
246
        if ($input->isFinal()) {
247
            return false;
248
        }
249
250
        return true;
251
    }
252
253
    /**
254
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
255
     * @return bool
256
     */
257 8
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
258
    {
259 8
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
260 8
        if ($this->transaction->getVersion() < 2) {
261 8
            return false;
262
        }
263
264
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
265
            return false;
266
        }
267
268
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
269
270
        $txToSequenceMasked = $txSequence & $mask;
271
        $nSequenceMasked = $sequence->getInt() & $mask;
272
        if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
273
            ($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG))
274
        ) {
275
            return false;
276
        }
277
278
        if ($nSequenceMasked > $txToSequenceMasked) {
279
            return false;
280
        }
281
282
        return true;
283
    }
284
}
285