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
|
4695 |
|
public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, int $nInput, int $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null) |
76
|
|
|
{ |
77
|
4695 |
|
$this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter)); |
78
|
4695 |
|
$this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter); |
79
|
4695 |
|
$this->adapter = $ecAdapter; |
80
|
4695 |
|
$this->transaction = $transaction; |
81
|
4695 |
|
$this->nInput = $nInput; |
82
|
4695 |
|
$this->amount = $amount; |
83
|
4695 |
|
} |
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; |
|
|
|
|
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @param BufferInterface $signature |
95
|
|
|
* @return bool |
96
|
|
|
*/ |
97
|
165 |
|
public function isValidSignatureEncoding(BufferInterface $signature): bool |
98
|
|
|
{ |
99
|
|
|
try { |
100
|
165 |
|
TransactionSignature::isDERSignature($signature); |
101
|
75 |
|
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
|
43 |
|
public function isDefinedHashtype(int $hashType): bool |
134
|
|
|
{ |
135
|
43 |
|
$nHashType = $hashType & (~($this->sigHashOptionalBits)); |
136
|
|
|
|
137
|
43 |
|
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
|
498 |
|
public function checkSignatureEncoding(BufferInterface $signature, int $flags) |
164
|
|
|
{ |
165
|
498 |
|
if ($signature->getSize() === 0) { |
166
|
85 |
|
return $this; |
167
|
|
|
} |
168
|
|
|
|
169
|
421 |
|
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
|
332 |
|
} 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
|
327 |
|
} 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
|
318 |
|
return $this; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @param BufferInterface $publicKey |
182
|
|
|
* @param int $flags |
183
|
|
|
* @return $this |
184
|
|
|
* @throws \Exception |
185
|
|
|
*/ |
186
|
393 |
|
public function checkPublicKeyEncoding(BufferInterface $publicKey, int $flags) |
187
|
|
|
{ |
188
|
393 |
|
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
|
372 |
|
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
|
491 |
|
public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, int $sigVersion, int $flags) |
205
|
|
|
{ |
206
|
|
|
$this |
207
|
491 |
|
->checkSignatureEncoding($sigBuf, $flags) |
208
|
391 |
|
->checkPublicKeyEncoding($keyBuf, $flags); |
209
|
|
|
|
210
|
|
|
try { |
211
|
371 |
|
$cacheCheck = "{$script->getBinary()}{$sigVersion}{$keyBuf->getBinary()}{$sigBuf->getBinary()}"; |
212
|
371 |
|
if (!isset($this->sigCache[$cacheCheck])) { |
213
|
371 |
|
$txSignature = $this->sigSerializer->parse($sigBuf); |
214
|
263 |
|
$publicKey = $this->pubKeySerializer->parse($keyBuf); |
215
|
|
|
|
216
|
243 |
|
$hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion); |
217
|
243 |
|
$result = $this->sigCache[$cacheCheck] = $publicKey->verify($hash, $txSignature->getSignature()); |
218
|
|
|
} else { |
219
|
8 |
|
$result = $this->sigCache[$cacheCheck]; |
220
|
|
|
} |
221
|
|
|
|
222
|
243 |
|
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
|
|
|
public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime): bool |
233
|
|
|
{ |
234
|
|
|
$input = $this->transaction->getInput($this->nInput); |
235
|
|
|
$nLockTime = $scriptLockTime->getInt(); |
236
|
|
|
$txLockTime = $this->transaction->getLockTime(); |
237
|
|
|
|
238
|
|
|
if (!(($txLockTime < Locktime::BLOCK_MAX && $nLockTime < Locktime::BLOCK_MAX) || |
239
|
|
|
($txLockTime >= Locktime::BLOCK_MAX && $nLockTime >= Locktime::BLOCK_MAX)) |
240
|
|
|
) { |
241
|
|
|
return false; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
if ($nLockTime > $txLockTime) { |
245
|
|
|
return false; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
if ($input->isFinal()) { |
249
|
|
|
return false; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
return true; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence |
257
|
|
|
* @return bool |
258
|
|
|
*/ |
259
|
8 |
|
public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence): bool |
260
|
|
|
{ |
261
|
8 |
|
$txSequence = $this->transaction->getInput($this->nInput)->getSequence(); |
262
|
8 |
|
if ($this->transaction->getVersion() < 2) { |
263
|
8 |
|
return false; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) { |
267
|
|
|
return false; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
$mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK; |
271
|
|
|
|
272
|
|
|
$txToSequenceMasked = $txSequence & $mask; |
273
|
|
|
$nSequenceMasked = $sequence->getInt() & $mask; |
274
|
|
|
if (!(($txToSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked < TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG) || |
275
|
|
|
($txToSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG && $nSequenceMasked >= TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG)) |
276
|
|
|
) { |
277
|
|
|
return false; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
if ($nSequenceMasked > $txToSequenceMasked) { |
281
|
|
|
return false; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return true; |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
|
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 toprotected
if a sub-class needs to have access, orpublic
if an external class needs access.