Completed
Pull Request — master (#538)
by thomas
69:50
created

Checker::getSigHash()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 4
nop 3
dl 0
loc 22
ccs 10
cts 11
cp 0.9091
crap 4.0119
rs 8.9197
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\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\TransactionInput;
21
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
22
use BitWasp\Bitcoin\Transaction\TransactionInterface;
23
use BitWasp\Buffertools\Buffer;
24
use BitWasp\Buffertools\BufferInterface;
25
26
class Checker
27
{
28
    /**
29
     * @var EcAdapterInterface
30
     */
31
    protected $adapter;
32
33
    /**
34
     * @var TransactionInterface
35
     */
36
    protected $transaction;
37
38
    /**
39
     * @var int
40
     */
41
    protected $nInput;
42
43
    /**
44
     * @var int|string
45
     */
46
    protected $amount;
47
48
    /**
49
     * @var Hasher
50
     */
51
    protected $hasherV0;
52
53
    /**
54
     * @var array
55
     */
56
    protected $sigHashCache = [];
57
58
    /**
59
     * @var array
60
     */
61
    protected $sigCache = [];
62
63
    /**
64
     * @var TransactionSignatureSerializer
65
     */
66
    private $sigSerializer;
67
68
    /**
69
     * @var PublicKeySerializerInterface
70
     */
71
    private $pubKeySerializer;
72
73
    /**
74
     * @var int
75
     */
76
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
77
78
    /**
79
     * Checker constructor.
80
     * @param EcAdapterInterface $ecAdapter
81
     * @param TransactionInterface $transaction
82
     * @param int $nInput
83
     * @param int $amount
84
     * @param TransactionSignatureSerializer|null $sigSerializer
85
     * @param PublicKeySerializerInterface|null $pubKeySerializer
86 2530
     */
87
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
88 2530
    {
89 2530
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
90 2530
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
91 2530
        $this->adapter = $ecAdapter;
92 2530
        $this->transaction = $transaction;
93 2530
        $this->nInput = $nInput;
94 2530
        $this->amount = $amount;
95
    }
96
97
    /**
98
     * @param BufferInterface $signature
99
     * @return bool
100 156
     */
101
    public function isValidSignatureEncoding(BufferInterface $signature)
102
    {
103 156
        try {
104 108
            TransactionSignature::isDERSignature($signature);
105 52
            return true;
106
        } catch (SignatureNotCanonical $e) {
107
            /* In any case, we will return false outside this block */
108
        }
109 52
110
        return false;
111
    }
112
113
    /**
114
     * @param BufferInterface $signature
115
     * @return bool
116
     * @throws ScriptRuntimeException
117
     * @throws \Exception
118 8
     */
119
    public function isLowDerSignature(BufferInterface $signature)
120 8
    {
121 2
        if (!$this->isValidSignatureEncoding($signature)) {
122
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
123
        }
124 6
125 6
        $binary = $signature->getBinary();
126 6
        $nLenR = ord($binary[3]);
127 6
        $nLenS = ord($binary[5 + $nLenR]);
128
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
129 6
130
        return $this->adapter->validateSignatureElement($s, true);
131
    }
132
133
    /**
134
     * @param int $hashType
135
     * @return bool
136 144
     */
137
    public function isDefinedHashtype($hashType)
138 144
    {
139
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
140 144
141
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
142
    }
143
144
    /**
145
     * Determine whether the sighash byte appended to the signature encodes
146
     * a valid sighash type.
147
     *
148
     * @param BufferInterface $signature
149
     * @return bool
150 28
     */
151
    public function isDefinedHashtypeSignature(BufferInterface $signature)
152 28
    {
153 2
        if ($signature->getSize() === 0) {
154
            return false;
155
        }
156 26
157 26
        $binary = $signature->getBinary();
158
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
159
    }
160
161
    /**
162
     * @param BufferInterface $signature
163
     * @param int $flags
164
     * @return $this
165
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
166 374
     */
167
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
168 374
    {
169 48
        if ($signature->getSize() === 0) {
170
            return $this;
171
        }
172 334
173 50
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
174 288
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
175 4
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
176 284
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
177 6
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
178
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
179
        }
180 278
181
        return $this;
182
    }
183
184
    /**
185
     * @param BufferInterface $publicKey
186
     * @param int $flags
187
     * @return $this
188
     * @throws \Exception
189 314
     */
190
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
191 314
    {
192 12
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
193
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
194
        }
195 302
196
        return $this;
197
    }
198
199
    /**
200
     * @param ScriptInterface $script
201
     * @param int $sigHashType
202
     * @param int $sigVersion
203
     * @return BufferInterface
204 240
     */
205
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
206 240
    {
207 240
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
208 240
        if (!isset($this->sigHashCache[$cacheCheck])) {
209 60
            if (SigHash::V1 === $sigVersion) {
210
                $hasher = new V1Hasher($this->transaction, $this->amount);
211 182
            } else {
212
                if ($this->hasherV0) {
213
                    $hasher = $this->hasherV0;
214 182
                } else {
215
                    $hasher = $this->hasherV0 = new Hasher($this->transaction, new TransactionSerializer());
0 ignored issues
show
Unused Code introduced by
The call to Hasher::__construct() has too many arguments starting with new \BitWasp\Bitcoin\Ser...TransactionSerializer().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
216
                }
217
            }
218 240
219 240
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
220
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
221 56
        } else {
222
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
223
        }
224 240
225
        return $hash;
226
    }
227
228
    /**
229
     * @param ScriptInterface $script
230
     * @param BufferInterface $sigBuf
231
     * @param BufferInterface $keyBuf
232
     * @param int $sigVersion
233
     * @param int $flags
234
     * @return bool
235
     * @throws ScriptRuntimeException
236 360
     */
237
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
238
    {
239 360
        $this
240 310
            ->checkSignatureEncoding($sigBuf, $flags)
241
            ->checkPublicKeyEncoding($keyBuf, $flags);
242
243 300
        try {
244 300
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
245 300
            if (!isset($this->sigCache[$cacheCheck])) {
246 246
                $txSignature = $this->sigSerializer->parse($sigBuf);
247
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
248 236
249 236
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
250
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
251 4
            } else {
252
                $result = $this->sigCache[$cacheCheck];
253
            }
254 236
255 72
            return $result;
256 72
        } catch (\Exception $e) {
257
            return false;
258
        }
259
    }
260
261
    /**
262
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
263
     * @return bool
264
     */
265
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime)
266
    {
267
        $input = $this->transaction->getInput($this->nInput);
268
269
        $nLockTime = $scriptLockTime->getInt();
270
        $txLockTime = $this->transaction->getLockTime();
271
        if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) ||
272
            ($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX))
273
        ) {
274
            return false;
275
        }
276
277
        if ($nLockTime > $txLockTime) {
278
            return false;
279
        }
280
281
        if ($input->isFinal()) {
282
            return false;
283
        }
284
285
        return true;
286
    }
287
288
    /**
289
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
290
     * @return bool
291
     */
292
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
293
    {
294
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
295
        if ($this->transaction->getVersion() < 2) {
296 4
            return false;
297
        }
298 4
299 4
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
300 4
            return true;
301
        }
302
303
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
304
305
        $txToSequenceMasked = $txSequence & $mask;
306
        $nSequenceMasked = $sequence->getInt() & $mask;
307
        if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
308
            ($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG))
309
        ) {
310
            return false;
311
        }
312
313
        if ($nSequenceMasked > $txToSequenceMasked) {
314
            return false;
315
        }
316
317
        return true;
318
    }
319
}
320