Completed
Pull Request — master (#758)
by thomas
22:04
created

CheckerBase::__construct()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 8
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 18
ccs 9
cts 9
cp 1
crap 5
rs 9.6111

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Script\Interpreter;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\XOnlyPublicKeySerializerInterface;
12
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
13
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\SchnorrSignatureSerializerInterface;
14
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
15
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
16
use BitWasp\Bitcoin\Locktime;
17
use BitWasp\Bitcoin\Script\ScriptInterface;
18
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
19
use BitWasp\Bitcoin\Signature\TransactionSignature;
20
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
21
use BitWasp\Bitcoin\Transaction\SignatureHash\TaprootHasher;
22
use BitWasp\Bitcoin\Transaction\TransactionInput;
23
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
24
use BitWasp\Bitcoin\Transaction\TransactionInterface;
25
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
26
use BitWasp\Buffertools\Buffer;
27
use BitWasp\Buffertools\BufferInterface;
28
29
abstract class CheckerBase
30
{
31
    /**
32
     * @var EcAdapterInterface
33
     */
34
    protected $adapter;
35
36
    /**
37
     * @var TransactionInterface
38
     */
39
    protected $transaction;
40
41
    /**
42
     * @var int
43
     */
44
    protected $nInput;
45
46
    /**
47
     * @var int
48
     */
49
    protected $amount;
50
51
    /**
52
     * @var array
53
     */
54
    protected $sigCache = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $schnorrSigHashCache = [];
60
61
    /**
62
     * @var TransactionOutputInterface[]
63
     */
64
    protected $spentOutputs = [];
65
66
    /**
67
     * @var TransactionSignatureSerializer
68
     */
69
    private $sigSerializer;
70
71
    /**
72
     * @var PublicKeySerializerInterface
73
     */
74
    private $pubKeySerializer;
75
76
    /**
77
     * @var XOnlyPublicKeySerializerInterface
78
     */
79
    private $xonlyKeySerializer;
80
81
    /**
82
     * @var SchnorrSignatureSerializerInterface
83
     */
84
    private $schnorrSigSerializer;
85
86
    /**
87
     * @var int
88
     */
89
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
90
91
    /**
92
     * CheckerBase constructor.
93
     * @param EcAdapterInterface $ecAdapter
94
     * @param TransactionInterface $transaction
95
     * @param int $nInput
96
     * @param int $amount
97
     * @param TransactionSignatureSerializer|null $sigSerializer
98
     * @param PublicKeySerializerInterface|null $pubKeySerializer
99
     * @param XOnlyPublicKeySerializerInterface|null $xonlyKeySerializer
100
     * @param SchnorrSignatureSerializerInterface|null $schnorrSigSerializer
101
     */
102 4808
    public function __construct(
103
        EcAdapterInterface $ecAdapter,
104
        TransactionInterface $transaction,
105
        int $nInput,
106
        int $amount,
107
        TransactionSignatureSerializer $sigSerializer = null,
108
        PublicKeySerializerInterface $pubKeySerializer = null,
109
        XOnlyPublicKeySerializerInterface $xonlyKeySerializer = null,
110
        SchnorrSignatureSerializerInterface $schnorrSigSerializer = null
111
    ) {
112 4808
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
113 4808
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
114 4808
        $this->xonlyKeySerializer = $xonlyKeySerializer ?: EcSerializer::getSerializer(XOnlyPublicKeySerializerInterface::class, true, $ecAdapter);
115 4808
        $this->schnorrSigSerializer = $schnorrSigSerializer ?: EcSerializer::getSerializer(SchnorrSignatureSerializerInterface::class, true, $ecAdapter);
116 4808
        $this->adapter = $ecAdapter;
117 4808
        $this->transaction = $transaction;
118 4808
        $this->nInput = $nInput;
119 4808
        $this->amount = $amount;
120 4808
    }
121
122
    public function setSpentOutputs(array $txOuts)
123
    {
124
        if (count($txOuts) !== count($this->transaction->getInputs())) {
125
            throw new \RuntimeException("number of spent txouts should equal number of inputs");
126
        }
127
        $this->spentOutputs = $txOuts;
128
    }
129
130
    /**
131
     * @param ScriptInterface $script
132
     * @param int $hashType
133
     * @param int $sigVersion
134
     * @return BufferInterface
135
     */
136
    abstract public function getSigHash(ScriptInterface $script, int $hashType, int $sigVersion): BufferInterface;
137
138
    /**
139
     * @param BufferInterface $signature
140
     * @return bool
141
     */
142 195
    public function isValidSignatureEncoding(BufferInterface $signature): bool
143
    {
144
        try {
145 195
            TransactionSignature::isDERSignature($signature);
146 105
            return true;
147 98
        } catch (SignatureNotCanonical $e) {
148
            /* In any case, we will return false outside this block */
149
        }
150
151 98
        return false;
152
    }
153
154
    /**
155
     * @param BufferInterface $signature
156
     * @return bool
157
     * @throws ScriptRuntimeException
158
     * @throws \Exception
159
     */
160 7
    public function isLowDerSignature(BufferInterface $signature): bool
161
    {
162 7
        if (!$this->isValidSignatureEncoding($signature)) {
163 1
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
164
        }
165
166 6
        $binary = $signature->getBinary();
167 6
        $nLenR = ord($binary[3]);
168 6
        $nLenS = ord($binary[5 + $nLenR]);
169 6
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
170
171 6
        return $this->adapter->validateSignatureElement($s, true);
172
    }
173
174
    /**
175
     * @param int $hashType
176
     * @return bool
177
     */
178 137
    public function isDefinedHashtype(int $hashType): bool
179
    {
180 137
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
181
182 137
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
183
    }
184
185
    /**
186
     * Determine whether the sighash byte appended to the signature encodes
187
     * a valid sighash type.
188
     *
189
     * @param BufferInterface $signature
190
     * @return bool
191
     */
192 44
    public function isDefinedHashtypeSignature(BufferInterface $signature): bool
193
    {
194 44
        if ($signature->getSize() === 0) {
195 1
            return false;
196
        }
197
198 43
        $binary = $signature->getBinary();
199 43
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
200
    }
201
202
    /**
203
     * @param BufferInterface $signature
204
     * @param int $flags
205
     * @return $this
206
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
207
     */
208 578
    public function checkSignatureEncoding(BufferInterface $signature, int $flags)
209
    {
210 578
        if ($signature->getSize() === 0) {
211 85
            return $this;
212
        }
213
214 501
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
215 97
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
216 412
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
217 5
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
218 407
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
219 9
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
220
        }
221
222 398
        return $this;
223
    }
224
225
    /**
226
     * @param BufferInterface $publicKey
227
     * @param int $flags
228
     * @return $this
229
     * @throws \Exception
230
     */
231 473
    public function checkPublicKeyEncoding(BufferInterface $publicKey, int $flags)
232
    {
233 473
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
234 21
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
235
        }
236
237 452
        return $this;
238
    }
239
240
    /**
241
     * @param ScriptInterface $script
242
     * @param BufferInterface $sigBuf
243
     * @param BufferInterface $keyBuf
244
     * @param int $sigVersion
245
     * @param int $flags
246
     * @return bool
247
     * @throws ScriptRuntimeException
248
     */
249 571
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, int $sigVersion, int $flags)
250
    {
251
        $this
252 571
            ->checkSignatureEncoding($sigBuf, $flags)
253 471
            ->checkPublicKeyEncoding($keyBuf, $flags);
254
255
        try {
256 451
            $cacheCheck = "{$script->getBinary()}{$sigVersion}{$keyBuf->getBinary()}{$sigBuf->getBinary()}";
257 451
            if (!isset($this->sigCache[$cacheCheck])) {
258 451
                $txSignature = $this->sigSerializer->parse($sigBuf);
259 339
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
260
261 323
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
262 323
                $result = $this->sigCache[$cacheCheck] = $publicKey->verify($hash, $txSignature->getSignature());
263
            } else {
264 8
                $result = $this->sigCache[$cacheCheck];
265
            }
266
267 323
            return $result;
268 136
        } catch (\Exception $e) {
269 136
            return false;
270
        }
271
    }
272
273
    public function getTaprootSigHash(int $sigHashType, int $sigVersion): BufferInterface
274
    {
275
        $cacheCheck = $sigVersion . $sigHashType;
276
        if (!isset($this->schnorrSigHashCache[$cacheCheck])) {
277
            $hasher = new TaprootHasher($this->transaction, $this->amount, $this->spentOutputs);
278
279
            $hash = $hasher->calculate($this->spentOutputs[$this->nInput]->getScript(), $this->nInput, $sigHashType);
280
            $this->schnorrSigHashCache[$cacheCheck] = $hash->getBinary();
281
        } else {
282
            $hash = new Buffer($this->schnorrSigHashCache[$cacheCheck], 32);
283
        }
284
285
        return $hash;
286
    }
287
288
    public function checkSigSchnorr(BufferInterface $sig64, BufferInterface $key32, int $sigVersion): bool
289
    {
290
        if ($sig64->getSize() === 0) {
291
            return false;
292
        }
293
        if ($key32->getSize() !== 32) {
294
            return false;
295
        }
296
297
        $hashType = SigHash::TAPDEFAULT;
298
        if ($sig64->getSize() === 65) {
299
            $hashType = $sig64->slice(64, 1);
300
            if ($hashType == SigHash::TAPDEFAULT) {
0 ignored issues
show
introduced by
The condition $hashType == BitWasp\Bit...ash\SigHash::TAPDEFAULT is always false.
Loading history...
301
                return false;
302
            }
303
            $sig64 = $sig64->slice(0, 64);
304
        }
305
306
        if ($sig64->getSize() !== 64) {
307
            return false;
308
        }
309
310
        try {
311
            $sig = $this->schnorrSigSerializer->parse($sig64);
312
            $pubKey = $this->xonlyKeySerializer->parse($key32);
313
            $sigHash = $this->getTaprootSigHash($hashType, $sigVersion);
0 ignored issues
show
Bug introduced by
It seems like $hashType can also be of type BitWasp\Buffertools\BufferInterface; however, parameter $sigHashType of BitWasp\Bitcoin\Script\I...se::getTaprootSigHash() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

313
            $sigHash = $this->getTaprootSigHash(/** @scrutinizer ignore-type */ $hashType, $sigVersion);
Loading history...
314
            return $pubKey->verifySchnorr($sigHash, $sig);
315
        } catch (\Exception $e) {
316
            return false;
317
        }
318
    }
319
320
    /**
321
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
322
     * @return bool
323
     */
324 8
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime): bool
325
    {
326 8
        $input = $this->transaction->getInput($this->nInput);
327 8
        $nLockTime = $scriptLockTime->getInt();
328 8
        $txLockTime = $this->transaction->getLockTime();
329
330 8
        if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) ||
331 8
            ($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX))
332
        ) {
333 2
            return false;
334
        }
335
336 6
        if ($nLockTime > $txLockTime) {
337 2
            return false;
338
        }
339
340 4
        if ($input->isFinal()) {
341 1
            return false;
342
        }
343
344 3
        return true;
345
    }
346
347
    /**
348
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
349
     * @return bool
350
     */
351 15
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence): bool
352
    {
353 15
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
354 15
        if ($this->transaction->getVersion() < 2) {
355 10
            return false;
356
        }
357
358 5
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
359 1
            return false;
360
        }
361
362 4
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
363
364 4
        $txToSequenceMasked = $txSequence & $mask;
365 4
        $nSequenceMasked = $sequence->getInt() & $mask;
366 4
        if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
367 4
            ($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG))
368
        ) {
369 2
            return false;
370
        }
371
372 2
        if ($nSequenceMasked > $txToSequenceMasked) {
373
            return false;
374
        }
375
376 2
        return true;
377
    }
378
}
379