1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace BitWasp\Bitcoin\Script\Interpreter; |
4
|
|
|
|
5
|
|
|
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface; |
6
|
|
|
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer; |
7
|
|
|
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey; |
8
|
|
|
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface; |
9
|
|
|
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface; |
10
|
|
|
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException; |
11
|
|
|
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical; |
12
|
|
|
use BitWasp\Bitcoin\Locktime; |
13
|
|
|
use BitWasp\Bitcoin\Script\ScriptInterface; |
14
|
|
|
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer; |
15
|
|
|
use BitWasp\Bitcoin\Serializer\Transaction\TransactionSerializer; |
16
|
|
|
use BitWasp\Bitcoin\Signature\TransactionSignature; |
17
|
|
|
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher; |
18
|
|
|
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash; |
19
|
|
|
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher; |
20
|
|
|
use BitWasp\Bitcoin\Transaction\TransactionInputInterface; |
21
|
|
|
use BitWasp\Bitcoin\Transaction\TransactionInterface; |
22
|
|
|
use BitWasp\Buffertools\Buffer; |
23
|
|
|
use BitWasp\Buffertools\BufferInterface; |
24
|
|
|
|
25
|
|
|
class Checker |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* @var EcAdapterInterface |
29
|
|
|
*/ |
30
|
|
|
protected $adapter; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var TransactionInterface |
34
|
|
|
*/ |
35
|
|
|
protected $transaction; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var int |
39
|
|
|
*/ |
40
|
|
|
protected $nInput; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var int|string |
44
|
|
|
*/ |
45
|
|
|
protected $amount; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var Hasher |
49
|
|
|
*/ |
50
|
|
|
protected $hasherV0; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var array |
54
|
|
|
*/ |
55
|
|
|
protected $sigHashCache = []; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var array |
59
|
|
|
*/ |
60
|
|
|
protected $sigCache = []; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var TransactionSignatureSerializer |
64
|
|
|
*/ |
65
|
|
|
private $sigSerializer; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var PublicKeySerializerInterface |
69
|
|
|
*/ |
70
|
|
|
private $pubKeySerializer; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @var int |
74
|
|
|
*/ |
75
|
|
|
protected $sigHashOptionalBits = SigHash::ANYONECANPAY; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Checker constructor. |
79
|
|
|
* @param EcAdapterInterface $ecAdapter |
80
|
|
|
* @param TransactionInterface $transaction |
81
|
|
|
* @param int $nInput |
82
|
|
|
* @param int $amount |
83
|
|
|
* @param TransactionSignatureSerializer|null $sigSerializer |
84
|
|
|
* @param PublicKeySerializerInterface|null $pubKeySerializer |
85
|
|
|
*/ |
86
|
2466 |
|
public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, $nInput, $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null) |
87
|
|
|
{ |
88
|
2466 |
|
$this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter)); |
89
|
2466 |
|
$this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter); |
90
|
2466 |
|
$this->adapter = $ecAdapter; |
91
|
2466 |
|
$this->transaction = $transaction; |
92
|
2466 |
|
$this->nInput = $nInput; |
93
|
2466 |
|
$this->amount = $amount; |
94
|
2466 |
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @param BufferInterface $signature |
98
|
|
|
* @return bool |
99
|
|
|
*/ |
100
|
94 |
|
public function isValidSignatureEncoding(BufferInterface $signature) |
101
|
|
|
{ |
102
|
|
|
try { |
103
|
94 |
|
TransactionSignature::isDERSignature($signature); |
104
|
46 |
|
return true; |
105
|
52 |
|
} catch (SignatureNotCanonical $e) { |
106
|
|
|
/* In any case, we will return false outside this block */ |
107
|
|
|
} |
108
|
|
|
|
109
|
52 |
|
return false; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @param BufferInterface $signature |
114
|
|
|
* @return bool |
115
|
|
|
* @throws ScriptRuntimeException |
116
|
|
|
* @throws \Exception |
117
|
|
|
*/ |
118
|
8 |
|
public function isLowDerSignature(BufferInterface $signature) |
119
|
|
|
{ |
120
|
8 |
|
if (!$this->isValidSignatureEncoding($signature)) { |
121
|
2 |
|
throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding'); |
122
|
|
|
} |
123
|
|
|
|
124
|
6 |
|
$binary = $signature->getBinary(); |
125
|
6 |
|
$nLenR = ord($binary[3]); |
126
|
6 |
|
$nLenS = ord($binary[5 + $nLenR]); |
127
|
6 |
|
$s = $signature->slice(6 + $nLenR, $nLenS)->getGmp(); |
128
|
|
|
|
129
|
6 |
|
return $this->adapter->validateSignatureElement($s, true); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param int $hashType |
134
|
|
|
* @return bool |
135
|
|
|
*/ |
136
|
80 |
|
public function isDefinedHashtype($hashType) |
137
|
|
|
{ |
138
|
80 |
|
$nHashType = $hashType & (~($this->sigHashOptionalBits)); |
139
|
|
|
|
140
|
80 |
|
return !(($nHashType < SigHash::ALL) || ($nHashType > SigHash::SINGLE)); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Determine whether the sighash byte appended to the signature encodes |
145
|
|
|
* a valid sighash type. |
146
|
|
|
* |
147
|
|
|
* @param BufferInterface $signature |
148
|
|
|
* @return bool |
149
|
|
|
*/ |
150
|
28 |
|
public function isDefinedHashtypeSignature(BufferInterface $signature) |
151
|
|
|
{ |
152
|
28 |
|
if ($signature->getSize() === 0) { |
153
|
2 |
|
return false; |
154
|
|
|
} |
155
|
|
|
|
156
|
26 |
|
$binary = $signature->getBinary(); |
157
|
26 |
|
return $this->isDefinedHashtype(ord(substr($binary, -1))); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param BufferInterface $signature |
162
|
|
|
* @param int $flags |
163
|
|
|
* @return $this |
164
|
|
|
* @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException |
165
|
|
|
*/ |
166
|
312 |
|
public function checkSignatureEncoding(BufferInterface $signature, $flags) |
167
|
|
|
{ |
168
|
312 |
|
if ($signature->getSize() === 0) { |
169
|
54 |
|
return $this; |
170
|
|
|
} |
171
|
|
|
|
172
|
272 |
|
if (($flags & (Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_LOW_S | Interpreter::VERIFY_STRICTENC)) !== 0 && !$this->isValidSignatureEncoding($signature)) { |
173
|
50 |
|
throw new ScriptRuntimeException(Interpreter::VERIFY_DERSIG, 'Signature with incorrect encoding'); |
174
|
226 |
|
} else if (($flags & Interpreter::VERIFY_LOW_S) !== 0 && !$this->isLowDerSignature($signature)) { |
175
|
4 |
|
throw new ScriptRuntimeException(Interpreter::VERIFY_LOW_S, 'Signature s element was not low'); |
176
|
222 |
|
} else if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !$this->isDefinedHashtypeSignature($signature)) { |
177
|
6 |
|
throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Signature with invalid hashtype'); |
178
|
|
|
} |
179
|
|
|
|
180
|
216 |
|
return $this; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* @param BufferInterface $publicKey |
185
|
|
|
* @param int $flags |
186
|
|
|
* @return $this |
187
|
|
|
* @throws \Exception |
188
|
|
|
*/ |
189
|
252 |
|
public function checkPublicKeyEncoding(BufferInterface $publicKey, $flags) |
190
|
|
|
{ |
191
|
252 |
|
if (($flags & Interpreter::VERIFY_STRICTENC) !== 0 && !PublicKey::isCompressedOrUncompressed($publicKey)) { |
192
|
12 |
|
throw new ScriptRuntimeException(Interpreter::VERIFY_STRICTENC, 'Public key with incorrect encoding'); |
193
|
|
|
} |
194
|
|
|
|
195
|
240 |
|
return $this; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @param ScriptInterface $script |
200
|
|
|
* @param int $sigHashType |
201
|
|
|
* @param int $sigVersion |
202
|
|
|
* @return BufferInterface |
203
|
|
|
*/ |
204
|
174 |
|
public function getSigHash(ScriptInterface $script, $sigHashType, $sigVersion) |
205
|
|
|
{ |
206
|
174 |
|
$cacheCheck = $sigVersion . $sigHashType . $script->getBuffer()->getBinary(); |
207
|
174 |
|
if (!isset($this->sigHashCache[$cacheCheck])) { |
208
|
174 |
|
if (SigHash::V1 === $sigVersion) { |
209
|
46 |
|
$hasher = new V1Hasher($this->transaction, $this->amount); |
210
|
|
|
} else { |
211
|
130 |
|
if ($this->hasherV0) { |
212
|
|
|
$hasher = $this->hasherV0; |
213
|
|
|
} else { |
214
|
130 |
|
$hasher = $this->hasherV0 = new Hasher($this->transaction, new TransactionSerializer()); |
|
|
|
|
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
174 |
|
$hash = $hasher->calculate($script, $this->nInput, $sigHashType); |
219
|
174 |
|
$this->sigHashCache[$cacheCheck] = $hash->getBinary(); |
220
|
|
|
} else { |
221
|
12 |
|
$hash = new Buffer($this->sigHashCache[$cacheCheck], 32, $this->adapter->getMath()); |
222
|
|
|
} |
223
|
|
|
|
224
|
174 |
|
return $hash; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @param ScriptInterface $script |
229
|
|
|
* @param BufferInterface $sigBuf |
230
|
|
|
* @param BufferInterface $keyBuf |
231
|
|
|
* @param int $sigVersion |
232
|
|
|
* @param int $flags |
233
|
|
|
* @return bool |
234
|
|
|
* @throws ScriptRuntimeException |
235
|
|
|
*/ |
236
|
298 |
|
public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags) |
237
|
|
|
{ |
238
|
|
|
$this |
239
|
298 |
|
->checkSignatureEncoding($sigBuf, $flags) |
240
|
248 |
|
->checkPublicKeyEncoding($keyBuf, $flags); |
241
|
|
|
|
242
|
|
|
try { |
243
|
238 |
|
$cacheCheck = $flags . $sigVersion . $keyBuf->getBinary() . $sigBuf->getBinary(); |
244
|
238 |
|
if (!isset($this->sigCache[$cacheCheck])) { |
245
|
238 |
|
$txSignature = $this->sigSerializer->parse($sigBuf); |
246
|
184 |
|
$publicKey = $this->pubKeySerializer->parse($keyBuf); |
247
|
|
|
|
248
|
174 |
|
$hash = $this->getSigHash($script, $txSignature->getHashType(), $sigVersion); |
249
|
174 |
|
$result = $this->sigCache[$cacheCheck] = $this->adapter->verify($hash, $publicKey, $txSignature->getSignature()); |
250
|
|
|
} else { |
251
|
14 |
|
$result = $this->sigCache[$cacheCheck]; |
252
|
|
|
} |
253
|
|
|
|
254
|
174 |
|
return $result; |
255
|
78 |
|
} catch (\Exception $e) { |
256
|
78 |
|
return false; |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* @param int $txLockTime |
262
|
|
|
* @param int $nThreshold |
263
|
|
|
* @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime |
264
|
|
|
* @return bool |
265
|
|
|
*/ |
266
|
|
|
private function verifyLockTime($txLockTime, $nThreshold, \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime) |
267
|
|
|
{ |
268
|
|
|
$nTime = $lockTime->getInt(); |
269
|
|
|
if (($txLockTime < $nThreshold && $nTime < $nThreshold) || |
270
|
|
|
($txLockTime >= $nThreshold && $nTime >= $nThreshold) |
271
|
|
|
) { |
272
|
|
|
return false; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
return $nTime >= $txLockTime; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @param \BitWasp\Bitcoin\Script\Interpreter\Number $lockTime |
280
|
|
|
* @return bool |
281
|
|
|
*/ |
282
|
|
|
public function checkLockTime(\BitWasp\Bitcoin\Script\Interpreter\Number $lockTime) |
283
|
|
|
{ |
284
|
|
|
if ($this->transaction->getInput($this->nInput)->isFinal()) { |
285
|
|
|
return false; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
return $this->verifyLockTime($this->transaction->getLockTime(), Locktime::BLOCK_MAX, $lockTime); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence |
294
|
|
|
* @return bool |
295
|
|
|
*/ |
296
|
4 |
|
public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence) |
297
|
|
|
{ |
298
|
4 |
|
$txSequence = $this->transaction->getInput($this->nInput)->getSequence(); |
299
|
4 |
|
if ($this->transaction->getVersion() < 2) { |
300
|
4 |
|
return false; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
if (($txSequence & TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG) !== 0) { |
304
|
|
|
return true; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
$mask = TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG | TransactionInputInterface::SEQUENCE_LOCKTIME_MASK; |
308
|
|
|
return $this->verifyLockTime( |
309
|
|
|
$txSequence & $mask, |
310
|
|
|
TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG, |
311
|
|
|
Number::int($sequence->getInt() & $mask) |
312
|
|
|
); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.