Completed
Pull Request — master (#593)
by thomas
15:02
created

InputSigner::__construct()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 24
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 20
nc 8
nop 8
dl 0
loc 24
ccs 13
cts 13
cp 1
crap 4
rs 8.6845
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Transaction\Factory;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
12
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
13
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
14
use BitWasp\Bitcoin\Exceptions\SignerException;
15
use BitWasp\Bitcoin\Exceptions\UnsupportedScript;
16
use BitWasp\Bitcoin\Locktime;
17
use BitWasp\Bitcoin\Script\Classifier\OutputData;
18
use BitWasp\Bitcoin\Script\FullyQualifiedScript;
19
use BitWasp\Bitcoin\Script\Interpreter\Checker;
20
use BitWasp\Bitcoin\Script\Interpreter\CheckerBase;
21
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
22
use BitWasp\Bitcoin\Script\Interpreter\Number;
23
use BitWasp\Bitcoin\Script\Interpreter\Stack;
24
use BitWasp\Bitcoin\Script\Opcodes;
25
use BitWasp\Bitcoin\Script\Parser\Operation;
26
use BitWasp\Bitcoin\Script\Path\BranchInterpreter;
27
use BitWasp\Bitcoin\Script\ScriptFactory;
28
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
29
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkey;
30
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkeyHash;
31
use BitWasp\Bitcoin\Script\ScriptInterface;
32
use BitWasp\Bitcoin\Script\ScriptType;
33
use BitWasp\Bitcoin\Script\ScriptWitness;
34
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
35
use BitWasp\Bitcoin\Signature\TransactionSignature;
36
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
37
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckLocktimeVerify;
38
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckSequenceVerify;
39
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
40
use BitWasp\Bitcoin\Transaction\TransactionFactory;
41
use BitWasp\Bitcoin\Transaction\TransactionInput;
42
use BitWasp\Bitcoin\Transaction\TransactionInterface;
43
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
44
use BitWasp\Buffertools\Buffer;
45
use BitWasp\Buffertools\BufferInterface;
46
47
class InputSigner implements InputSignerInterface
48
{
49
    /**
50
     * @var array
51
     */
52
    protected static $canSign = [
53
        ScriptType::P2PKH,
54
        ScriptType::P2PK,
55
        ScriptType::MULTISIG
56
    ];
57
58
    /**
59
     * @var array
60
     */
61
    protected static $validP2sh = [
62
        ScriptType::P2WKH,
63
        ScriptType::P2WSH,
64
        ScriptType::P2PKH,
65
        ScriptType::P2PK,
66
        ScriptType::MULTISIG
67
    ];
68
69
    /**
70
     * @var EcAdapterInterface
71
     */
72
    private $ecAdapter;
73
74
    /**
75
     * @var FullyQualifiedScript
76
     */
77
    private $fqs;
78
79
    /**
80
     * @var bool
81
     */
82
    private $padUnsignedMultisigs = false;
83
84
    /**
85
     * @var bool
86
     */
87
    private $tolerateInvalidPublicKey = false;
88
89
    /**
90
     * @var bool
91
     */
92
    private $allowComplexScripts = false;
93
94
    /**
95
     * @var SignData
96
     */
97
    private $signData;
98
99
    /**
100
     * @var int
101
     */
102
    private $flags;
103
104
    /**
105
     * @var TransactionInterface
106
     */
107
    private $tx;
108
109
    /**
110
     * @var int
111
     */
112
    private $nInput;
113
114
    /**
115
     * @var TransactionOutputInterface
116
     */
117
    private $txOut;
118
119
    /**
120
     * @var Interpreter
121
     */
122
    private $interpreter;
123
124
    /**
125
     * @var Checker
126
     */
127
    private $signatureChecker;
128
129
    /**
130
     * @var TransactionSignatureSerializer
131
     */
132
    private $txSigSerializer;
133
134
    /**
135
     * @var PublicKeySerializerInterface
136
     */
137
    private $pubKeySerializer;
138
139
    /**
140
     * @var Conditional[]|Checksig[]
141
     */
142
    private $steps = [];
143
144
    /**
145
     * InputSigner constructor.
146
     *
147
     * Note, the implementation of this class is considered internal
148
     * and only the methods exposed on InputSignerInterface should
149
     * be depended on to avoid BC breaks.
150
     *
151
     * The only recommended way to produce this class is using Signer::input()
152
     *
153
     * @param EcAdapterInterface $ecAdapter
154
     * @param TransactionInterface $tx
155
     * @param int $nInput
156
     * @param TransactionOutputInterface $txOut
157
     * @param SignData $signData
158
     * @param CheckerBase $checker
159
     * @param TransactionSignatureSerializer|null $sigSerializer
160
     * @param PublicKeySerializerInterface|null $pubKeySerializer
161
     */
162 15
    public function __construct(
163
        EcAdapterInterface $ecAdapter,
164
        TransactionInterface $tx,
165
        int $nInput,
166
        TransactionOutputInterface $txOut,
167
        SignData $signData,
168
        CheckerBase $checker,
169
        TransactionSignatureSerializer $sigSerializer = null,
170
        PublicKeySerializerInterface $pubKeySerializer = null
171
    ) {
172 15
        $this->ecAdapter = $ecAdapter;
173 15
        $this->tx = $tx;
174 15
        $this->nInput = $nInput;
175 15
        $this->txOut = $txOut;
176 15
        $this->signData = $signData;
177
178 15
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
179 15
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
180
181 15
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
182 15
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
183 15
        $this->interpreter = new Interpreter($this->ecAdapter);
184 15
        $this->signatureChecker = $checker;
0 ignored issues
show
Documentation Bug introduced by
$checker is of type object<BitWasp\Bitcoin\S...nterpreter\CheckerBase>, but the property $signatureChecker was declared to be of type object<BitWasp\Bitcoin\S...pt\Interpreter\Checker>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
185 15
    }
186
187
    /**
188
     * Ensures a FullyQualifiedScript will be accepted
189
     * by the InputSigner.
190
     *
191
     * @param FullyQualifiedScript $script
192
     */
193 6
    public static function ensureAcceptableScripts(FullyQualifiedScript $script)
194
    {
195 6
        $spkType = $script->scriptPubKey()->getType();
196
197 6
        if ($spkType !== ScriptType::P2SH) {
198 5
            if (!in_array($spkType, self::$validP2sh)) {
199 1
                throw new UnsupportedScript("scriptPubKey not supported");
200
            }
201 4
            $hasWitnessScript = $spkType === ScriptType::P2WSH;
202
        } else {
203 1
            $rsType = $script->redeemScript()->getType();
204 1
            if (!in_array($rsType, self::$validP2sh)) {
205 1
                throw new UnsupportedScript("Unsupported pay-to-script-hash script");
206
            }
207
            $hasWitnessScript = $rsType === ScriptType::P2WSH;
208
        }
209
210 4
        if ($hasWitnessScript) {
211 1
            $wsType = $script->witnessScript()->getType();
212 1
            if (!in_array($wsType, self::$canSign)) {
213 1
                throw new UnsupportedScript('Unsupported witness-script-hash script');
214
            }
215
        }
216 3
    }
217
218
    /**
219
     *  It ensures that violating the following prevents instance creation
220
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
221
     *  - the P2SH script covers signable types and P2WSH/P2WKH
222
     *  - the witnessScript covers signable types only
223
     * @return $this|InputSigner
224
     * @throws ScriptRuntimeException
225
     * @throws SignerException
226
     * @throws \Exception
227
     */
228 15
    public function extract()
229
    {
230 15
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
231 14
        $witnesses = $this->tx->getWitnesses();
232 14
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput] : new ScriptWitness();
233
234 14
        $fqs = FullyQualifiedScript::fromTxData($this->txOut->getScript(), $scriptSig, $witness, $this->signData);
235 6
        if (!$this->allowComplexScripts) {
236 6
            self::ensureAcceptableScripts($fqs);
237
        }
238
239 3
        $this->fqs = $fqs;
240 3
        $this->steps = $this->extractScript(
241 3
            $this->fqs->signScript(),
242 3
            $this->fqs->extractStack($scriptSig, $witness),
243 3
            $this->signData
244
        );
245
246
        return $this;
247
    }
248
249
    /**
250
     * @param bool $setting
251
     * @return $this
252
     */
253
    public function padUnsignedMultisigs(bool $setting)
254
    {
255
        $this->padUnsignedMultisigs = $setting;
256
        return $this;
257
    }
258
259
    /**
260
     * @param bool $setting
261
     * @return $this
262
     */
263
    public function tolerateInvalidPublicKey(bool $setting)
264
    {
265
        $this->tolerateInvalidPublicKey = $setting;
266
        return $this;
267
    }
268
269
    /**
270
     * @param bool $setting
271
     * @return $this
272
     */
273
    public function allowComplexScripts(bool $setting)
274
    {
275
        $this->allowComplexScripts = $setting;
276
        return $this;
277
    }
278
279
    /**
280
     * @param BufferInterface $vchPubKey
281
     * @return PublicKeyInterface|null
282
     * @throws \Exception
283
     */
284 1
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
285
    {
286
        try {
287 1
            return $this->pubKeySerializer->parse($vchPubKey);
288
        } catch (\Exception $e) {
289
            if ($this->tolerateInvalidPublicKey) {
290
                return null;
291
            }
292
293
            throw $e;
294
        }
295
    }
296
297
    /**
298
     * @param ScriptInterface $script
299
     * @param BufferInterface[] $signatures
300
     * @param BufferInterface[] $publicKeys
301
     * @param int $sigVersion
302
     * @return \SplObjectStorage
303
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
304
     */
305 1
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, int $sigVersion): \SplObjectStorage
306
    {
307 1
        $sigCount = count($signatures);
308 1
        $keyCount = count($publicKeys);
309 1
        $ikey = $isig = 0;
310 1
        $fSuccess = true;
311 1
        $result = new \SplObjectStorage;
312
313 1
        while ($fSuccess && $sigCount > 0) {
314
            // Fetch the signature and public key
315 1
            $sig = $signatures[$isig];
316 1
            $pubkey = $publicKeys[$ikey];
317
318 1
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
319
                $result[$pubkey] = $sig;
320
                $isig++;
321
                $sigCount--;
322
            }
323
324 1
            $ikey++;
325 1
            $keyCount--;
326
327
            // If there are more signatures left than keys left,
328
            // then too many signatures have failed. Exit early,
329
            // without checking any further signatures.
330 1
            if ($sigCount > $keyCount) {
331 1
                $fSuccess = false;
332
            }
333
        }
334
335 1
        return $result;
336
    }
337
338
    /**
339
     * @param array $decoded
340
     * @param null $solution
341
     * @return null|TimeLock|Checksig
342
     */
343 3
    private function classifySignStep(array $decoded, &$solution = null)
344
    {
345
        try {
346 3
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
347 1
            $solution = $details->getKeyBuffers();
348 1
            return new Checksig($details);
349 3
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
350
        }
351
352
        try {
353 3
            $details = PayToPubkey::fromDecodedScript($decoded, true);
354 1
            $solution = $details->getKeyBuffer();
355 1
            return new Checksig($details);
356 3
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
357
        }
358
359
        try {
360 3
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
361 1
            $solution = $details->getPubKeyHash();
362 1
            return new Checksig($details);
363 3
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
364
        }
365
366
        try {
367 3
            $details = CheckLocktimeVerify::fromDecodedScript($decoded);
368
            return new TimeLock($details);
369 3
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
370
        }
371
372
        try {
373 3
            $details = CheckSequenceVerify::fromDecodedScript($decoded);
374
            return new TimeLock($details);
375 3
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
376
        }
377
378 3
        return null;
379
    }
380
381
    /**
382
     * @param Operation[] $scriptOps
383
     * @return Checksig[]
384
     */
385 3
    public function parseSequence(array $scriptOps)
386
    {
387 3
        $j = 0;
388 3
        $l = count($scriptOps);
389 3
        $result = [];
390 3
        while ($j < $l) {
391 3
            $step = null;
392 3
            $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...
393
394
            // increment the $last, and break if it's valid
395 3
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
396 3
                $slice = array_slice($scriptOps, $j, $i);
397 3
                $step = $this->classifySignStep($slice, $solution);
398 3
                if ($step !== null) {
399 3
                    break;
400
                }
401
            }
402
403 3
            if (null === $step) {
404
                throw new \RuntimeException("Invalid script");
405
            } else {
406 3
                $j += $i;
407 3
                $result[] = $step;
408
            }
409
        }
410
411 3
        return $result;
412
    }
413
414
    /**
415
     * @param Operation $operation
416
     * @param Stack $mainStack
417
     * @param bool[] $pathData
418
     * @return Conditional
419
     */
420
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData): Conditional
421
    {
422
        $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...
423
424
        if (!$mainStack->isEmpty()) {
425
            if (count($pathData) === 0) {
426
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
427
            }
428
429
            $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...
430
            $dataValue = array_shift($pathData);
431
            if ($opValue !== $dataValue) {
432
                throw new \RuntimeException("Current stack doesn't follow branch path");
433
            }
434
        } else {
435
            if (count($pathData) === 0) {
436
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
437
            }
438
439
            $opValue = array_shift($pathData);
440
        }
441
442
        $conditional = new Conditional($operation->getOp());
443
444
        if ($opValue !== null) {
445
            if (!is_bool($opValue)) {
446
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
447
            }
448
449
            $conditional->setValue($opValue);
450
        }
451
452
        return $conditional;
453
    }
454
455
    /**
456
     * @param int $idx
457
     * @return Checksig|Conditional
458
     */
459
    public function step(int $idx)
460
    {
461
        if (!array_key_exists($idx, $this->steps)) {
462
            throw new \RuntimeException("Out of range index for input sign step");
463
        }
464
465
        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...
466
    }
467
468
    /**
469
     * @param OutputData $signScript
470
     * @param Stack $stack
471
     * @param SignData $signData
472
     * @return array
473
     * @throws ScriptRuntimeException
474
     * @throws SignerException
475
     * @throws \Exception
476
     */
477 3
    public function extractScript(OutputData $signScript, Stack $stack, SignData $signData): array
478
    {
479 3
        $logicInterpreter = new BranchInterpreter();
480 3
        $tree = $logicInterpreter->getScriptTree($signScript->getScript());
481
482 3
        if ($tree->hasMultipleBranches()) {
483
            $logicalPath = $signData->getLogicalPath();
484
            // we need a function like findWitnessScript to 'check'
485
            // partial signatures against _our_ path
486
        } else {
487 3
            $logicalPath = [];
488
        }
489
490
        $scriptSections = $tree
491 3
            ->getBranchByPath($logicalPath)
492 3
            ->getScriptSections();
493
494 3
        $vfStack = new Stack();
495
496 3
        $pathCopy = $logicalPath;
497 3
        $steps = [];
498 3
        foreach ($scriptSections as $i => $scriptSection) {
499
            /** @var Operation[] $scriptSection */
500 3
            $fExec = !$this->interpreter->checkExec($vfStack, false);
501 3
            if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) {
502
                $op = $scriptSection[0];
503
                switch ($op->getOp()) {
504
                    case Opcodes::OP_IF:
505
                    case Opcodes::OP_NOTIF:
506
                        $value = false;
507
                        if ($fExec) {
508
                            // Pop from mainStack if $fExec
509
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
510
511
                            // the Conditional has a value in this case:
512
                            $value = $step->getValue();
513
514
                            // Connect the last operation (if there is one)
515
                            // with the last step with isRequired==$value
516
                            // todo: check this part out..
517
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
518
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
519
                                    $step->providedBy($steps[$j]);
520
                                    break;
521
                                }
522
                            }
523
                        } else {
524
                            $step = new Conditional($op->getOp());
525
                        }
526
527
                        $steps[] = $step;
528
529
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
530
                            $value = !$value;
531
                        }
532
533
                        $vfStack->push($value);
534
                        break;
535
                    case Opcodes::OP_ENDIF:
536
                        $vfStack->pop();
537
                        break;
538
                    case Opcodes::OP_ELSE:
539
                        $vfStack->push(!$vfStack->pop());
540
                        break;
541
                }
542
            } else {
543 3
                $templateTypes = $this->parseSequence($scriptSection);
544
545
                // Detect if effect on mainStack is `false`
546 3
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
547 3
                if ($resolvesFalse) {
548
                    if (count($templateTypes) > 1) {
549
                        throw new UnsupportedScript("Unsupported script, multiple steps to segment which is negated");
550
                    }
551
                }
552
553 3
                foreach ($templateTypes as $k => $checksig) {
554 3
                    if ($fExec) {
555 3
                        if ($checksig instanceof Checksig) {
556 3
                            $this->extractChecksig($signScript->getScript(), $checksig, $stack, $this->fqs->sigVersion(), $resolvesFalse);
557
558
                            // If this statement results is later consumed
559
                            // by a conditional which would be false, mark
560
                            // this operation as not required
561
                            if ($resolvesFalse) {
562
                                $checksig->setRequired(false);
563
                            }
564
                        } else if ($checksig instanceof TimeLock) {
565
                            $this->checkTimeLock($checksig);
566
                        }
567
568
                        $steps[] = $checksig;
569
                    }
570
                }
571
            }
572
        }
573
574
        return $steps;
575
    }
576
577
    /**
578
     * @param int $verify
579
     * @param int $input
580
     * @param int $threshold
581
     * @return int
582
     */
583
    private function compareRangeAgainstThreshold($verify, $input, $threshold)
584
    {
585
        if ($verify <= $threshold && $input > $threshold) {
586
            return -1;
587
        }
588
589
        if ($verify > $threshold && $input <= $threshold) {
590
            return 1;
591
        }
592
593
        return 0;
594
    }
595
596
    /**
597
     * @param TimeLock $timelock
598
     */
599
    public function checkTimeLock(TimeLock $timelock)
600
    {
601
        $info = $timelock->getInfo();
602
        if (($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY) != 0 && $info instanceof CheckLocktimeVerify) {
603
            $verifyLocktime = $info->getLocktime();
604
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
605
                $input = $this->tx->getInput($this->nInput);
606
                if ($input->isFinal()) {
607
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
608
                }
609
610
                $locktime = $this->tx->getLockTime();
611
                $cmp = $this->compareRangeAgainstThreshold($verifyLocktime, $locktime, Locktime::BLOCK_MAX);
612
                if ($cmp === -1) {
613
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
614
                } else if ($cmp === 1) {
615
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
616
                }
617
618
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
619
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
620
            }
621
        }
622
623
        if (($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY) != 0 && $info instanceof CheckSequenceVerify) {
624
            // Future soft-fork extensibility, NOP if disabled flag
625
            if (($info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
626
                return;
627
            }
628
629
            if (!$this->signatureChecker->checkSequence(Number::int($info->getRelativeLockTime()))) {
630
                if ($this->tx->getVersion() < 2) {
631
                    throw new \RuntimeException("Transaction version must be 2 or greater for CSV");
632
                }
633
634
                $input = $this->tx->getInput($this->nInput);
635
                if ($input->isFinal()) {
636
                    throw new \RuntimeException("Sequence LOCKTIME_DISABLE_FLAG is set - not allowed on CSV output");
637
                }
638
639
                $cmp = $this->compareRangeAgainstThreshold($info->getRelativeLockTime(), $input->getSequence(), TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG);
640
                if ($cmp === -1) {
641
                    throw new \RuntimeException("CSV was for block height, but txin sequence was in timestamp range");
642
                } else if ($cmp === 1) {
643
                    throw new \RuntimeException("CSV was for timestamp, but txin sequence was in block range");
644
                }
645
646
                $masked = $info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_MASK;
647
                $requiredLock = "{$masked} " . ($info->isRelativeToBlock() ? " (blocks)" : "(seconds after txOut)");
648
                throw new \RuntimeException("Output unspendable with this sequence, must be locked for {$requiredLock}");
649
            }
650
        }
651
    }
652
653
    /**
654
     * @param ScriptInterface $script
655
     * @param BufferInterface $vchSig
656
     * @param BufferInterface $vchKey
657
     * @return bool
658
     */
659 1
    private function checkSignature(ScriptInterface $script, BufferInterface $vchSig, BufferInterface $vchKey)
660
    {
661
        try {
662 1
            return $this->signatureChecker->checkSig($script, $vchSig, $vchKey, $this->fqs->sigVersion(), $this->flags);
663
        } catch (ScriptRuntimeException $e) {
664
            return false;
665
        }
666
    }
667
668
    /**
669
     * This function is strictly for $canSign types.
670
     * It will extract signatures/publicKeys when given $outputData, and $stack.
671
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
672
     *
673
     * @param ScriptInterface $script
674
     * @param Checksig $checksig
675
     * @param Stack $stack
676
     * @param int $sigVersion
677
     * @param bool $expectFalse
678
     * @throws ScriptRuntimeException
679
     * @throws SignerException
680
     * @throws \Exception
681
     */
682 3
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, int $sigVersion, bool $expectFalse)
683
    {
684 3
        $size = count($stack);
685
686 3
        if ($checksig->getType() === ScriptType::P2PKH) {
687 1
            if ($size > 1) {
688 1
                $vchPubKey = $stack->pop();
689 1
                $vchSig = $stack->pop();
690
691 1
                $value = false;
692 1
                if (!$expectFalse) {
693 1
                    $value = $this->checkSignature($script, $vchSig, $vchPubKey);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 689 can be null; however, BitWasp\Bitcoin\Transact...igner::checkSignature() 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 688 can be null; however, BitWasp\Bitcoin\Transact...igner::checkSignature() 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...
694
695 1
                    if (!$value) {
696 1
                        throw new SignerException('Existing signatures are invalid!');
697
                    }
698
                }
699
700
                if (!$checksig->isVerify()) {
701
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
702
                }
703
704
                if (!$expectFalse) {
705
                    $checksig
706
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 689 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...
707
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 688 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...
708
                    ;
709
                }
710
            }
711 2
        } else if ($checksig->getType() === ScriptType::P2PK) {
712 1
            if ($size > 0) {
713 1
                $vchSig = $stack->pop();
714
715 1
                $value = false;
716 1
                if (!$expectFalse) {
717 1
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $checksig->getSolution(), $this->fqs->sigVersion(), $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 713 can be null; however, BitWasp\Bitcoin\Script\I...CheckerBase::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; however, BitWasp\Bitcoin\Script\I...CheckerBase::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...
718 1
                    if (!$value) {
719 1
                        throw new SignerException('Existing signatures are invalid!');
720
                    }
721
                }
722
723
                if (!$checksig->isVerify()) {
724
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
725
                }
726
727
                if (!$expectFalse) {
728
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 713 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...
729
                }
730
            }
731
732
            $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; 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...
733 1
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
734
            /** @var Multisig $info */
735 1
            $info = $checksig->getInfo();
736 1
            $keyBuffers = $info->getKeyBuffers();
737 1
            foreach ($keyBuffers as $idx => $keyBuf) {
738 1
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
739
            }
740
741 1
            $value = false;
742 1
            if ($this->padUnsignedMultisigs) {
743
                // Multisig padding is only used for partially signed transactions,
744
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
745
                // values (including the dummy), with one for each candidate signature,
746
                // such that $this->signatures state is captured.
747
                // The feature serves to skip validation/sorting an incomplete multisig.
748
749
                if ($size === 1 + $info->getKeyCount()) {
750
                    $sigBufCount = 0;
751
                    $null = new Buffer();
752
                    $keyToSigMap = new \SplObjectStorage();
753
754
                    // Reproduce $keyToSigMap and $sigBufCount
755
                    for ($i = 0; $i < $info->getKeyCount(); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
756
                        if (!$stack[-1 - $i]->equals($null)) {
757
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
758
                            $sigBufCount++;
759
                        }
760
                    }
761
762
                    // We observed $this->requiredSigs sigs, therefore we can
763
                    // say the implementation is incompatible
764
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
765
                        throw new SignerException("Padding is forbidden for a fully signed multisig script");
766
                    }
767
768
                    $toDelete = 1 + $info->getKeyCount();
769
                    $value = true;
770
                }
771
            }
772
773 1
            if (!isset($toDelete) || !isset($keyToSigMap)) {
774
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
775 1
                $sigBufs = [];
776 1
                $max = min($checksig->getRequiredSigs(), $size - 1);
777 1
                for ($i = 0; $i < $max; $i++) {
778 1
                    $vchSig = $stack[-1 - $i];
779 1
                    $sigBufs[] = $vchSig;
780
                }
781
782 1
                $sigBufs = array_reverse($sigBufs);
783 1
                $sigBufCount = count($sigBufs);
784
785 1
                if (!$expectFalse) {
786 1
                    if ($sigBufCount > 0) {
787 1
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
788
                        // Here we learn if any signatures were invalid, it won't be in the map.
789 1
                        if ($sigBufCount !== count($keyToSigMap)) {
790 1
                            throw new SignerException('Existing signatures are invalid!');
791
                        }
792
                        $toDelete = 1 + count($keyToSigMap);
793
                    } else {
794
                        $toDelete = 0;
795
                        $keyToSigMap = new \SplObjectStorage();
796
                    }
797
                    $value = true;
798
                } else {
799
                    // todo: should check that all signatures are zero
800
                    $keyToSigMap = new \SplObjectStorage();
801
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
802
                    $value = false;
803
                }
804
            }
805
806
            while ($toDelete--) {
807
                $stack->pop();
808
            }
809
810
            foreach ($keyBuffers as $idx => $key) {
811
                if (isset($keyToSigMap[$key])) {
812
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
813
                }
814
            }
815
816
            if (!$checksig->isVerify()) {
817
                $stack->push($value ? new Buffer("\x01") : new Buffer());
818
            }
819
        } else {
820
            throw new UnsupportedScript('Unsupported output type passed to extractFromValues');
821
        }
822
    }
823
824
    /**
825
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
826
     *
827
     * @param ScriptInterface $scriptCode
828
     * @param int $sigHashType
829
     * @param int $sigVersion
830
     * @throws SignerException
831
     * @return BufferInterface
832
     */
833
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, int $sigHashType, int $sigVersion): BufferInterface
834
    {
835
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
836
            throw new SignerException('Invalid sigHashType requested');
837
        }
838
839
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
840
    }
841
842
    /**
843
     * Calculates the signature hash for the input for the given $sigHashType.
844
     *
845
     * @param int $sigHashType
846
     * @return BufferInterface
847
     * @throws SignerException
848
     */
849
    public function getSigHash(int $sigHashType): BufferInterface
850
    {
851
        return $this->calculateSigHashUnsafe($this->fqs->signScript()->getScript(), $sigHashType, $this->fqs->sigVersion());
852
    }
853
854
    /**
855
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
856
     *
857
     * @param PrivateKeyInterface $key
858
     * @param ScriptInterface $scriptCode
859
     * @param int $sigHashType
860
     * @param int $sigVersion
861
     * @return TransactionSignatureInterface
862
     * @throws SignerException
863
     */
864
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, int $sigHashType, int $sigVersion)
865
    {
866
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
867
        return new TransactionSignature($this->ecAdapter, $key->sign($hash), $sigHashType);
868
    }
869
870
    /**
871
     * Returns whether all required signatures have been provided.
872
     *
873
     * @return bool
874
     */
875
    public function isFullySigned(): bool
876
    {
877
        foreach ($this->steps as $step) {
878
            if ($step instanceof Conditional) {
879
                if (!$step->hasValue()) {
880
                    return false;
881
                }
882
            } else if ($step instanceof Checksig) {
883
                if (!$step->isFullySigned()) {
884
                    return false;
885
                }
886
            }
887
        }
888
889
        return true;
890
    }
891
892
    /**
893
     * Returns the required number of signatures for this input.
894
     *
895
     * @return int
896
     */
897
    public function getRequiredSigs(): int
898
    {
899
        $count = 0;
900
        foreach ($this->steps as $step) {
901
            if ($step instanceof Checksig) {
902
                $count += $step->getRequiredSigs();
903
            }
904
        }
905
        return $count;
906
    }
907
908
    /**
909
     * Returns an array where the values are either null,
910
     * or a TransactionSignatureInterface.
911
     *
912
     * @return TransactionSignatureInterface[]
913
     */
914
    public function getSignatures(): array
915
    {
916
        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...
917
    }
918
919
    /**
920
     * Returns an array where the values are either null,
921
     * or a PublicKeyInterface.
922
     *
923
     * @return PublicKeyInterface[]
924
     */
925
    public function getPublicKeys(): array
926
    {
927
        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...
928
    }
929
930
    /**
931
     * Returns a FullyQualifiedScript since we
932
     * have solved all scripts to do with this input
933
     *
934
     * @return FullyQualifiedScript
935
     */
936
    public function getInputScripts(): FullyQualifiedScript
937
    {
938
        return $this->fqs;
939
    }
940
941
    /**
942
     * @param int $stepIdx
943
     * @param PrivateKeyInterface $privateKey
944
     * @param int $sigHashType
945
     * @return $this
946
     * @throws SignerException
947
     */
948
    public function signStep(int $stepIdx, PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL)
949
    {
950
        if (!array_key_exists($stepIdx, $this->steps)) {
951
            throw new \RuntimeException("Unknown step index");
952
        }
953
954
        $checksig = $this->steps[$stepIdx];
955
        if (!($checksig instanceof Checksig)) {
956
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
957
        }
958
959
        if ($checksig->isFullySigned()) {
960
            return $this;
961
        }
962
963
        if (SigHash::V1 === $this->fqs->sigVersion() && !$privateKey->isCompressed()) {
964
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
965
        }
966
967
        $signScript = $this->fqs->signScript()->getScript();
968
        if ($checksig->getType() === ScriptType::P2PK) {
969
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
970
                throw new \RuntimeException('Signing with the wrong private key');
971
            }
972
973
            if (!$checksig->hasSignature(0)) {
974
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
975
                $checksig->setSignature(0, $signature);
976
            }
977
        } else if ($checksig->getType() === ScriptType::P2PKH) {
978
            $publicKey = $privateKey->getPublicKey();
979
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
980
                throw new \RuntimeException('Signing with the wrong private key');
981
            }
982
983
            if (!$checksig->hasSignature(0)) {
984
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
985
                $checksig->setSignature(0, $signature);
986
            }
987
988
            if (!$checksig->hasKey(0)) {
989
                $checksig->setKey(0, $publicKey);
990
            }
991
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
992
            $signed = false;
993
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
994
                if (!$checksig->hasSignature($keyIdx)) {
995
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
996
                        $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
997
                        $checksig->setSignature($keyIdx, $signature);
998
                        $signed = true;
999
                    }
1000
                }
1001
            }
1002
1003
            if (!$signed) {
1004
                throw new \RuntimeException('Signing with the wrong private key');
1005
            }
1006
        } else {
1007
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1008
        }
1009
1010
        return $this;
1011
    }
1012
1013
    /**
1014
     * Sign the input using $key and $sigHashTypes
1015
     *
1016
     * @param PrivateKeyInterface $privateKey
1017
     * @param int $sigHashType
1018
     * @return $this
1019
     * @throws SignerException
1020
     */
1021
    public function sign(PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL)
1022
    {
1023
        return $this->signStep(0, $privateKey, $sigHashType);
1024
    }
1025
1026
    /**
1027
     * Verifies the input using $flags for script verification
1028
     *
1029
     * @param int $flags
1030
     * @return bool
1031
     */
1032
    public function verify(int $flags = null): bool
1033
    {
1034
        $consensus = ScriptFactory::consensus();
1035
1036
        if ($flags === null) {
1037
            $flags = $this->flags;
1038
        }
1039
1040
        $flags |= Interpreter::VERIFY_P2SH;
1041
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1042
            $flags |= Interpreter::VERIFY_WITNESS;
1043
        }
1044
1045
        $sig = $this->serializeSignatures();
1046
1047
        // Take serialized signatures, and use mutator to add this inputs sig data
1048
        $mutator = TransactionFactory::mutate($this->tx);
1049
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1050
1051
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1052
            $witness = [];
1053
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1054
                if ($i === $this->nInput) {
1055
                    $witness[] = $sig->getScriptWitness();
1056
                } else {
1057
                    $witness[] = new ScriptWitness();
1058
                }
1059
            }
1060
1061
            $mutator->witness($witness);
1062
        }
1063
1064
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1065
    }
1066
1067
    /**
1068
     * @return Stack
1069
     */
1070
    private function serializeSteps(): Stack
1071
    {
1072
        $results = [];
1073
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1074
            $step = $this->steps[$i];
1075
1076
            if ($step instanceof Conditional) {
1077
                $results[] = $step->serialize();
1078
            } else if ($step instanceof Checksig) {
1079
                if ($step->isRequired()) {
1080
                    if (count($step->getSignatures()) === 0) {
1081
                        break;
1082
                    }
1083
                }
1084
1085
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1086
1087
                if (!$step->isFullySigned()) {
1088
                    break;
1089
                }
1090
            }
1091
        }
1092
1093
        $values = [];
1094
        foreach (array_reverse($results) as $v) {
1095
            foreach ($v as $value) {
1096
                $values[] = $value;
1097
            }
1098
        }
1099
1100
        return new Stack($values);
1101
    }
1102
1103
    /**
1104
     * Produces a SigValues instance containing the scriptSig & script witness
1105
     *
1106
     * @return SigValues
1107
     */
1108
    public function serializeSignatures(): SigValues
1109
    {
1110
        return $this->fqs->encodeStack($this->serializeSteps());
1111
    }
1112
1113
    /**
1114
     * @return Checksig[]|Conditional[]|mixed
1115
     */
1116
    public function getSteps()
1117
    {
1118
        return $this->steps;
1119
    }
1120
}
1121