Completed
Pull Request — master (#391)
by thomas
259:03 queued 188:03
created

Checker::isDefinedHashtype()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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