Completed
Pull Request — master (#239)
by thomas
49:01 queued 45:29
created

Checker::checkLockTime()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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