Completed
Pull Request — master (#403)
by thomas
31:50
created

Checker::checkSig()   A

Complexity

Conditions 3
Paths 10

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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