Completed
Pull Request — master (#593)
by thomas
14:58
created

CheckerBase::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
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\Signature\DerSignatureSerializerInterface;
12
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
13
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
14
use BitWasp\Bitcoin\Locktime;
15
use BitWasp\Bitcoin\Script\ScriptInterface;
16
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
17
use BitWasp\Bitcoin\Signature\TransactionSignature;
18
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
19
use BitWasp\Bitcoin\Transaction\TransactionInput;
20
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
21
use BitWasp\Bitcoin\Transaction\TransactionInterface;
22
use BitWasp\Buffertools\BufferInterface;
23
24
abstract class CheckerBase
25
{
26
    /**
27
     * @var EcAdapterInterface
28
     */
29
    protected $adapter;
30
31
    /**
32
     * @var TransactionInterface
33
     */
34
    protected $transaction;
35
36
    /**
37
     * @var int
38
     */
39
    protected $nInput;
40
41
    /**
42
     * @var int|string
43
     */
44
    protected $amount;
45
46
    /**
47
     * @var array
48
     */
49
    protected $sigCache = [];
50
51
    /**
52
     * @var TransactionSignatureSerializer
53
     */
54
    private $sigSerializer;
55
56
    /**
57
     * @var PublicKeySerializerInterface
58
     */
59
    private $pubKeySerializer;
60
61
    /**
62
     * @var int
63
     */
64
    protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
65
66
    /**
67
     * Checker constructor.
68
     * @param EcAdapterInterface $ecAdapter
69
     * @param TransactionInterface $transaction
70
     * @param int $nInput
71
     * @param int $amount
72
     * @param TransactionSignatureSerializer|null $sigSerializer
73
     * @param PublicKeySerializerInterface|null $pubKeySerializer
74
     */
75 4803
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, int $nInput, int $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
76
    {
77 4803
        $this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
78 4803
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
79 4803
        $this->adapter = $ecAdapter;
80 4803
        $this->transaction = $transaction;
81 4803
        $this->nInput = $nInput;
82 4803
        $this->amount = $amount;
83 4803
    }
84
85
    /**
86
     * @param ScriptInterface $script
87
     * @param int $hashType
88
     * @param int $sigVersion
89
     * @return BufferInterface
90
     */
91
    abstract function getSigHash(ScriptInterface $script, int $hashType, int $sigVersion): BufferInterface;
0 ignored issues
show
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for getSigHash.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
92
93
    /**
94
     * @param BufferInterface $signature
95
     * @return bool
96
     */
97 195
    public function isValidSignatureEncoding(BufferInterface $signature): bool
98
    {
99
        try {
100 195
            TransactionSignature::isDERSignature($signature);
101 105
            return true;
102 98
        } catch (SignatureNotCanonical $e) {
103
            /* In any case, we will return false outside this block */
104
        }
105
106 98
        return false;
107
    }
108
109
    /**
110
     * @param BufferInterface $signature
111
     * @return bool
112
     * @throws ScriptRuntimeException
113
     * @throws \Exception
114
     */
115 7
    public function isLowDerSignature(BufferInterface $signature): bool
116
    {
117 7
        if (!$this->isValidSignatureEncoding($signature)) {
118 1
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
119
        }
120
121 6
        $binary = $signature->getBinary();
122 6
        $nLenR = ord($binary[3]);
123 6
        $nLenS = ord($binary[5 + $nLenR]);
124 6
        $s = $signature->slice(6 + $nLenR, $nLenS)->getGmp();
125
126 6
        return $this->adapter->validateSignatureElement($s, true);
127
    }
128
129
    /**
130
     * @param int $hashType
131
     * @return bool
132
     */
133 132
    public function isDefinedHashtype(int $hashType): bool
134
    {
135 132
        $nHashType = $hashType & (~($this->sigHashOptionalBits));
136
137 132
        return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE));
138
    }
139
140
    /**
141
     * Determine whether the sighash byte appended to the signature encodes
142
     * a valid sighash type.
143
     *
144
     * @param BufferInterface $signature
145
     * @return bool
146
     */
147 44
    public function isDefinedHashtypeSignature(BufferInterface $signature): bool
148
    {
149 44
        if ($signature->getSize() === 0) {
150 1
            return false;
151
        }
152
153 43
        $binary = $signature->getBinary();
154 43
        return $this->isDefinedHashtype(ord(substr($binary, -1)));
155
    }
156
157
    /**
158
     * @param BufferInterface $signature
159
     * @param int $flags
160
     * @return $this
161
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
162
     */
163 578
    public function checkSignatureEncoding(BufferInterface $signature, int $flags)
164
    {
165 578
        if ($signature->getSize() === 0) {
166 85
            return $this;
167
        }
168
169 501
        if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) {
170 97
            throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding');
171 412
        } else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) {
172 5
            throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low');
173 407
        } else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) {
174 9
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype');
175
        }
176
177 398
        return $this;
178
    }
179
180
    /**
181
     * @param BufferInterface $publicKey
182
     * @param int $flags
183
     * @return $this
184
     * @throws \Exception
185
     */
186 473
    public function checkPublicKeyEncoding(BufferInterface $publicKey, int $flags)
187
    {
188 473
        if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) {
189 21
            throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding');
190
        }
191
192 452
        return $this;
193
    }
194
195
    /**
196
     * @param ScriptInterface $script
197
     * @param BufferInterface $sigBuf
198
     * @param BufferInterface $keyBuf
199
     * @param int $sigVersion
200
     * @param int $flags
201
     * @return bool
202
     * @throws ScriptRuntimeException
203
     */
204 571
    public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, int $sigVersion, int $flags)
205
    {
206
        $this
207 571
            ->checkSignatureEncoding($sigBuf, $flags)
208 471
            ->checkPublicKeyEncoding($keyBuf, $flags);
209
210
        try {
211 451
            $cacheCheck = "{$script->getBinary()}{$sigVersion}{$keyBuf->getBinary()}{$sigBuf->getBinary()}";
212 451
            if (!isset($this->sigCache[$cacheCheck])) {
213 451
                $txSignature = $this->sigSerializer->parse($sigBuf);
214 343
                $publicKey = $this->pubKeySerializer->parse($keyBuf);
215
216 323
                $hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion);
217 323
                $result = $this->sigCache[$cacheCheck] = $publicKey->verify($hash, $txSignature->getSignature());
218
            } else {
219 8
                $result = $this->sigCache[$cacheCheck];
220
            }
221
222 323
            return $result;
223 136
        } catch (\Exception $e) {
224 136
            return false;
225
        }
226
    }
227
228
    /**
229
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
230
     * @return bool
231
     */
232 8
    public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime): bool
233
    {
234 8
        $input = $this->transaction->getInput($this->nInput);
235 8
        $nLockTime = $scriptLockTime->getInt();
236 8
        $txLockTime = $this->transaction->getLockTime();
237
238 8
        if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) ||
239 8
            ($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX))
240
        ) {
241 2
            return false;
242
        }
243
244 6
        if ($nLockTime > $txLockTime) {
245 2
            return false;
246
        }
247
248 4
        if ($input->isFinal()) {
249 1
            return false;
250
        }
251
252 3
        return true;
253
    }
254
255
    /**
256
     * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence
257
     * @return bool
258
     */
259 15
    public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence): bool
260
    {
261 15
        $txSequence = $this->transaction->getInput($this->nInput)->getSequence();
262 15
        if ($this->transaction->getVersion() < 2) {
263 10
            return false;
264
        }
265
266 5
        if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) {
267 1
            return false;
268
        }
269
270 4
        $mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK;
271
272 4
        $txToSequenceMasked = $txSequence & $mask;
273 4
        $nSequenceMasked = $sequence->getInt() & $mask;
274 4
        if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
275 4
            ($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG))
276
        ) {
277 2
            return false;
278
        }
279
280 2
        if ($nSequenceMasked > $txToSequenceMasked) {
281
            return false;
282
        }
283
284 2
        return true;
285
    }
286
}
287