Completed
Push — master ( a22894...37130c )
by thomas
62:03 queued 58:09
created

Checker   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 66.23%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 218
ccs 51
cts 77
cp 0.6623
rs 9.6
wmc 32
lcom 1
cbo 13

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A isValidSignatureEncoding() 0 11 2
A isLowDerSignature() 0 13 2
B checkSignatureEncoding() 0 16 8
A checkPublicKeyEncoding() 0 8 3
A checkSig() 0 22 3
B verifyLockTime() 0 11 5
A isDefinedHashtypeSignature() 0 11 3
A checkLockTime() 0 8 2
A checkSequence() 0 19 3
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Interpreter;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
8
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
9
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
10
use BitWasp\Bitcoin\Key\PublicKeyFactory;
11
use BitWasp\Bitcoin\Locktime;
12
use BitWasp\Bitcoin\Script\ScriptInterface;
13
use BitWasp\Bitcoin\Signature\TransactionSignature;
14
use BitWasp\Bitcoin\Signature\TransactionSignatureFactory;
15
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
16
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHashInterface;
17
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
18
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
19
use BitWasp\Bitcoin\Transaction\TransactionInterface;
20
use BitWasp\Buffertools\BufferInterface;
21
22
class Checker
23
{
24
    /**
25
     * @var EcAdapterInterface
26
     */
27
    private $adapter;
28
29
    /**
30
     * @var TransactionInterface
31
     */
32
    private $transaction;
33
34
    /**
35
     * @var int
36
     */
37
    private $nInput;
38
39
    /**
40
     * @var int|string
41
     */
42
    private $amount;
43
44
    /**
45
     * Checker constructor.
46
     * @param EcAdapterInterface $ecAdapter
47
     * @param TransactionInterface $transaction
48
     * @param int $nInput
49
     * @param int|string $amount
50
     */
51 441
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount)
52
    {
53 441
        $this->adapter = $ecAdapter;
54 441
        $this->transaction = $transaction;
55 441
        $this->nInput = $nInput;
56 441
        $this->amount = $amount;
57 441
    }
58
59
    /**
60
     * @param BufferInterface $signature
61
     * @return bool
62
     */
63 27
    public function isValidSignatureEncoding(BufferInterface $signature)
64
    {
65
        try {
66 27
            TransactionSignature::isDERSignature($signature);
67 18
            return true;
68 9
        } catch (SignatureNotCanonical $e) {
69
            /* In any case, we will return false outside this block */
70
        }
71
72 9
        return false;
73
    }
74
75
    /**
76
     * @param BufferInterface $signature
77
     * @return bool
78
     * @throws ScriptRuntimeException
79
     * @throws \Exception
80
     */
81 15
    public function isLowDerSignature(BufferInterface $signature)
82
    {
83 15
        if (!$this->isValidSignatureEncoding($signature)) {
84 3
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
85
        }
86
87 12
        $binary = $signature->getBinary();
88 12
        $nLenR = ord($binary[3]);
89 12
        $nLenS = ord($binary[5 + $nLenR]);
90 12
        $s = gmp_init($signature->slice(6 + $nLenR, $nLenS)->getInt(), 10);
91
92 12
        return $this->adapter->validateSignatureElement($s, true);
93
    }
94
95
    /**
96
     * Determine whether the sighash byte appended to the signature encodes
97
     * a valid sighash type.
98
     *
99
     * @param BufferInterface $signature
100
     * @return bool
101
     */
102 18
    public function isDefinedHashtypeSignature(BufferInterface $signature)
103
    {
104 18
        if ($signature->getSize() === 0) {
105 3
            return false;
106
        }
107
108 15
        $binary = $signature->getBinary();
109 15
        $nHashType = ord(substr($binary, -1)) & (~(SigHashInterface::ANYONECANPAY));
110
111 15
        return !(($nHashType < SigHashInterface::ALL) || ($nHashType > SigHashInterface::SINGLE));
112
    }
113
114
    /**
115
     * @param BufferInterface $signature
116
     * @param int $flags
117
     * @return $this
118
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
119
     */
120 60
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
121
    {
122 60
        if ($signature->getSize() === 0) {
123 3
            return $this;
124
        }
125
126 57
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) != 0 && !$this->isValidSignatureEncoding($signature)) {
127 6
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
128 51
        } else if (($flags & Interpreter::VERIFY_LOW_S) != 0 && !$this->isLowDerSignature($signature)) {
129 3
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
130 48
        } else if (($flags & Interpreter::VERIFY_STRICTENC) != 0 && !$this->isDefinedHashtypeSignature($signature)) {
131 3
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
132
        }
133
134 45
        return $this;
135
    }
136
137
    /**
138
     * @param BufferInterface $publicKey
139
     * @param int $flags
140
     * @return $this
141
     * @throws \Exception
142
     */
143 42
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
144
    {
145 42
        if (($flags & Interpreter::VERIFY_STRICTENC) != 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
146 3
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
147
        }
148
149 39
        return $this;
150
    }
151
152
    /**
153
     * @param ScriptInterface $script
154
     * @param BufferInterface $sigBuf
155
     * @param BufferInterface $keyBuf
156
     * @param int $sigVersion
157
     * @param int $flags
158
     * @return bool
159
     * @throws ScriptRuntimeException
160
     */
161 39
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
162
    {
163 26
        $this
164 39
            ->checkSignatureEncoding($sigBuf, $flags)
165 36
            ->checkPublicKeyEncoding($keyBuf, $flags);
166
167
        try {
168 36
            $txSignature = TransactionSignatureFactory::fromHex($sigBuf->getHex());
169 36
            $publicKey = PublicKeyFactory::fromHex($keyBuf->getHex());
170
171 36
            if ($sigVersion === 1) {
172 15
                $hasher = new V1Hasher($this->transaction, $this->amount);
173 10
            } else {
174 21
                $hasher = new Hasher($this->transaction);
175
            }
176
177 36
            $hash = $hasher->calculate($script, $this->nInput, $txSignature->getHashType());
178 36
            return $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
179
        } catch (\Exception $e) {
180
            return false;
181
        }
182
    }
183
184
    /**
185
     * @param int $txLockTime
186
     * @param int $nThreshold
187
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
188
     * @return bool
189
     */
190
    private function verifyLockTime($txLockTime, $nThreshold, \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
191
    {
192
        $nTime = $lockTime->getInt();
193
        if (($txLockTime < $nThreshold && $nTime < $nThreshold) ||
194
            ($txLockTime >= $nThreshold && $nTime >= $nThreshold)
195
        ) {
196
            return false;
197
        }
198
199
        return $nTime >= $txLockTime;
200
    }
201
202
    /**
203
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
204
     * @return bool
205
     */
206
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
207
    {
208
        if ($this->transaction->getInput($this->nInput)->isFinal()) {
209
            return false;
210
        }
211
212
        return $this->verifyLockTime($this->transaction->getLockTime(), Locktime::BLOCK_MAX, $lockTime);
213
    }
214
215
216
    /**
217
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
218
     * @return bool
219
     */
220
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
221
    {
222
        $math = $this->adapter->getMath();
223
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
224
        if ($this->transaction->getVersion() < 2) {
225
            return false;
226
        }
227
228
        if ($math->cmp($math->bitwiseAnd($txSequence, TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG), 0) !== 0) {
0 ignored issues
show
Documentation introduced by
$txSequence is of type integer, but the function expects a object<GMP>.

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...
Documentation introduced by
0 is of type integer, but the function expects a object<GMP>.

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...
229
            return 0;
230
        }
231
232
        $mask = $math->bitwiseOr(TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG, TransactionInputInterface::SEQUENCE_LOCKTIME_MASK);
233
        return $this->verifyLockTime(
234
            $math->bitwiseAnd($txSequence, $mask),
0 ignored issues
show
Documentation introduced by
$txSequence is of type integer, but the function expects a object<GMP>.

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...
Documentation introduced by
$math->bitwiseAnd($txSequence, $mask) is of type resource, but the function expects a integer.

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...
235
            TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG,
236
            Number::int($math->bitwiseAnd($sequence->getInt(), $mask))
0 ignored issues
show
Documentation introduced by
$sequence->getInt() is of type integer, but the function expects a object<GMP>.

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...
Documentation introduced by
$math->bitwiseAnd($sequence->getInt(), $mask) is of type resource, but the function expects a integer.

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...
237
        );
238
    }
239
}
240