Completed
Pull Request — master (#525)
by thomas
71:08
created

Checker::isLowDerSignature()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 13
ccs 8
cts 8
cp 1
crap 2
rs 9.4285
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\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
    protected $adapter;
31
32
    /**
33
     * @var TransactionInterface
34
     */
35
    protected $transaction;
36
37
    /**
38
     * @var int
39
     */
40
    protected $nInput;
41
42
    /**
43
     * @var int|string
44
     */
45
    protected $amount;
46
47
    /**
48
     * @var Hasher
49
     */
50
    protected $hasherV0;
51
52
    /**
53
     * @var array
54
     */
55
    protected $sigHashCache = [];
56
57
    /**
58
     * @var array
59
     */
60
    protected $sigCache = [];
61
62
    /**
63
     * @var TransactionSignatureSerializer
64
     */
65
    private $sigSerializer;
66
67
    /**
68
     * @var PublicKeySerializerInterface
69
     */
70
    private $pubKeySerializer;
71
72
    /**
73
     * @var int
74
     */
75
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
76
77
    /**
78
     * Checker constructor.
79
     * @param EcAdapterInterface $ecAdapter
80
     * @param TransactionInterface $transaction
81
     * @param int $nInput
82
     * @param int $amount
83
     * @param TransactionSignatureSerializer|null $sigSerializer
84
     * @param PublicKeySerializerInterface|null $pubKeySerializer
85
     */
86 2494
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
87
    {
88 2494
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
89 2494
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
90 2494
        $this->adapter = $ecAdapter;
91 2494
        $this->transaction = $transaction;
92 2494
        $this->nInput = $nInput;
93 2494
        $this->amount = $amount;
94 2494
    }
95
96
    /**
97
     * @param BufferInterface $signature
98
     * @return bool
99
     */
100 106
    public function isValidSignatureEncoding(BufferInterface $signature)
101
    {
102
        try {
103 106
            TransactionSignature::isDERSignature($signature);
104 58
            return true;
105 52
        } catch (SignatureNotCanonical $e) {
106
            /* In any case, we will return false outside this block */
107
        }
108
109 52
        return false;
110
    }
111
112
    /**
113
     * @param BufferInterface $signature
114
     * @return bool
115
     * @throws ScriptRuntimeException
116
     * @throws \Exception
117
     */
118 8
    public function isLowDerSignature(BufferInterface $signature)
119
    {
120 8
        if (!$this->isValidSignatureEncoding($signature)) {
121 2
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
122
        }
123
124 6
        $binary = $signature->getBinary();
125 6
        $nLenR = ord($binary[3]);
126 6
        $nLenS = ord($binary[5 + $nLenR]);
127 6
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
128
129 6
        return $this->adapter->validateSignatureElement($s, true);
130
    }
131
132
    /**
133
     * @param int $hashType
134
     * @return bool
135
     */
136 108
    public function isDefinedHashtype($hashType)
137
    {
138 108
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
139
140 108
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
141
    }
142
143
    /**
144
     * Determine whether the sighash byte appended to the signature encodes
145
     * a valid sighash type.
146
     *
147
     * @param BufferInterface $signature
148
     * @return bool
149
     */
150 28
    public function isDefinedHashtypeSignature(BufferInterface $signature)
151
    {
152 28
        if ($signature->getSize() === 0) {
153 2
            return false;
154
        }
155
156 26
        $binary = $signature->getBinary();
157 26
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
158
    }
159
160
    /**
161
     * @param BufferInterface $signature
162
     * @param int $flags
163
     * @return $this
164
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
165
     */
166 324
    public function checkSignatureEncoding(BufferInterface $signature, $flags)
167
    {
168 324
        if ($signature->getSize() === 0) {
169 54
            return $this;
170
        }
171
172 284
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
173 50
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
174 238
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
175 4
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
176 234
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
177 6
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
178
        }
179
180 228
        return $this;
181
    }
182
183
    /**
184
     * @param BufferInterface $publicKey
185
     * @param int $flags
186
     * @return $this
187
     * @throws \Exception
188
     */
189 264
    public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags)
190
    {
191 264
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
192 12
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
193
        }
194
195 252
        return $this;
196
    }
197
198
    /**
199
     * @param ScriptInterface $script
200
     * @param int $sigHashType
201
     * @param int $sigVersion
202
     * @return BufferInterface
203
     */
204 202
    public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion)
205
    {
206 202
        $cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary();
207 202
        if (!isset($this->sigHashCache[$cacheCheck])) {
208 202
            if (SigHash::V1 === $sigVersion) {
209 60
                $hasher = new V1Hasher($this->transaction, $this->amount);
210
            } else {
211 144
                if ($this->hasherV0) {
212
                    $hasher = $this->hasherV0;
213
                } else {
214 144
                    $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...
215
                }
216
            }
217
218 202
            $hash = $hasher->calculate($script, $this->nInput, $sigHashType);
219 202
            $this->sigHashCache[$cacheCheck] = $hash->getBinary();
220
        } else {
221 28
            $hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath());
222
        }
223
224 202
        return $hash;
225
    }
226
227
    /**
228
     * @param ScriptInterface $script
229
     * @param BufferInterface $sigBuf
230
     * @param BufferInterface $keyBuf
231
     * @param int $sigVersion
232
     * @param int $flags
233
     * @return bool
234
     * @throws ScriptRuntimeException
235
     */
236 310
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags)
237
    {
238
        $this
239 310
            ->checkSignatureEncoding($sigBuf, $flags)
240 260
            ->checkPublicKeyEncoding($keyBuf, $flags);
241
242
        try {
243 250
            $cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary();
244 250
            if (!isset($this->sigCache[$cacheCheck])) {
245 250
                $txSignature = $this->sigSerializer->parse($sigBuf);
246 196
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
247
248 186
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
249 186
                $result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature());
250
            } else {
251 26
                $result = $this->sigCache[$cacheCheck];
252
            }
253
254 186
            return $result;
255 78
        } catch (\Exception $e) {
256 78
            return false;
257
        }
258
    }
259
260
    /**
261
     * @param int $txLockTime
262
     * @param int $nThreshold
263
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
264
     * @return bool
265
     */
266
    private function verifyLockTime($txLockTime, $nThreshold, \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
267
    {
268
        $nTime = $lockTime->getInt();
269
        if (($txLockTime < $nThreshold && $nTime < $nThreshold) ||
270
            ($txLockTime >= $nThreshold && $nTime >= $nThreshold)
271
        ) {
272
            return false;
273
        }
274
275
        return $nTime >= $txLockTime;
276
    }
277
278
    /**
279
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime
280
     * @return bool
281
     */
282
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $lockTime)
283
    {
284
        if ($this->transaction->getInput($this->nInput)->isFinal()) {
285
            return false;
286
        }
287
288
        return $this->verifyLockTime($this->transaction->getLockTime(), Locktime::BLOCK_MAX, $lockTime);
289
    }
290
291
292
    /**
293
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
294
     * @return bool
295
     */
296 4
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence)
297
    {
298 4
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
299 4
        if ($this->transaction->getVersion() < 2) {
300 4
            return false;
301
        }
302
303
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
304
            return true;
305
        }
306
307
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
308
        return $this->verifyLockTime(
309
            $txSequence & $mask,
310
            TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG,
311
            Number::int($sequence->getInt() & $mask)
312
        );
313
    }
314
}
315