Completed
Push — master ( 87bee6...df4f8a )
by thomas
109:43 queued 39:49
created

InputSigner::extractSignatures()   D

Complexity

Conditions 10
Paths 64

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 29
nc 64
nop 0
dl 0
loc 46
ccs 33
cts 33
cp 1
crap 10
rs 4.983
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
9
use BitWasp\Bitcoin\Key\PublicKeyFactory;
10
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
11
use BitWasp\Bitcoin\Script\Classifier\OutputData;
12
use BitWasp\Bitcoin\Script\Interpreter\Checker;
13
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
14
use BitWasp\Bitcoin\Script\Interpreter\Stack;
15
use BitWasp\Bitcoin\Script\Opcodes;
16
use BitWasp\Bitcoin\Script\Script;
17
use BitWasp\Bitcoin\Script\ScriptFactory;
18
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
19
use BitWasp\Bitcoin\Script\ScriptInterface;
20
use BitWasp\Bitcoin\Script\ScriptWitness;
21
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
22
use BitWasp\Bitcoin\Signature\SignatureSort;
23
use BitWasp\Bitcoin\Signature\TransactionSignature;
24
use BitWasp\Bitcoin\Signature\TransactionSignatureFactory;
25
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
26
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
27
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
28
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
29
use BitWasp\Bitcoin\Transaction\TransactionFactory;
30
use BitWasp\Bitcoin\Transaction\TransactionInterface;
31
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
32
use BitWasp\Buffertools\Buffer;
33
use BitWasp\Buffertools\BufferInterface;
34
35
class InputSigner
36
{
37
    /**
38
     * @var array
39
     */
40
    protected static $canSign = [
41
        OutputClassifier::PAYTOPUBKEYHASH,
42
        OutputClassifier::PAYTOPUBKEY,
43
        OutputClassifier::MULTISIG
44
    ];
45
46
    /**
47
     * @var array
48
     */
49
    protected static $validP2sh = [
50
        OutputClassifier::WITNESS_V0_KEYHASH,
51
        OutputClassifier::WITNESS_V0_SCRIPTHASH,
52
        OutputClassifier::PAYTOPUBKEYHASH,
53
        OutputClassifier::PAYTOPUBKEY,
54
        OutputClassifier::MULTISIG
55
    ];
56
57
    /**
58
     * @var EcAdapterInterface
59
     */
60
    private $ecAdapter;
61
62
    /**
63
     * @var OutputData $scriptPubKey
64
     */
65
    private $scriptPubKey;
66
67
    /**
68
     * @var OutputData $redeemScript
69
     */
70
    private $redeemScript;
71
72
    /**
73
     * @var OutputData $witnessScript
74
     */
75
    private $witnessScript;
76
77
    /**
78
     * @var OutputData
79
     */
80
    private $signScript;
81
82
    /**
83
     * @var int
84
     */
85
    private $sigVersion;
86
87
    /**
88
     * @var OutputData $witnessKeyHash
89
     */
90
    private $witnessKeyHash;
91
92
    /**
93
     * @var TransactionInterface
94
     */
95
    private $tx;
96
97
    /**
98
     * @var int
99
     */
100
    private $nInput;
101
102
    /**
103
     * @var TransactionOutputInterface
104
     */
105
    private $txOut;
106
107
    /**
108
     * @var PublicKeyInterface[]
109
     */
110
    private $publicKeys = [];
111
112
    /**
113
     * @var TransactionSignatureInterface[]
114
     */
115
    private $signatures = [];
116
117
    /**
118
     * @var int
119
     */
120
    private $requiredSigs = 0;
121
122
    /**
123
     * @var OutputClassifier
124
     */
125
    private $classifier;
126
127
    /**
128
     * @var Interpreter
129
     */
130
    private $interpreter;
131
132
    /**
133
     * @var Checker
134
     */
135
    private $signatureChecker;
136
137
    /**
138
     * TxInputSigning constructor.
139
     * @param EcAdapterInterface $ecAdapter
140
     * @param TransactionInterface $tx
141
     * @param int $nInput
142
     * @param TransactionOutputInterface $txOut
143
     * @param SignData $signData
144
     */
145 240
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData)
146
    {
147 240
        $inputs = $tx->getInputs();
148 6
        if (!isset($inputs[$nInput])) {
149
            throw new \RuntimeException('No input at this index');
150
        }
151 234
152 234
        $this->ecAdapter = $ecAdapter;
153 234
        $this->tx = $tx;
154 234
        $this->nInput = $nInput;
155 234
        $this->txOut = $txOut;
156 234
        $this->classifier = new OutputClassifier();
157 234
        $this->interpreter = new Interpreter();
158 234
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue());
159 234
        $this->flags = $signData->hasSignaturePolicy() ? $signData->getSignaturePolicy() : Interpreter::VERIFY_NONE;
0 ignored issues
show
Bug introduced by
The property flags does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
160 234
        $this->publicKeys = [];
161 234
        $this->signatures = [];
162 204
163 168
        $scriptSig = $inputs[$nInput]->getScript();
164
        $witness = isset($tx->getWitnesses()[$nInput]) ? $tx->getWitnesses()[$nInput]->all() : [];
165
166
        $this->solve($signData, $txOut->getScript(), $scriptSig, $witness);
167
    }
168
169
    /**
170 36
     * @param TransactionSignatureInterface[] $stack
171
     * @param PublicKeyInterface[] $publicKeys
172 36
     * @return \SplObjectStorage
173 36
     */
174
    private function sortMultiSigs($stack, array $publicKeys)
175 36
    {
176 36
        $sigSort = new SignatureSort($this->ecAdapter);
177 36
        $sigs = new \SplObjectStorage;
178 36
179 36
        foreach ($stack as $txSig) {
180 33
            $hash = $this->getSigHash($txSig->getHashType());
181 15
            $linked = $sigSort->link([$txSig->getSignature()], $publicKeys, $hash);
182 18
            foreach ($publicKeys as $key) {
183 18
                if ($linked->contains($key)) {
184
                    $sigs[$key] = $txSig;
185 36
                }
186
            }
187
        }
188
189
        return $sigs;
190
    }
191
192 174
    /**
193
     * @param ScriptInterface $script
194 174
     * @return \BitWasp\Buffertools\BufferInterface[]
195 174
     */
196 174
    private function evalPushOnly(ScriptInterface $script)
197 174
    {
198
        $stack = new Stack();
199
        $interpreter = new Interpreter();
200
        $interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
201
        return $stack->all();
202
    }
203
204
    /**
205
     * @param BufferInterface[] $buffers
206 168
     * @return ScriptInterface
207 144
     */
208
    private function pushAll(array $buffers)
209
    {
210
        return ScriptFactory::sequence(array_map(function ($buffer) {
211 144
            if (!($buffer instanceof BufferInterface)) {
212 144
                throw new \RuntimeException('Script contained a non-push opcode');
213 18
            }
214
215
            $size = $buffer->getSize();
216 144
            if ($size === 0) {
217 144
                return Opcodes::OP_0;
218
            }
219
220 144
            $first = ord($buffer->getBinary());
221
            if ($size === 1 && $first >= 1 && $first <= 16) {
222 168
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
223
            } else {
224
                return $buffer;
225
            }
226
        }, $buffers));
227
    }
228
229
    /**
230
     * @param int $flags
231 90
     * @param ScriptInterface $scriptSig
232
     * @param ScriptInterface $scriptPubKey
233 90
     * @param ScriptWitnessInterface|null $scriptWitness
234
     * @return bool
235
     */
236
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
237
    {
238
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
239
    }
240
241
    /**
242 132
     * @param ScriptInterface $scriptPubKey
243
     * @param array $chunks
244 132
     * @param int $sigVersion
245 132
     * @return bool
246 6
     */
247
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
248
    {
249 126
        $stack = new Stack($chunks);
250
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
251 45
            return false;
252
        }
253 126
254 6
        if ($stack->isEmpty()) {
255
            return false;
256
        }
257 120
258
        if (false === $this->interpreter->castToBool($stack[-1])) {
259
            return false;
260
        }
261
262
        return true;
263
    }
264
265
    /**
266
     * This function is strictly for $canSign types.
267
     * It will extract signatures/publicKeys when given $outputData, and $stack.
268
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
269
     * @param OutputData $outputData
270
     * @param array $stack
271
     * @param $sigVersion
272
     * @return mixed
273
     */
274 234
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
275
    {
276 234
        $type = $outputData->getType();
277 234
        $size = count($stack);
278 234
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
279 234
            $this->requiredSigs = 1;
280 234
            if ($size === 2) {
281 6
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
282
                    throw new \RuntimeException('Existing signatures are invalid!');
283
                }
284 228
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
285 90
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
286 90
            }
287 6
        }
288
289 84
        if ($type === OutputClassifier::PAYTOPUBKEY && count($stack) === 1) {
290 84
            $this->requiredSigs = 1;
291 6
            if ($size === 1) {
292
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
293 39
                    throw new \RuntimeException('Existing signatures are invalid!');
294
                }
295 216
296 24
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
297 24
                $this->publicKeys = [PublicKeyFactory::fromHex($outputData->getSolution())];
298 207
            }
299 66
        }
300 66
301 66
        if ($type === OutputClassifier::MULTISIG) {
302 6
            $info = new Multisig($outputData->getScript());
303
            $this->requiredSigs = $info->getRequiredSigCount();
304 60
            $this->publicKeys = $info->getKeys();
305 60
            if ($size > 1) {
306 6
                $vars = [];
307
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
308 27
                    $vars[] = TransactionSignatureFactory::fromHex($stack[$i], $this->ecAdapter);
309
                }
310 204
311 204
                $this->signatures = array_fill(0, count($this->publicKeys), null);
312
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
313 204
                $count = 0;
314
                foreach ($this->publicKeys as $idx => $key) {
315
                    if (isset($sigs[$key])) {
316
                        $this->signatures[$idx] = $sigs[$key];
317
                        $count++;
318
                    }
319
                }
320
321
                if (count($vars) !== $count) {
322
                    throw new \RuntimeException('Existing signatures are invalid!');
323
                }
324
                // Don't evaluate, already checked sigs during sort. Todo: fix this.
325 186
            }
326
        }
327 186
328 186
        return $type;
329 186
    }
330 108
331 108
    /**
332 102
     * Called upon instance creation.
333 6
     * This function must throw an exception whenever execution
334
     * does not yield a signable script.
335 96
     *
336 96
     * It ensures:
337 48
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
338 51
     *  - the P2SH script covers signable types and P2WSH/P2WKH
339
     *  - the witnessScript covers signable types only
340 180
     *  - violating the above prevents instance creation
341 36
     * @param SignData $signData
342 36
     * @param ScriptInterface $scriptPubKey
343 36
     * @param ScriptInterface $scriptSig
344 6
     * @param BufferInterface[] $witness
345
     * @return $this
346
     * @throws \Exception
347 30
     */
348 30
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
349 15
    {
350 15
        $sigVersion = SigHash::V0;
351
        $sigChunks = [];
352 174
353 36
        $solution = $this->scriptPubKey = $this->classifier->decode($scriptPubKey);
354 36
        if ($solution->getType() !== OutputClassifier::PAYTOSCRIPTHASH && !in_array($solution->getType(), self::$validP2sh)) {
355 36
            throw new \RuntimeException('scriptPubKey not supported');
356 36
        }
357 36
358 36
        if ($solution->canSign()) {
359 36
            $sigChunks = $this->evalPushOnly($scriptSig);
360 18
        }
361
362 36
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
363 36
            $chunks = $this->evalPushOnly($scriptSig);
364 36
            $redeemScript = null;
365 36
            if (count($chunks) > 0) {
366 36
                $redeemScript = new Script($chunks[count($chunks) - 1]);
367 30
            }
368 33
369 15
            if ($signData->hasRedeemScript()) {
370 18
                if ($redeemScript === null) {
371
                    $redeemScript = $signData->getRedeemScript();
372 36
                }
373 6
374
                if (!$redeemScript->equals($signData->getRedeemScript())) {
375
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
376 15
                }
377 15
            }
378
379 168
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
380
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
381
            }
382
383
            $solution = $this->redeemScript = $this->classifier->decode($redeemScript);
0 ignored issues
show
Bug introduced by
It seems like $redeemScript defined by null on line 364 can be null; however, BitWasp\Bitcoin\Script\C...putClassifier::decode() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
384
            if (!in_array($solution->getType(), self::$validP2sh)) {
385
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
386
            }
387
388 204
            $sigChunks = array_slice($chunks, 0, -1);
389
        }
390
391 204
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
392 204
            $sigVersion = SigHash::V1;
393 204
            $solution = $this->witnessKeyHash = $this->classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
394
            $sigChunks = $witness;
395 204
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
396 204
            $sigVersion = SigHash::V1;
397 204
398 204
            $witnessScript = null;
399 102
            if (count($witness) > 0) {
400 51
                $witnessScript = new Script($witness[count($witness) - 1]);
401
            }
402 204
403 72
            if ($signData->hasWitnessScript()) {
404 72
                if ($witnessScript === null) {
405 66
                    $witnessScript = $signData->getWitnessScript();
406 6
                } else {
407
                    if (!$witnessScript->equals($signData->getWitnessScript())) {
408
                        throw new \RuntimeException('Extracted witnessScript did not match sign data');
409 60
                    }
410 60
                }
411 30
            }
412 33
413
            // Essentially all the reference implementation does
414 198
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
415 24
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
416 24
            }
417 24
418 195
            $solution = $this->witnessScript = $this->classifier->decode($witnessScript);
0 ignored issues
show
Bug introduced by
It seems like $witnessScript defined by null on line 398 can be null; however, BitWasp\Bitcoin\Script\C...putClassifier::decode() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
419 54
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
420 54
                throw new \RuntimeException('Unsupported witness-script-hash script');
421 12
            }
422
423
            $sigChunks = array_slice($witness, 0, -1);
424 42
        }
425 42
426 42
        $this->sigVersion = $sigVersion;
427 21
        $this->signScript = $solution;
428 21
429
        $this->extractFromValues($solution, $sigChunks, $sigVersion);
430 186
431
        return $this;
432 168
    }
433
434
    /**
435
     * @param ScriptInterface $scriptCode
436
     * @param int $sigHashType
437
     * @param int $sigVersion
438
     * @return BufferInterface
439
     */
440
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
441 174
    {
442
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
443 174
            throw new \RuntimeException('Invalid sigHashType requested');
444
        }
445
446
        if ($sigVersion === SigHash::V1) {
447 174
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
448 66
        } else {
449 33
            $hasher = new Hasher($this->tx);
450 114
        }
451
452
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
453 174
    }
454
455
    /**
456
     * @param int $sigHashType
457
     * @return BufferInterface
458
     */
459
    public function getSigHash($sigHashType)
460 36
    {
461
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
462 36
    }
463
464
    /**
465
     * @param PrivateKeyInterface $key
466
     * @param ScriptInterface $scriptCode
467
     * @param int $sigHashType
468
     * @param int $sigVersion
469
     * @return TransactionSignature
470
     */
471
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
472 168
    {
473
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
474 168
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
475 168
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
476 168
    }
477
478
    /**
479
     * @return bool
480
     */
481
    public function isFullySigned()
482 168
    {
483
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
484 168
    }
485
486
    /**
487
     * @return int
488
     */
489
    public function getRequiredSigs()
490 150
    {
491
        return $this->requiredSigs;
492 150
    }
493
494
    /**
495
     * @return TransactionSignatureInterface[]
496
     */
497
    public function getSignatures()
498 150
    {
499
        return $this->signatures;
500 150
    }
501
502
    /**
503
     * @return PublicKeyInterface[]
504
     */
505
    public function getPublicKeys()
506 150
    {
507
        return $this->publicKeys;
508 150
    }
509
510
    /**
511
     * @param PrivateKeyInterface $key
512
     * @param int $sigHashType
513
     * @return $this
514
     */
515
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
516
    {
517
        if ($this->isFullySigned()) {
518
            return $this;
519 168
        }
520
521 168
        if ($this->signScript->getType() === OutputClassifier::PAYTOPUBKEY) {
522
            if (!$key->getPublicKey()->getBuffer()->equals($this->signScript->getSolution())) {
523
                throw new \RuntimeException('Signing with the wrong private key');
524
            }
525 168
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
526 36
            $this->publicKeys[0] = $key->getPublicKey();
527
            $this->requiredSigs = 1;
528
        } else if ($this->signScript->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
529 36
            if (!$key->getPubKeyHash()->equals($this->signScript->getSolution())) {
530 36
                throw new \RuntimeException('Signing with the wrong private key');
531 36
            }
532 153
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
533 102
            $this->publicKeys[0] = $key->getPublicKey();
534
            $this->requiredSigs = 1;
535
        } else if ($this->signScript->getType() === OutputClassifier::MULTISIG) {
536 102
            $info = new Multisig($this->signScript->getScript());
537 102
            $this->publicKeys = $info->getKeys();
538 102
            $this->requiredSigs = $info->getRequiredSigCount();
539 87
540 36
            $myKey = $key->getPublicKey()->getBuffer();
541 36
            $signed = false;
542 36
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
543
                if ($myKey->equals($publicKey->getBuffer())) {
0 ignored issues
show
Documentation introduced by
$publicKey->getBuffer() is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
544 36
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
545 36
                    $signed = true;
546 36
                }
547 36
            }
548 36
549 36
            if (!$signed) {
550 18
                throw new \RuntimeException('Signing with the wrong private key');
551 18
            }
552
        } else {
553 36
            throw new \RuntimeException('Cannot sign unknown script type');
554 18
        }
555
556 18
        return $this;
557
    }
558
559 168
    /**
560
     * @param int $flags
561
     * @return bool
562
     */
563
    public function verify($flags = null)
564
    {
565
        $consensus = ScriptFactory::consensus();
566 168
567
        if ($flags === null) {
568 168
            $flags = $this->flags;
569 168
        }
570 168
571 60
        $flags |= Interpreter::VERIFY_P2SH;
572 30
        if ($this->sigVersion === 1) {
573
            $flags |= Interpreter::VERIFY_WITNESS;
574 168
        }
575 24
576 24
        $sig = $this->serializeSignatures();
577 159
578 42
        $mutator = TransactionFactory::mutate($this->tx);
579 87
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
580 21
        if ($this->sigVersion === 1) {
581
            $witness = [];
582 168
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
583 168
                if ($i === $this->nInput) {
584 168
                    $witness[] = $sig->getScriptWitness();
585
                } else {
586
                    $witness[] = new ScriptWitness([]);
587
                }
588
            }
589
590
            $mutator->witness($witness);
591
        }
592
593
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
594 150
    }
595
596 150
    /**
597
     * @param string $outputType
598 150
     * @return BufferInterface[]
599 150
     */
600 75
    private function serializeSolution($outputType)
601
    {
602 150
        $result = [];
603 150
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
604 66
            if (count($this->signatures) === 1) {
605 33
                $result = [$this->signatures[0]->getBuffer()];
606
            }
607 150
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
608
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
609 150
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
610 150
            }
611 150
        } else if ($outputType === OutputClassifier::MULTISIG) {
612 66
            $result[] = new Buffer();
613 66
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
614 66
                if (isset($this->signatures[$i])) {
615 66
                    $result[] = $this->signatures[$i]->getBuffer();
616 33
                }
617 6
            }
618
        } else {
619 33
            throw new \RuntimeException('Cannot serialize this script sig');
620
        }
621 66
622 33
        return $result;
623
    }
624 150
625
    /**
626
     * @return SigValues
627
     */
628
    public function serializeSignatures()
629
    {
630
        static $emptyScript = null;
631 168
        static $emptyWitness = null;
632
        if (is_null($emptyScript) || is_null($emptyWitness)) {
633 168
            $emptyScript = new Script();
634 168
            $emptyWitness = new ScriptWitness([]);
635 36
        }
636 36
637 18
        $scriptSigChunks = [];
638 153
        $witness = [];
639 102
        if ($this->scriptPubKey->canSign()) {
640 102
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
641 51
        }
642 87
643 36
        $solution = $this->scriptPubKey;
644 36
        $p2sh = false;
645 36
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
646 36
            $p2sh = true;
647 18
            if ($this->redeemScript->canSign()) {
648 18
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
649 18
            }
650
            $solution = $this->redeemScript;
651
        }
652
653 168
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
654
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
655
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
656
            if ($this->witnessScript->canSign()) {
657
                $witness = $this->serializeSolution($this->witnessScript->getType());
658
                $witness[] = $this->witnessScript->getScript()->getBuffer();
659 168
            }
660
        }
661 168
662 168
        if ($p2sh) {
663 168
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
664 6
        }
665 6
666 3
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
667
    }
668
}
669