Completed
Pull Request — master (#524)
by thomas
40:24 queued 38:13
created

InputSigner::verifySolution()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
11
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
12
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
13
use BitWasp\Bitcoin\Script\Classifier\OutputData;
14
use BitWasp\Bitcoin\Script\Interpreter\BitcoinCashChecker;
15
use BitWasp\Bitcoin\Script\Interpreter\Checker;
16
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
17
use BitWasp\Bitcoin\Script\Interpreter\Stack;
18
use BitWasp\Bitcoin\Script\Opcodes;
19
use BitWasp\Bitcoin\Script\Parser\Operation;
20
use BitWasp\Bitcoin\Script\Path\BranchInterpreter;
21
use BitWasp\Bitcoin\Script\Script;
22
use BitWasp\Bitcoin\Script\ScriptFactory;
23
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
24
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkey;
25
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkeyHash;
26
use BitWasp\Bitcoin\Script\ScriptInterface;
27
use BitWasp\Bitcoin\Script\ScriptType;
28
use BitWasp\Bitcoin\Script\ScriptWitness;
29
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
30
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
31
use BitWasp\Bitcoin\Signature\TransactionSignature;
32
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
33
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
34
use BitWasp\Bitcoin\Transaction\TransactionFactory;
35
use BitWasp\Bitcoin\Transaction\TransactionInterface;
36
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
37
use BitWasp\Buffertools\Buffer;
38
use BitWasp\Buffertools\BufferInterface;
39
40
class InputSigner implements InputSignerInterface
41
{
42
    /**
43
     * @var array
44
     */
45
    protected static $canSign = [
46
        ScriptType::P2PKH,
47
        ScriptType::P2PK,
48
        ScriptType::MULTISIG
49
    ];
50
51
    /**
52
     * @var array
53
     */
54
    protected static $validP2sh = [
55
        ScriptType::P2WKH,
56
        ScriptType::P2WSH,
57
        ScriptType::P2PKH,
58
        ScriptType::P2PK,
59
        ScriptType::MULTISIG
60
    ];
61
62
    /**
63
     * @var EcAdapterInterface
64
     */
65
    private $ecAdapter;
66
67
    /**
68
     * @var OutputData $scriptPubKey
69
     */
70
    private $scriptPubKey;
71
72
    /**
73
     * @var OutputData $redeemScript
74
     */
75
    private $redeemScript;
76
77
    /**
78
     * @var OutputData $witnessScript
79
     */
80
    private $witnessScript;
81
82
    /**
83
     * @var OutputData
84
     */
85
    private $signScript;
86
87
    /**
88
     * @var bool
89
     */
90
    private $tolerateInvalidPublicKey = false;
91
92
    /**
93
     * @var bool
94
     */
95
    private $redeemBitcoinCash = false;
96
97
    /**
98
     * @var bool
99
     */
100
    private $allowComplexScripts = false;
101
102
    /**
103
     * @var SignData
104
     */
105
    private $signData;
106
107
    /**
108
     * @var int
109
     */
110
    private $sigVersion;
111
112
    /**
113
     * @var int
114
     */
115
    private $flags;
116
117
    /**
118
     * @var TransactionInterface
119
     */
120
    private $tx;
121
122
    /**
123
     * @var int
124
     */
125
    private $nInput;
126
127
    /**
128
     * @var TransactionOutputInterface
129
     */
130
    private $txOut;
131
132
    /**
133
     * @var Interpreter
134
     */
135
    private $interpreter;
136
137
    /**
138
     * @var Checker
139
     */
140
    private $signatureChecker;
141
142
    /**
143
     * @var TransactionSignatureSerializer
144
     */
145
    private $txSigSerializer;
146
147
    /**
148
     * @var PublicKeySerializerInterface
149
     */
150
    private $pubKeySerializer;
151
152
    /**
153
     * @var Conditional[]|Checksig[]
154
     */
155
    private $steps = [];
156
157
    /**
158
     * InputSigner constructor.
159
     *
160
     * Note, the implementation of this class is considered internal
161
     * and only the methods exposed on InputSignerInterface should
162
     * be depended on to avoid BC breaks.
163
     *
164
     * The only recommended way to produce this class is using Signer::input()
165
     *
166
     * @param EcAdapterInterface $ecAdapter
167
     * @param TransactionInterface $tx
168
     * @param int $nInput
169
     * @param TransactionOutputInterface $txOut
170
     * @param SignData $signData
171
     * @param TransactionSignatureSerializer|null $sigSerializer
172
     * @param PublicKeySerializerInterface|null $pubKeySerializer
173
     */
174 132
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
175
    {
176 132
        $this->ecAdapter = $ecAdapter;
177 132
        $this->tx = $tx;
178 132
        $this->nInput = $nInput;
179 132
        $this->txOut = $txOut;
180 132
        $this->signData = $signData;
181
182 132
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
183 132
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
184 132
        $this->interpreter = new Interpreter($this->ecAdapter);
185 132
    }
186
187
    /**
188
     * @return InputSigner
189
     */
190 132
    public function extract()
191
    {
192 132
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
193 132
        $checker = new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
194
195 132
        if ($this->redeemBitcoinCash) {
196
            // unset VERIFY_WITNESS default
197 2
            $defaultFlags = $defaultFlags & (~Interpreter::VERIFY_WITNESS);
198
199 2
            if ($this->signData->hasSignaturePolicy()) {
200
                if ($this->signData->getSignaturePolicy() & Interpreter::VERIFY_WITNESS) {
201
                    throw new \RuntimeException("VERIFY_WITNESS is not possible for bitcoin cash");
202
                }
203
            }
204
205 2
            $checker = new BitcoinCashChecker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
206
        }
207
208 132
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
209 132
        $this->signatureChecker = $checker;
210
211 132
        $witnesses = $this->tx->getWitnesses();
212 132
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput]->all() : [];
213
214 132
        return $this->solve(
215 132
            $this->signData,
216 132
            $this->txOut->getScript(),
217 132
            $this->tx->getInput($this->nInput)->getScript(),
218
            $witness
219
        );
220
    }
221
222
    /**
223
     * @param bool $setting
224
     * @return $this
225
     */
226 102
    public function tolerateInvalidPublicKey($setting)
227
    {
228 102
        $this->tolerateInvalidPublicKey = (bool) $setting;
229 102
        return $this;
230
    }
231
232
    /**
233
     * @param bool $setting
234
     * @return $this
235
     */
236 102
    public function redeemBitcoinCash($setting)
237
    {
238 102
        $this->redeemBitcoinCash = (bool) $setting;
239 102
        return $this;
240
    }
241
242
    /**
243
     * @param bool $setting
244
     * @return $this
245
     */
246 102
    public function allowComplexScripts($setting)
247
    {
248 102
        $this->allowComplexScripts = (bool) $setting;
249 102
        return $this;
250
    }
251
252
    /**
253
     * @param BufferInterface $vchPubKey
254
     * @return PublicKeyInterface|null
255
     * @throws \Exception
256
     */
257 100
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
258
    {
259
        try {
260 100
            return $this->pubKeySerializer->parse($vchPubKey);
261 6
        } catch (\Exception $e) {
262 6
            if ($this->tolerateInvalidPublicKey) {
263 2
                return null;
264
            }
265
266 4
            throw $e;
267
        }
268
    }
269
270
    /**
271
     * A snippet from OP_CHECKMULTISIG - links keys to signatures
272
     *
273
     * @param ScriptInterface $script
274
     * @param BufferInterface[] $signatures
275
     * @param BufferInterface[] $publicKeys
276
     * @param int $sigVersion
277
     * @return \SplObjectStorage
278
     */
279 26
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
280
    {
281 26
        $sigCount = count($signatures);
282 26
        $keyCount = count($publicKeys);
283 26
        $ikey = $isig = 0;
284 26
        $fSuccess = true;
285 26
        $result = new \SplObjectStorage;
286
287 26
        while ($fSuccess && $sigCount > 0) {
288
            // Fetch the signature and public key
289 26
            $sig = $signatures[$isig];
290 26
            $pubkey = $publicKeys[$ikey];
291
292 26
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
293 24
                $result[$pubkey] = $sig;
294 24
                $isig++;
295 24
                $sigCount--;
296
            }
297
298 26
            $ikey++;
299 26
            $keyCount--;
300
301
            // If there are more signatures left than keys left,
302
            // then too many signatures have failed. Exit early,
303
            // without checking any further signatures.
304 26
            if ($sigCount > $keyCount) {
305 2
                $fSuccess = false;
306
            }
307
        }
308
309 26
        return $result;
310
    }
311
312
    /**
313
     * @param ScriptInterface $script
314
     * @return \BitWasp\Buffertools\BufferInterface[]
315
     */
316 128
    private function evalPushOnly(ScriptInterface $script)
317
    {
318 128
        $stack = new Stack();
319 128
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
320 128
        return $stack->all();
321
    }
322
323
    /**
324
     * Create a script consisting only of push-data operations.
325
     * Suitable for a scriptSig.
326
     *
327
     * @param BufferInterface[] $buffers
328
     * @return ScriptInterface
329
     */
330
    private function pushAll(array $buffers)
331
    {
332 88
        return ScriptFactory::sequence(array_map(function ($buffer) {
333 80
            if (!($buffer instanceof BufferInterface)) {
334
                throw new \RuntimeException('Script contained a non-push opcode');
335
            }
336
337 80
            $size = $buffer->getSize();
338 80
            if ($size === 0) {
339 30
                return Opcodes::OP_0;
340
            }
341
342 80
            $first = ord($buffer->getBinary());
343 80
            if ($size === 1 && $first >= 1 && $first <= 16) {
344 8
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
345
            } else {
346 80
                return $buffer;
347
            }
348 88
        }, $buffers));
349
    }
350
351
    /**
352
     * Verify a scriptSig / scriptWitness against a scriptPubKey.
353
     * Useful for checking the outcome of certain things, like hash locks (p2sh)
354
     *
355
     * @param int $flags
356
     * @param ScriptInterface $scriptSig
357
     * @param ScriptInterface $scriptPubKey
358
     * @param ScriptWitnessInterface|null $scriptWitness
359
     * @return bool
360
     */
361 26
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
362
    {
363 26
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
364
    }
365
366
    /**
367
     * @param array $decoded
368
     * @param null $solution
369
     * @return null|PayToPubkey|PayToPubkeyHash|Multisig
370
     */
371 108
    private function classifySignStep(array $decoded, &$solution = null)
372
    {
373
        try {
374 108
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
375 36
            $solution = $details->getKeyBuffers();
376 36
            return $details;
377 108
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
378
        }
379
380
        try {
381 108
            $details = PayToPubkey::fromDecodedScript($decoded, true);
382 50
            $solution = $details->getKeyBuffer();
383 50
            return $details;
384 108
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
385
        }
386
387
        try {
388 108
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
389 42
            $solution = $details->getPubKeyHash();
390 42
            return $details;
391 108
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
392
        }
393
394 108
        return null;
395
    }
396
397
    /**
398
     * @param ScriptInterface $script
399
     * @return Checksig[]
400
     */
401 108
    public function parseSequence(ScriptInterface $script)
402
    {
403 108
        $decoded = $script->getScriptParser()->decode();
404
405 108
        $j = 0;
406 108
        $l = count($decoded);
407 108
        $result = [];
408 108
        while ($j < $l) {
409 108
            $step = null;
410 108
            $slice = null;
0 ignored issues
show
Unused Code introduced by
$slice is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
411
412
            // increment the $last, and break if it's valid
413 108
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
414 108
                $slice = array_slice($decoded, $j, $i);
415 108
                $step = $this->classifySignStep($slice, $solution);
416 108
                if ($step !== null) {
417 108
                    break;
418
                }
419
            }
420
421 108
            if (null === $step) {
422
                throw new \RuntimeException("Invalid script");
423
            } else {
424 108
                $j += $i;
425 108
                $result[] = new Checksig($step, $this->txSigSerializer, $this->pubKeySerializer);
426
            }
427
        }
428
429 108
        return $result;
430
    }
431
432
    /**
433
     * @param Operation $operation
434
     * @param Stack $mainStack
435
     * @param bool[] $pathData
436
     * @return Conditional
437
     */
438 22
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData)
439
    {
440 22
        $opValue = null;
0 ignored issues
show
Unused Code introduced by
$opValue is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
441
442 22
        if (!$mainStack->isEmpty()) {
443 22
            if (count($pathData) === 0) {
444
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
445
            }
446
447 22
            $opValue = $this->interpreter->castToBool($mainStack->pop());
0 ignored issues
show
Bug introduced by
It seems like $mainStack->pop() can be null; however, castToBool() 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...
448 22
            $dataValue = array_shift($pathData);
449 22
            if ($opValue !== $dataValue) {
450 22
                throw new \RuntimeException("Current stack doesn't follow branch path");
451
            }
452
        } else {
453 18
            if (count($pathData) === 0) {
454
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
455
            }
456
457 18
            $opValue = array_shift($pathData);
458
        }
459
460 22
        $conditional = new Conditional($operation->getOp());
461
462 22
        if ($opValue !== null) {
463 22
            if (!is_bool($opValue)) {
464
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
465
            }
466
467 22
            $conditional->setValue($opValue);
468
        }
469
470 22
        return $conditional;
471
    }
472
473
    /**
474
     * @param int $idx
475
     * @return Checksig|Conditional
476
     */
477 36
    public function step($idx)
478
    {
479 36
        if (!array_key_exists($idx, $this->steps)) {
480
            throw new \RuntimeException("Out of range index for input sign step");
481
        }
482
483 36
        return $this->steps[$idx];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->steps[$idx]; (BitWasp\Bitcoin\Transaction\Factory\Conditional) is incompatible with the return type declared by the interface BitWasp\Bitcoin\Transact...utSignerInterface::step of type array<BitWasp\Bitcoin\Tr...on\Factory\Conditional>.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
484
    }
485
486
    /**
487
     * @param OutputData $solution
488
     * @param array $sigChunks
489
     * @param SignData $signData
490
     */
491 108
    public function extractScript(OutputData $solution, array $sigChunks, SignData $signData)
492
    {
493 108
        $logicInterpreter = new BranchInterpreter();
494 108
        $tree = $logicInterpreter->getScriptTree($solution->getScript());
495
496 108
        if ($tree->hasMultipleBranches()) {
497 22
            $logicalPath = $signData->getLogicalPath();
498
            // we need a function like findWitnessScript to 'check'
499
            // partial signatures against _our_ path
500
        } else {
501 86
            $logicalPath = [];
502
        }
503
504 108
        $branch = $tree->getBranchByDesc($logicalPath);
505 108
        $segments = $branch->getSegments();
506
507 108
        $vfStack = new Stack();
508 108
        $stack = new Stack($sigChunks);
509
510 108
        $pathCopy = $logicalPath;
511 108
        $steps = [];
512 108
        foreach ($segments as $i => $segment) {
513 108
            $fExec = !$this->interpreter->checkExec($vfStack, false);
514 108
            if ($segment->isLoneLogicalOp()) {
515 22
                $op = $segment[0];
516 22
                switch ($op->getOp()) {
517 22
                    case Opcodes::OP_IF:
518 22
                    case Opcodes::OP_NOTIF:
519 22
                        $value = false;
520 22
                        if ($fExec) {
521
                            // Pop from mainStack if $fExec
522 22
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
523
524
                            // the Conditional has a value in this case:
525 22
                            $value = $step->getValue();
526
527
                            // Connect the last operation (if there is one)
528
                            // with the last step with isRequired==$value
529 22
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
530 8
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
531 8
                                    $step->providedBy($steps[$j]);
532 8
                                    break;
533
                                }
534
                            }
535
                        } else {
536 2
                            $step = new Conditional($op->getOp());
537
                        }
538
539 22
                        $steps[] = $step;
540
541 22
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
542 10
                            $value = !$value;
543
                        }
544
545 22
                        $vfStack->push($value);
546 22
                        break;
547 22
                    case Opcodes::OP_ENDIF;
548 22
                        $vfStack->pop();
549 22
                        break;
550 18
                    case Opcodes::OP_ELSE;
551 18
                        $vfStack->push(!$vfStack->pop());
552 22
                        break;
553
                }
554
            } else {
555 108
                $segmentScript = $segment->makeScript();
556 108
                $templateTypes = $this->parseSequence($segmentScript);
557
558
                // Detect if effect on vfStack is `false`
559 108
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
560 108
                if ($resolvesFalse) {
561 4
                    if (count($templateTypes) > 1) {
562
                        throw new \RuntimeException("Unsupported script, multiple steps to segment which is negated");
563
                    }
564
                }
565
566 108
                foreach ($templateTypes as $k => $checksig) {
567 108
                    if ($fExec) {
568 108
                        $this->extractFromValues($solution->getScript(), $checksig, $stack, $this->sigVersion, $resolvesFalse);
569
570
                        // If this statement results is later consumed
571
                        // by a conditional which would be false, mark
572
                        // this operation as not required
573 98
                        if ($resolvesFalse) {
574 4
                            $checksig->setRequired(false);
575
                        }
576 98
                        $steps[] = $checksig;
577
                    }
578
                }
579
            }
580
        }
581
582 98
        $this->steps = $steps;
583 98
    }
584
585
    /**
586
     * This function is strictly for $canSign types.
587
     * It will extract signatures/publicKeys when given $outputData, and $stack.
588
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
589
     *
590
     * @param ScriptInterface $script
591
     * @param Checksig $checksig
592
     * @param Stack $stack
593
     * @param int $sigVersion
594
     * @param bool $expectFalse
595
     */
596 108
    public function extractFromValues(ScriptInterface $script, Checksig $checksig, Stack $stack, $sigVersion, $expectFalse)
597
    {
598 108
        $size = count($stack);
599
600 108
        if ($checksig->getType() === ScriptType::P2PKH) {
601 42
            if ($size > 1) {
602 38
                $vchPubKey = $stack->pop();
603 38
                $vchSig = $stack->pop();
604
605 38
                $value = false;
606 38
                if (!$expectFalse) {
607 38
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $vchPubKey, $this->sigVersion, $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 603 can be null; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() 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...
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 602 can be null; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() 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...
608 38
                    if (!$value) {
609 2
                        throw new \RuntimeException('Existing signatures are invalid!');
610
                    }
611
                }
612
613 36
                if (!$checksig->isVerify()) {
614 36
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
615
                }
616
617 36
                if ($expectFalse) {
618
                    $checksig->setRequired(false);
619
                } else {
620
                    $checksig
621 36
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 603 can be null; however, BitWasp\Bitcoin\Serializ...tureSerializer::parse() 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...
622 40
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 602 can be null; however, BitWasp\Bitcoin\Transact...r::parseStepPublicKey() 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...
623
                    ;
624
                }
625
            }
626 72
        } else if ($checksig->getType() === ScriptType::P2PK) {
627 50
            if ($size > 0) {
628 46
                $vchSig = $stack->pop();
629
630 46
                $value = false;
631 46
                if (!$expectFalse) {
632 46
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $checksig->getSolution(), $this->sigVersion, $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 628 can be null; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() 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...
Bug introduced by
It seems like $checksig->getSolution() targeting BitWasp\Bitcoin\Transact...Checksig::getSolution() can also be of type array<integer,object<Bit...tools\BufferInterface>>; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() does only seem to accept object<BitWasp\Buffertools\BufferInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
633 46
                    if (!$value) {
634 2
                        throw new \RuntimeException('Existing signatures are invalid!');
635
                    }
636
                }
637
638 44
                if (!$checksig->isVerify()) {
639 40
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
640
                }
641
642 44
                if ($expectFalse) {
643 2
                    $checksig->setRequired(false);
644
                } else {
645 44
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 628 can be null; however, BitWasp\Bitcoin\Serializ...tureSerializer::parse() 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...
646
                }
647
            }
648 48
            $checksig->setKey(0, $this->parseStepPublicKey($checksig->getSolution()));
0 ignored issues
show
Bug introduced by
It seems like $checksig->getSolution() targeting BitWasp\Bitcoin\Transact...Checksig::getSolution() can also be of type array<integer,object<Bit...tools\BufferInterface>>; however, BitWasp\Bitcoin\Transact...r::parseStepPublicKey() does only seem to accept object<BitWasp\Buffertools\BufferInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
649 36
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
650
            /** @var Multisig $info */
651 36
            $info = $checksig->getInfo();
652 36
            $keyBuffers = $info->getKeyBuffers();
653 36
            foreach ($keyBuffers as $idx => $keyBuf) {
654 36
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
655
            }
656
657 32
            $value = !$expectFalse;
658 32
            if ($size > 1) {
659
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
660 28
                $sigBufs = [];
661 28
                $max = min($checksig->getRequiredSigs(), $size - 1);
662 28
                for ($i = 0; $i < $max; $i++) {
663 28
                    $vchSig = $stack[-1-$i];
664 28
                    $sigBufs[] = $vchSig;
665
                }
666
667 28
                $sigBufs = array_reverse($sigBufs);
668 28
                $sigBufCount = count($sigBufs);
669
670 28
                if (!$expectFalse) {
671 26
                    $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
672
673
                    // Here we learn if any signatures were invalid, it won't be in the map.
674 26
                    if ($sigBufCount !== count($keyToSigMap)) {
675 2
                        throw new \RuntimeException('Existing signatures are invalid!');
676
                    }
677
678 24
                    $toDelete = count($keyToSigMap);
679
                } else {
680
                    // should check that all signatures are zero
681 2
                    $keyToSigMap = new \SplObjectStorage();
682 2
                    $toDelete = $info->getRequiredSigCount();
683
                }
684
685 26
                $toDelete = 1 + $toDelete;
686 26
                while ($toDelete--) {
687 26
                    $stack->pop();
688
                }
689
690 26
                foreach ($keyBuffers as $idx => $key) {
691 26
                    if (isset($keyToSigMap[$key])) {
692 26
                        $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
693
                    }
694
                }
695
            }
696
697 30
            if (!$checksig->isVerify()) {
698 30
                $stack->push($value ? new Buffer("\x01") : new Buffer());
699
            }
700
        } else {
701
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
702
        }
703 98
    }
704
705
    /**
706
     * Checks $chunks (a decompiled scriptSig) for it's last element,
707
     * or defers to SignData. If both are provided, it checks the
708
     * value from $chunks against SignData.
709
     *
710
     * @param BufferInterface[] $chunks
711
     * @param SignData $signData
712
     * @return ScriptInterface
713
     */
714 30
    private function findRedeemScript(array $chunks, SignData $signData)
715
    {
716 30
        if (count($chunks) > 0) {
717 22
            $redeemScript = new Script($chunks[count($chunks) - 1]);
718 22
            if ($signData->hasRedeemScript()) {
719 22
                if (!$redeemScript->equals($signData->getRedeemScript())) {
720 22
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
721
                }
722
            }
723
        } else {
724 26
            if (!$signData->hasRedeemScript()) {
725 2
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
726
            }
727 24
            $redeemScript = $signData->getRedeemScript();
728
        }
729
730 26
        return $redeemScript;
731
    }
732
733
    /**
734
     * Checks $witness (a witness structure) for it's last element,
735
     * or defers to SignData. If both are provided, it checks the
736
     * value from $chunks against SignData.
737
     *
738
     * @param BufferInterface[] $witness
739
     * @param SignData $signData
740
     * @return ScriptInterface
741
     */
742 26
    private function findWitnessScript(array $witness, SignData $signData)
743
    {
744 26
        if (count($witness) > 0) {
745 18
            $witnessScript = new Script($witness[count($witness) - 1]);
746 18
            if ($signData->hasWitnessScript()) {
747 18
                if (!$witnessScript->equals($signData->getWitnessScript())) {
748 18
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
749
                }
750
            }
751
        } else {
752 22
            if (!$signData->hasWitnessScript()) {
753 4
                throw new \RuntimeException('Witness script not provided in sign data or witness');
754
            }
755 18
            $witnessScript = $signData->getWitnessScript();
756
        }
757
758 18
        return $witnessScript;
759
    }
760
761
    /**
762
     * Needs to be called before using the instance. By `extract`.
763
     *
764
     * It ensures that violating the following prevents instance creation
765
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
766
     *  - the P2SH script covers signable types and P2WSH/P2WKH
767
     *  - the witnessScript covers signable types only
768
     *
769
     * @param SignData $signData
770
     * @param ScriptInterface $scriptPubKey
771
     * @param ScriptInterface $scriptSig
772
     * @param BufferInterface[] $witness
773
     * @return $this
774
     */
775 130
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
776
    {
777 130
        $classifier = new OutputClassifier();
778 130
        $sigVersion = SigHash::V0;
779 130
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
780
781 130
        if (!$this->allowComplexScripts) {
782 94
            if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
783 2
                throw new \RuntimeException('scriptPubKey not supported');
784
            }
785
        }
786
787 128
        $sigChunks = $this->evalPushOnly($scriptSig);
788
789 128
        if ($solution->getType() === ScriptType::P2SH) {
790 30
            $redeemScript = $this->findRedeemScript($sigChunks, $signData);
791 26
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
792 2
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
793
            }
794
795 24
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
796 24
            if (!$this->allowComplexScripts) {
797 24
                if (!in_array($solution->getType(), self::$validP2sh)) {
798 2
                    throw new \RuntimeException('Unsupported pay-to-script-hash script');
799
                }
800
            }
801
802 22
            $sigChunks = array_slice($sigChunks, 0, -1);
803
        }
804
805 120
        if ($solution->getType() === ScriptType::P2WKH) {
806 8
            $sigVersion = SigHash::V1;
807 8
            $solution = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
808 8
            $sigChunks = $witness;
809 114
        } else if ($solution->getType() === ScriptType::P2WSH) {
810 26
            $sigVersion = SigHash::V1;
811 26
            $witnessScript = $this->findWitnessScript($witness, $signData);
812
813
            // Essentially all the reference implementation does
814 18
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
815 2
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
816
            }
817
818 16
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
819 16
            if (!$this->allowComplexScripts) {
820 16
                if (!in_array($this->witnessScript->getType(), self::$canSign)) {
821 2
                    throw new \RuntimeException('Unsupported witness-script-hash script');
822
                }
823
            }
824
825 14
            $sigChunks = array_slice($witness, 0, -1);
826
        }
827
828 108
        $this->sigVersion = $sigVersion;
829 108
        $this->signScript = $solution;
830
831 108
        $this->extractScript($solution, $sigChunks, $signData);
832
833 98
        return $this;
834
    }
835
836
    /**
837
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
838
     *
839
     * @param ScriptInterface $scriptCode
840
     * @param int $sigHashType
841
     * @param int $sigVersion
842
     * @return BufferInterface
843
     */
844 90
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
845
    {
846 90
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
847 2
            throw new \RuntimeException('Invalid sigHashType requested');
848
        }
849
850 88
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
851
    }
852
853
    /**
854
     * Calculates the signature hash for the input for the given $sigHashType.
855
     *
856
     * @param int $sigHashType
857
     * @return BufferInterface
858
     */
859 2
    public function getSigHash($sigHashType)
860
    {
861 2
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
862
    }
863
864
    /**
865
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
866
     *
867
     * @param PrivateKeyInterface $key
868
     * @param ScriptInterface $scriptCode
869
     * @param int $sigHashType
870
     * @param int $sigVersion
871
     * @return TransactionSignatureInterface
872
     */
873 88
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
874
    {
875 88
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
876 88
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
877 88
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
878
    }
879
880
    /**
881
     * Returns whether all required signatures have been provided.
882
     *
883
     * @return bool
884
     */
885 50
    public function isFullySigned()
886
    {
887 50
        foreach ($this->steps as $step) {
888 50
            if ($step instanceof Conditional) {
889
                if (!$step->hasValue()) {
890
                    return false;
891
                }
892 50
            } else if ($step instanceof Checksig) {
893 50
                if (!$step->isFullySigned()) {
894 50
                    return false;
895
                }
896
            }
897
        }
898
899 50
        return true;
900
    }
901
902
    /**
903
     * Returns the required number of signatures for this input.
904
     *
905
     * @return int
906
     */
907 50
    public function getRequiredSigs()
908
    {
909 50
        $count = 0;
910 50
        foreach ($this->steps as $step) {
911 50
            if ($step instanceof Checksig) {
912 50
                $count += $step->getRequiredSigs();
913
            }
914
        }
915 50
        return $count;
916
    }
917
918
    /**
919
     * Returns an array where the values are either null,
920
     * or a TransactionSignatureInterface.
921
     *
922
     * @return TransactionSignatureInterface[]
923
     */
924 50
    public function getSignatures()
925
    {
926 50
        return $this->steps[0]->getSignatures();
0 ignored issues
show
Bug introduced by
The method getSignatures() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
927
    }
928
929
    /**
930
     * Returns an array where the values are either null,
931
     * or a PublicKeyInterface.
932
     *
933
     * @return PublicKeyInterface[]
934
     */
935 52
    public function getPublicKeys()
936
    {
937 52
        return $this->steps[0]->getKeys();
0 ignored issues
show
Bug introduced by
The method getKeys() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
938
    }
939
940
    /**
941
     * OutputData for the script to be signed (will be
942
     * equal to getScriptPubKey, or getRedeemScript, or
943
     * getWitnessScript.
944
     *
945
     * @return OutputData
946
     */
947 50
    public function getSignScript()
948
    {
949 50
        return $this->signScript;
950
    }
951
952
    /**
953
     * OutputData for the txOut script.
954
     *
955
     * @return OutputData
956
     */
957 24
    public function getScriptPubKey()
958
    {
959 24
        return $this->scriptPubKey;
960
    }
961
962
    /**
963
     * Returns OutputData for the P2SH redeemScript.
964
     *
965
     * @return OutputData
966
     */
967 18
    public function getRedeemScript()
968
    {
969 18
        if (null === $this->redeemScript) {
970
            throw new \RuntimeException("Input has no redeemScript, cannot call getRedeemScript");
971
        }
972
973 18
        return $this->redeemScript;
974
    }
975
976
    /**
977
     * Returns OutputData for the P2WSH witnessScript.
978
     *
979
     * @return OutputData
980
     */
981 14
    public function getWitnessScript()
982
    {
983 14
        if (null === $this->witnessScript) {
984
            throw new \RuntimeException("Input has no witnessScript, cannot call getWitnessScript");
985
        }
986
987 14
        return $this->witnessScript;
988
    }
989
990
    /**
991
     * Returns whether the scriptPubKey is P2SH.
992
     *
993
     * @return bool
994
     */
995 50
    public function isP2SH()
996
    {
997 50
        if ($this->scriptPubKey->getType() === ScriptType::P2SH && ($this->redeemScript instanceof OutputData)) {
998 18
            return true;
999
        }
1000
1001 32
        return false;
1002
    }
1003
1004
    /**
1005
     * Returns whether the scriptPubKey or redeemScript is P2WSH.
1006
     *
1007
     * @return bool
1008
     */
1009 50
    public function isP2WSH()
1010
    {
1011 50
        if ($this->redeemScript instanceof OutputData) {
1012 18
            if ($this->redeemScript->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1013 8
                return true;
1014
            }
1015
        }
1016
1017 42
        if ($this->scriptPubKey->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1018 6
            return true;
1019
        }
1020
1021 36
        return false;
1022
    }
1023
1024
    /**
1025
     * @param int $stepIdx
1026
     * @param PrivateKeyInterface $privateKey
1027
     * @param int $sigHashType
1028
     * @return $this
1029
     */
1030 94
    public function signStep($stepIdx, PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1031
    {
1032 94
        if (!array_key_exists($stepIdx, $this->steps)) {
1033
            throw new \RuntimeException("Unknown step index");
1034
        }
1035
1036 94
        $checksig = $this->steps[$stepIdx];
1037 94
        if ($checksig->isFullySigned()) {
0 ignored issues
show
Bug introduced by
The method isFullySigned() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1038
            return $this;
1039
        }
1040
1041 94
        if (SigHash::V1 === $this->sigVersion && !$privateKey->isCompressed()) {
1042
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
1043
        }
1044
1045 94
        if ($checksig->getType() === ScriptType::P2PK) {
0 ignored issues
show
Bug introduced by
The method getType() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1046 48
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
0 ignored issues
show
Bug introduced by
The method getSolution() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1047 2
                throw new \RuntimeException('Signing with the wrong private key');
1048
            }
1049
1050 46
            if (!$checksig->hasSignature(0)) {
0 ignored issues
show
Bug introduced by
The method hasSignature() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1051 46
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1052 46
                $checksig->setSignature(0, $signature);
0 ignored issues
show
Bug introduced by
The method setSignature() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1053
            }
1054 60
        } else if ($checksig->getType() === ScriptType::P2PKH) {
0 ignored issues
show
Bug introduced by
The method getType() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1055 38
            $publicKey = $privateKey->getPublicKey();
1056 38
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
0 ignored issues
show
Bug introduced by
The method getSolution() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1057 2
                throw new \RuntimeException('Signing with the wrong private key');
1058
            }
1059
1060 36
            if (!$checksig->hasSignature(0)) {
0 ignored issues
show
Bug introduced by
The method hasSignature() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1061 36
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1062 36
                $checksig->setSignature(0, $signature);
0 ignored issues
show
Bug introduced by
The method setSignature() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1063
            }
1064
1065 36
            if (!$checksig->hasKey(0)) {
0 ignored issues
show
Bug introduced by
The method hasKey() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1066 36
                $checksig->setKey(0, $publicKey);
0 ignored issues
show
Bug introduced by
The method setKey() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1067
            }
1068 26
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
0 ignored issues
show
Bug introduced by
The method getType() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1069 26
            $signed = false;
1070 26
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
0 ignored issues
show
Bug introduced by
The method getKeys() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1071 26
                if (!$checksig->hasSignature($keyIdx)) {
0 ignored issues
show
Bug introduced by
The method hasSignature() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1072 26
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
1073 24
                        $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1074 24
                        $checksig->setSignature($keyIdx, $signature);
0 ignored issues
show
Bug introduced by
The method setSignature() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1075 26
                        $signed = true;
1076
                    }
1077
                }
1078
            }
1079
1080 26
            if (!$signed) {
1081 26
                throw new \RuntimeException('Signing with the wrong private key');
1082
            }
1083
        } else {
1084
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1085
        }
1086
1087 88
        return $this;
1088
    }
1089
1090
    /**
1091
     * Sign the input using $key and $sigHashTypes
1092
     *
1093
     * @param PrivateKeyInterface $privateKey
1094
     * @param int $sigHashType
1095
     * @return $this
1096
     */
1097 58
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1098
    {
1099 58
        return $this->signStep(0, $privateKey, $sigHashType);
1100
    }
1101
1102
    /**
1103
     * Verifies the input using $flags for script verification
1104
     *
1105
     * @param int $flags
1106
     * @return bool
1107
     */
1108 86
    public function verify($flags = null)
1109
    {
1110 86
        $consensus = ScriptFactory::consensus();
1111
1112 86
        if ($flags === null) {
1113 50
            $flags = $this->flags;
1114
        }
1115
1116 86
        $flags |= Interpreter::VERIFY_P2SH;
1117 86
        if (SigHash::V1 === $this->sigVersion) {
1118 22
            $flags |= Interpreter::VERIFY_WITNESS;
1119
        }
1120
1121 86
        $sig = $this->serializeSignatures();
1122
1123
        // Take serialized signatures, and use mutator to add this inputs sig data
1124 86
        $mutator = TransactionFactory::mutate($this->tx);
1125 86
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1126
1127 86
        if (SigHash::V1 === $this->sigVersion) {
1128 22
            $witness = [];
1129 22
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1130 22
                if ($i === $this->nInput) {
1131 22
                    $witness[] = $sig->getScriptWitness();
1132
                } else {
1133 2
                    $witness[] = new ScriptWitness([]);
1134
                }
1135
            }
1136
1137 22
            $mutator->witness($witness);
1138
        }
1139
1140 86
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1141
    }
1142
1143
    /**
1144
     * @return array
1145
     */
1146 88
    private function serializeSteps()
1147
    {
1148 88
        $results = [];
1149 88
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1150 88
            $step = $this->steps[$i];
1151 88
            if ($step instanceof Conditional) {
1152 22
                $results[] = $step->serialize();
1153 88
            } else if ($step instanceof Checksig) {
1154 88
                if ($step->isRequired()) {
1155 88
                    if (count($step->getSignatures()) === 0) {
1156 50
                        break;
1157
                    }
1158
                }
1159
1160 88
                $results[] = $step->serialize();
1161
1162 88
                if (!$step->isFullySigned()) {
1163
                    break;
1164
                }
1165
            }
1166
        }
1167
1168 88
        $values = [];
1169 88
        foreach (array_reverse($results) as $v) {
1170 88
            foreach ($v as $value) {
1171 88
                $values[] = $value;
1172
            }
1173
        }
1174
1175 88
        return $values;
1176
    }
1177
1178
    /**
1179
     * Produces a SigValues instance containing the scriptSig & script witness
1180
     *
1181
     * @return SigValues
1182
     */
1183 88
    public function serializeSignatures()
1184
    {
1185 88
        static $emptyScript = null;
1186 88
        static $emptyWitness = null;
1187 88
        if (is_null($emptyScript) || is_null($emptyWitness)) {
1188 2
            $emptyScript = new Script();
1189 2
            $emptyWitness = new ScriptWitness([]);
1190
        }
1191
1192 88
        $witness = [];
1193 88
        $scriptSigChunks = $this->serializeSteps();
1194
1195 88
        $solution = $this->scriptPubKey;
1196 88
        $p2sh = false;
1197 88
        if ($solution->getType() === ScriptType::P2SH) {
1198 18
            $p2sh = true;
1199 18
            $solution = $this->redeemScript;
1200
        }
1201
1202 88
        if ($solution->getType() === ScriptType::P2WKH) {
1203 8
            $witness = $scriptSigChunks;
1204 8
            $scriptSigChunks = [];
1205 82
        } else if ($solution->getType() === ScriptType::P2WSH) {
1206 14
            $witness = $scriptSigChunks;
1207 14
            $witness[] = $this->witnessScript->getScript()->getBuffer();
1208 14
            $scriptSigChunks = [];
1209
        }
1210
1211 88
        if ($p2sh) {
1212 18
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
1213
        }
1214
1215 88
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
1216
    }
1217
1218 50
    public function getSteps()
1219
    {
1220 50
        return $this->steps;
1221
    }
1222
}
1223