Completed
Pull Request — master (#451)
by thomas
71:01
created

Checker::getSigHash()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 15
c 0
b 0
f 0
nc 4
nop 3
dl 0
loc 22
ccs 0
cts 9
cp 0
crap 20
rs 8.9197
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
    private $adapter;
31
32
    /**
33
     * @var TransactionInterface
34
     */
35
    private $transaction;
36
37
    /**
38
     * @var int
39
     */
40
    private $nInput;
41
42
    /**
43
     * @var int|string
44
     */
45
    private $amount;
46
47
    /**
48
     * @var Hasher
49
     */
50 1328
    private $hasherV0;
51
52 1328
    /**
53 1328
     * @var array
54 1328
     */
55 1328
    private $sigHashCache = [];
56 1328
57
    /**
58
     * @var array
59
     */
60
    private $sigCache = [];
61
62 127
    /**
63
     * @var TransactionSignatureSerializer
64
     */
65 127
    private $sigSerializer;
66 81
67 50
    /**
68
     * @var PublicKeySerializerInterface
69
     */
70
    private $pubKeySerializer;
71 50
72
    /**
73
     * Checker constructor.
74
     * @param EcAdapterInterface $ecAdapter
75
     * @param TransactionInterface $transaction
76
     * @param int $nInput
77
     * @param int $amount
78
     * @param TransactionSignatureSerializer|null $sigSerializer
79
     * @param PublicKeySerializerInterface|null $pubKeySerializer
80 7
     */
81
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
82 7
    {
83 2
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, $ecAdapter));
0 ignored issues
show
Documentation introduced by
$ecAdapter is of type object<BitWasp\Bitcoin\C...ter\EcAdapterInterface>, but the function expects a boolean|object<BitWasp\B...\Crypto\EcAdapter\true>.

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...
84
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, $ecAdapter);
0 ignored issues
show
Documentation introduced by
$ecAdapter is of type object<BitWasp\Bitcoin\C...ter\EcAdapterInterface>, but the function expects a boolean|object<BitWasp\B...\Crypto\EcAdapter\true>.

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...
85
        $this->adapter = $ecAdapter;
86 5
        $this->transaction = $transaction;
87 5
        $this->nInput = $nInput;
88 5
        $this->amount = $amount;
89 5
    }
90
91 5
    /**
92
     * @param BufferInterface $signature
93
     * @return bool
94
     */
95
    public function isValidSignatureEncoding(BufferInterface $signature)
96
    {
97
        try {
98 70
            TransactionSignature::isDERSignature($signature);
99
            return true;
100 70
        } catch (SignatureNotCanonical $e) {
101
            /* In any case, we will return false outside this block */
102 70
        }
103
104
        return false;
105
    }
106
107
    /**
108
     * @param BufferInterface $signature
109
     * @return bool
110
     * @throws ScriptRuntimeException
111
     * @throws \Exception
112 18
     */
113
    public function isLowDerSignature(BufferInterface $signature)
114 18
    {
115 2
        if (!$this->isValidSignatureEncoding($signature)) {
116
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
117
        }
118 16
119 16
        $binary = $signature->getBinary();
120
        $nLenR = ord($binary[3]);
121
        $nLenS = ord($binary[5 + $nLenR]);
122
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
123
124
        return $this->adapter->validateSignatureElement($s, true);
125
    }
126
127
    /**
128 225
     * @param int $hashType
129
     * @return bool
130 225
     */
131 44
    public function isDefinedHashtype($hashType)
132
    {
133
        $nHashType = $hashType & (~(SigHash::ANYONECANPAY));
134 193
135 48
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
136 149
    }
137 3
138 146
    /**
139 4
     * Determine whether the sighash byte appended to the signature encodes
140
     * a valid sighash type.
141
     *
142 142
     * @param BufferInterface $signature
143
     * @return bool
144
     */
145
    public function isDefinedHashtypeSignature(BufferInterface $signature)
146
    {
147
        if ($signature->getSize() === 0) {
148
            return false;
149
        }
150
151 170
        $binary = $signature->getBinary();
152
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
153 170
    }
154 7
155
    /**
156
     * @param BufferInterface $signature
157 163
     * @param int $flags
158
     * @return $this
159
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
160
     */
161
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
162
    {
163
        if ($signature->getSize() === 0) {
164
            return $this;
165
        }
166
167
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
168
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
169 211
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
170
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
171
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
172 211
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
173 166
        }
174
175
        return $this;
176 161
    }
177 124
178
    /**
179 116
     * @param BufferInterface $publicKey
180 34
     * @param int $flags
181
     * @return $this
182 84
     * @throws \Exception
183
     */
184
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
185 116
    {
186
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
187 116
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
188 57
        }
189 57
190
        return $this;
191
    }
192
193
    /**
194
     * @param ScriptInterface $script
195
     * @param int $sigHashType
196
     * @param int $sigVersion
197
     * @return BufferInterface
198
     */
199
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
200
    {
201
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
202
        if (!isset($this->sigHashCache[$cacheCheck])) {
203
            if ($sigVersion === 1) {
204
                $hasher = new V1Hasher($this->transaction, $this->amount);
205
            } else {
206
                if ($this->hasherV0) {
207
                    $hasher = $this->hasherV0;
208
                } else {
209
                    $hasher = $this->hasherV0 = new Hasher($this->transaction, new TransactionSerializer());
210
                }
211
            }
212
213
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
214
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
215
        } else {
216
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
217
        }
218
219
        return $hash;
220
    }
221
222
    /**
223
     * @param ScriptInterface $script
224
     * @param BufferInterface $sigBuf
225
     * @param BufferInterface $keyBuf
226
     * @param int $sigVersion
227
     * @param int $flags
228
     * @return bool
229 2
     * @throws ScriptRuntimeException
230
     */
231 2
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
232 2
    {
233 2
        $this
234
            ->checkSignatureEncoding($sigBuf, $flags)
235
            ->checkPublicKeyEncoding($keyBuf, $flags);
236
237
        try {
238
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
239
            if (!isset($this->sigCache[$cacheCheck])) {
240
                $txSignature = $this->sigSerializer->parse($sigBuf);
0 ignored issues
show
Documentation introduced by
$sigBuf is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a string.

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...
241
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
242
243
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
244
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
245
            } else {
246
                $result = $this->sigCache[$cacheCheck];
247
            }
248
249
            return $result;
250
        } catch (\Exception $e) {
251
            return false;
252
        }
253
    }
254
255
    /**
256
     * @param int $txLockTime
257
     * @param int $nThreshold
258
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
259
     * @return bool
260
     */
261
    private function verifyLockTime($txLockTime, $nThreshold, \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
262
    {
263
        $nTime = $lockTime->getInt();
264
        if (($txLockTime < $nThreshold && $nTime < $nThreshold) ||
265
            ($txLockTime >= $nThreshold && $nTime >= $nThreshold)
266
        ) {
267
            return false;
268
        }
269
270
        return $nTime >= $txLockTime;
271
    }
272
273
    /**
274
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
275
     * @return bool
276
     */
277
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
278
    {
279
        if ($this->transaction->getInput($this->nInput)->isFinal()) {
280
            return false;
281
        }
282
283
        return $this->verifyLockTime($this->transaction->getLockTime(), Locktime::BLOCK_MAX, $lockTime);
284
    }
285
286
287
    /**
288
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
289
     * @return bool
290
     */
291
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
292
    {
293
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
294
        if ($this->transaction->getVersion() < 2) {
295
            return false;
296
        }
297
298
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
299
            return true;
300
        }
301
302
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
303
        return $this->verifyLockTime(
304
            $txSequence & $mask,
305
            TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG,
306
            Number::int($sequence->getInt() & $mask)
307
        );
308
    }
309
}
310