Completed
Push — master ( b59571...1ee45e )
by thomas
55:12 queued 52:48
created

InputSigner::serializeSteps()   D

Complexity

Conditions 9
Paths 24

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 18
nc 24
nop 0
dl 0
loc 32
ccs 18
cts 18
cp 1
crap 9
rs 4.909
c 0
b 0
f 0
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\Exceptions\UnsupportedScript;
13
use BitWasp\Bitcoin\Locktime;
14
use BitWasp\Bitcoin\Script\Classifier\OutputData;
15
use BitWasp\Bitcoin\Script\FullyQualifiedScript;
16
use BitWasp\Bitcoin\Script\Interpreter\BitcoinCashChecker;
17
use BitWasp\Bitcoin\Script\Interpreter\Checker;
18
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
19
use BitWasp\Bitcoin\Script\Interpreter\Number;
20
use BitWasp\Bitcoin\Script\Interpreter\Stack;
21
use BitWasp\Bitcoin\Script\Opcodes;
22
use BitWasp\Bitcoin\Script\Parser\Operation;
23
use BitWasp\Bitcoin\Script\Path\BranchInterpreter;
24
use BitWasp\Bitcoin\Script\ScriptFactory;
25
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
26
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkey;
27
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkeyHash;
28
use BitWasp\Bitcoin\Script\ScriptInterface;
29
use BitWasp\Bitcoin\Script\ScriptType;
30
use BitWasp\Bitcoin\Script\ScriptWitness;
31
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
32
use BitWasp\Bitcoin\Signature\TransactionSignature;
33
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
34
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckLocktimeVerify;
35
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckSequenceVerify;
36
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
37
use BitWasp\Bitcoin\Transaction\TransactionFactory;
38
use BitWasp\Bitcoin\Transaction\TransactionInput;
39
use BitWasp\Bitcoin\Transaction\TransactionInterface;
40
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
41
use BitWasp\Buffertools\Buffer;
42
use BitWasp\Buffertools\BufferInterface;
43
44
class InputSigner implements InputSignerInterface
45
{
46
    /**
47
     * @var array
48
     */
49
    protected static $canSign = [
50
        ScriptType::P2PKH,
51
        ScriptType::P2PK,
52
        ScriptType::MULTISIG
53
    ];
54
55
    /**
56
     * @var array
57
     */
58
    protected static $validP2sh = [
59
        ScriptType::P2WKH,
60
        ScriptType::P2WSH,
61
        ScriptType::P2PKH,
62
        ScriptType::P2PK,
63
        ScriptType::MULTISIG
64
    ];
65
66
    /**
67
     * @var EcAdapterInterface
68
     */
69
    private $ecAdapter;
70
71
    /**
72
     * @var FullyQualifiedScript
73
     */
74
    private $fqs;
75
76
    /**
77
     * @var bool
78
     */
79
    private $padUnsignedMultisigs = false;
80
81
    /**
82
     * @var bool
83
     */
84
    private $tolerateInvalidPublicKey = false;
85
86
    /**
87
     * @var bool
88
     */
89
    private $redeemBitcoinCash = false;
90
91
    /**
92
     * @var bool
93
     */
94
    private $allowComplexScripts = false;
95
96
    /**
97
     * @var SignData
98
     */
99
    private $signData;
100
101
    /**
102
     * @var int
103
     */
104
    private $flags;
105
106
    /**
107
     * @var TransactionInterface
108
     */
109
    private $tx;
110
111
    /**
112
     * @var int
113
     */
114
    private $nInput;
115
116
    /**
117
     * @var TransactionOutputInterface
118
     */
119
    private $txOut;
120
121
    /**
122
     * @var Interpreter
123
     */
124
    private $interpreter;
125
126
    /**
127
     * @var Checker
128
     */
129
    private $signatureChecker;
130
131
    /**
132
     * @var TransactionSignatureSerializer
133
     */
134
    private $txSigSerializer;
135
136
    /**
137
     * @var PublicKeySerializerInterface
138
     */
139
    private $pubKeySerializer;
140
141
    /**
142
     * @var Conditional[]|Checksig[]
143
     */
144
    private $steps = [];
145
146
    /**
147
     * InputSigner constructor.
148
     *
149
     * Note, the implementation of this class is considered internal
150
     * and only the methods exposed on InputSignerInterface should
151
     * be depended on to avoid BC breaks.
152
     *
153
     * The only recommended way to produce this class is using Signer::input()
154
     *
155
     * @param EcAdapterInterface $ecAdapter
156
     * @param TransactionInterface $tx
157
     * @param int $nInput
158
     * @param TransactionOutputInterface $txOut
159
     * @param SignData $signData
160
     * @param TransactionSignatureSerializer|null $sigSerializer
161
     * @param PublicKeySerializerInterface|null $pubKeySerializer
162
     */
163 95
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
164
    {
165 95
        $this->ecAdapter = $ecAdapter;
166 95
        $this->tx = $tx;
167 95
        $this->nInput = $nInput;
168 95
        $this->txOut = $txOut;
169 95
        $this->signData = $signData;
170
171 95
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
172 95
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
173 95
        $this->interpreter = new Interpreter($this->ecAdapter);
174 95
    }
175
176
    /**
177
     * Ensures a FullyQualifiedScript will be accepted
178
     * by the InputSigner.
179
     *
180
     * @param FullyQualifiedScript $script
181
     */
182 53
    public static function ensureAcceptableScripts(FullyQualifiedScript $script)
183
    {
184 53
        $spkType = $script->scriptPubKey()->getType();
185
186 53
        if ($spkType !== ScriptType::P2SH) {
187 36
            if (!in_array($spkType, self::$validP2sh)) {
188 1
                throw new UnsupportedScript("scriptPubKey not supported");
189
            }
190 35
            $hasWitnessScript = $spkType === ScriptType::P2WSH;
191
        } else {
192 17
            $rsType = $script->redeemScript()->getType();
193 17
            if (!in_array($rsType, self::$validP2sh)) {
194 1
                throw new UnsupportedScript("Unsupported pay-to-script-hash script");
195
            }
196 16
            $hasWitnessScript = $rsType === ScriptType::P2WSH;
197
        }
198
199 51
        if ($hasWitnessScript) {
200 15
            $wsType = $script->witnessScript()->getType();
201 15
            if (!in_array($wsType, self::$canSign)) {
202 1
                throw new UnsupportedScript('Unsupported witness-script-hash script');
203
            }
204
        }
205 50
    }
206
207
    /**
208
     *  It ensures that violating the following prevents instance creation
209
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
210
     *  - the P2SH script covers signable types and P2WSH/P2WKH
211
     *  - the witnessScript covers signable types only
212
     * @return $this|InputSigner
213
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
214
     * @throws \Exception
215
     */
216 95
    public function extract()
217
    {
218 95
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
219 95
        $checker = new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
220
221 95
        if ($this->redeemBitcoinCash) {
222
            // unset VERIFY_WITNESS default
223 1
            $defaultFlags = $defaultFlags & (~Interpreter::VERIFY_WITNESS);
224
225 1
            if ($this->signData->hasSignaturePolicy()) {
226
                if ($this->signData->getSignaturePolicy() & Interpreter::VERIFY_WITNESS) {
227
                    throw new \RuntimeException("VERIFY_WITNESS is not possible for bitcoin cash");
228
                }
229
            }
230
231 1
            $checker = new BitcoinCashChecker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
232
        }
233
234 95
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
235 94
        $witnesses = $this->tx->getWitnesses();
236 94
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput] : new ScriptWitness([]);
237
238 94
        $fqs = FullyQualifiedScript::fromTxData($this->txOut->getScript(), $scriptSig, $witness, $this->signData);
239 86
        if (!$this->allowComplexScripts) {
240 53
            self::ensureAcceptableScripts($fqs);
241
        }
242
243 83
        $this->fqs = $fqs;
244 83
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
245 83
        $this->signatureChecker = $checker;
246
247 83
        $this->extractScript(
248 83
            $this->fqs->signScript(),
249 83
            $this->fqs->extractStack(
250 83
                $scriptSig,
251 83
                $witness
252
            ),
253 83
            $this->signData
254
        );
255
256 68
        return $this;
257
    }
258
259
    /**
260
     * @param bool $setting
261
     * @return $this
262
     */
263 80
    public function padUnsignedMultisigs($setting)
264
    {
265 80
        $this->padUnsignedMultisigs = (bool) $setting;
266 80
        return $this;
267
    }
268
269
    /**
270
     * @param bool $setting
271
     * @return $this
272
     */
273 80
    public function tolerateInvalidPublicKey($setting)
274
    {
275 80
        $this->tolerateInvalidPublicKey = (bool) $setting;
276 80
        return $this;
277
    }
278
279
    /**
280
     * @param bool $setting
281
     * @return $this
282
     */
283 80
    public function redeemBitcoinCash($setting)
284
    {
285 80
        $this->redeemBitcoinCash = (bool) $setting;
286 80
        return $this;
287
    }
288
289
    /**
290
     * @param bool $setting
291
     * @return $this
292
     */
293 80
    public function allowComplexScripts($setting)
294
    {
295 80
        $this->allowComplexScripts = (bool) $setting;
296 80
        return $this;
297
    }
298
299
    /**
300
     * @param BufferInterface $vchPubKey
301
     * @return PublicKeyInterface|null
302
     * @throws \Exception
303
     */
304 69
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
305
    {
306
        try {
307 69
            return $this->pubKeySerializer->parse($vchPubKey);
308 3
        } catch (\Exception $e) {
309 3
            if ($this->tolerateInvalidPublicKey) {
310 1
                return null;
311
            }
312
313 2
            throw $e;
314
        }
315
    }
316
317
    /**
318
     * @param ScriptInterface $script
319
     * @param BufferInterface[] $signatures
320
     * @param BufferInterface[] $publicKeys
321
     * @param int $sigVersion
322
     * @return \SplObjectStorage
323
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
324
     */
325 25
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
326
    {
327 25
        $sigCount = count($signatures);
328 25
        $keyCount = count($publicKeys);
329 25
        $ikey = $isig = 0;
330 25
        $fSuccess = true;
331 25
        $result = new \SplObjectStorage;
332
333 25
        while ($fSuccess && $sigCount > 0) {
334
            // Fetch the signature and public key
335 25
            $sig = $signatures[$isig];
336 25
            $pubkey = $publicKeys[$ikey];
337
338 25
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
339 24
                $result[$pubkey] = $sig;
340 24
                $isig++;
341 24
                $sigCount--;
342
            }
343
344 25
            $ikey++;
345 25
            $keyCount--;
346
347
            // If there are more signatures left than keys left,
348
            // then too many signatures have failed. Exit early,
349
            // without checking any further signatures.
350 25
            if ($sigCount > $keyCount) {
351 1
                $fSuccess = false;
352
            }
353
        }
354
355 25
        return $result;
356
    }
357
358
    /**
359
     * @param array $decoded
360
     * @param null $solution
361
     * @return null|TimeLock|Checksig
362
     */
363 83
    private function classifySignStep(array $decoded, &$solution = null)
364
    {
365
        try {
366 83
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
367 32
            $solution = $details->getKeyBuffers();
368 32
            return new Checksig($details);
369 83
        } 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 83
            $details = PayToPubkey::fromDecodedScript($decoded, true);
374 40
            $solution = $details->getKeyBuffer();
375 40
            return new Checksig($details);
376 83
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
377
        }
378
379
        try {
380 83
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
381 21
            $solution = $details->getPubKeyHash();
382 21
            return new Checksig($details);
383 83
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
384
        }
385
386
        try {
387 83
            $details = CheckLocktimeVerify::fromDecodedScript($decoded);
388 8
            return new TimeLock($details);
389 83
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
390
        }
391
392
        try {
393 83
            $details = CheckSequenceVerify::fromDecodedScript($decoded);
394 7
            return new TimeLock($details);
395 83
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
396
        }
397
398 83
        return null;
399
    }
400
401
    /**
402
     * @param Operation[] $scriptOps
403
     * @return Checksig[]
404
     */
405 83
    public function parseSequence(array $scriptOps)
406
    {
407 83
        $j = 0;
408 83
        $l = count($scriptOps);
409 83
        $result = [];
410 83
        while ($j < $l) {
411 83
            $step = null;
412 83
            $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...
413
414
            // increment the $last, and break if it's valid
415 83
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
416 83
                $slice = array_slice($scriptOps, $j, $i);
417 83
                $step = $this->classifySignStep($slice, $solution);
418 83
                if ($step !== null) {
419 83
                    break;
420
                }
421
            }
422
423 83
            if (null === $step) {
424
                throw new \RuntimeException("Invalid script");
425
            } else {
426 83
                $j += $i;
427 83
                $result[] = $step;
428
            }
429
        }
430
431 83
        return $result;
432
    }
433
434
    /**
435
     * @param Operation $operation
436
     * @param Stack $mainStack
437
     * @param bool[] $pathData
438
     * @return Conditional
439
     */
440 11
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData)
441
    {
442 11
        $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...
443
444 11
        if (!$mainStack->isEmpty()) {
445 11
            if (count($pathData) === 0) {
446
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
447
            }
448
449 11
            $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...
450 11
            $dataValue = array_shift($pathData);
451 11
            if ($opValue !== $dataValue) {
452 11
                throw new \RuntimeException("Current stack doesn't follow branch path");
453
            }
454
        } else {
455 9
            if (count($pathData) === 0) {
456
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
457
            }
458
459 9
            $opValue = array_shift($pathData);
460
        }
461
462 11
        $conditional = new Conditional($operation->getOp());
463
464 11
        if ($opValue !== null) {
465 11
            if (!is_bool($opValue)) {
466
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
467
            }
468
469 11
            $conditional->setValue($opValue);
470
        }
471
472 11
        return $conditional;
473
    }
474
475
    /**
476
     * @param int $idx
477
     * @return Checksig|Conditional
478
     */
479 18
    public function step($idx)
480
    {
481 18
        if (!array_key_exists($idx, $this->steps)) {
482
            throw new \RuntimeException("Out of range index for input sign step");
483
        }
484
485 18
        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...
486
    }
487
488
    /**
489
     * @param OutputData $signScript
490
     * @param Stack $stack
491
     * @param SignData $signData
492
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
493
     * @throws \Exception
494
     */
495 83
    public function extractScript(OutputData $signScript, Stack $stack, SignData $signData)
496
    {
497 83
        $logicInterpreter = new BranchInterpreter();
498 83
        $tree = $logicInterpreter->getScriptTree($signScript->getScript());
499
500 83
        if ($tree->hasMultipleBranches()) {
501 11
            $logicalPath = $signData->getLogicalPath();
502
            // we need a function like findWitnessScript to 'check'
503
            // partial signatures against _our_ path
504
        } else {
505 72
            $logicalPath = [];
506
        }
507
508
        $scriptSections = $tree
509 83
            ->getBranchByPath($logicalPath)
510 83
            ->getScriptSections();
511
512 83
        $vfStack = new Stack();
513
514 83
        $pathCopy = $logicalPath;
515 83
        $steps = [];
516 83
        foreach ($scriptSections as $i => $scriptSection) {
517
            /** @var Operation[] $scriptSection */
518 83
            $fExec = !$this->interpreter->checkExec($vfStack, false);
519 83
            if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) {
520 11
                $op = $scriptSection[0];
521 11
                switch ($op->getOp()) {
522 11
                    case Opcodes::OP_IF:
523 11
                    case Opcodes::OP_NOTIF:
524 11
                        $value = false;
525 11
                        if ($fExec) {
526
                            // Pop from mainStack if $fExec
527 11
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
528
529
                            // the Conditional has a value in this case:
530 11
                            $value = $step->getValue();
531
532
                            // Connect the last operation (if there is one)
533
                            // with the last step with isRequired==$value
534
                            // todo: check this part out..
535 11
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
536 4
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
537 4
                                    $step->providedBy($steps[$j]);
538 4
                                    break;
539
                                }
540
                            }
541
                        } else {
542 1
                            $step = new Conditional($op->getOp());
543
                        }
544
545 11
                        $steps[] = $step;
546
547 11
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
548 5
                            $value = !$value;
549
                        }
550
551 11
                        $vfStack->push($value);
552 11
                        break;
553 11
                    case Opcodes::OP_ENDIF:
554 11
                        $vfStack->pop();
555 11
                        break;
556 9
                    case Opcodes::OP_ELSE:
557 9
                        $vfStack->push(!$vfStack->pop());
558 11
                        break;
559
                }
560
            } else {
561 83
                $templateTypes = $this->parseSequence($scriptSection);
562
563
                // Detect if effect on mainStack is `false`
564 83
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
565 83
                if ($resolvesFalse) {
566 2
                    if (count($templateTypes) > 1) {
567
                        throw new UnsupportedScript("Unsupported script, multiple steps to segment which is negated");
568
                    }
569
                }
570
571 83
                foreach ($templateTypes as $k => $checksig) {
572 83
                    if ($fExec) {
573 83
                        if ($checksig instanceof Checksig) {
574 73
                            $this->extractChecksig($signScript->getScript(), $checksig, $stack, $this->fqs->sigVersion(), $resolvesFalse);
575
576
                            // If this statement results is later consumed
577
                            // by a conditional which would be false, mark
578
                            // this operation as not required
579 68
                            if ($resolvesFalse) {
580 68
                                $checksig->setRequired(false);
581
                            }
582 15
                        } else if ($checksig instanceof TimeLock) {
583 15
                            $this->checkTimeLock($checksig);
584
                        }
585
586 68
                        $steps[] = $checksig;
587
                    }
588
                }
589
            }
590
        }
591
592 68
        $this->steps = $steps;
593 68
    }
594
595
    /**
596
     * @param int $verify
597
     * @param int $input
598
     * @param int $threshold
599
     * @return int
600
     */
601 5
    private function compareRangeAgainstThreshold($verify, $input, $threshold)
602
    {
603 5
        if ($verify <= $threshold && $input > $threshold) {
604 2
            return -1;
605
        }
606
607 3
        if ($verify > $threshold && $input <= $threshold) {
608 2
            return 1;
609
        }
610
611 1
        return 0;
612
    }
613
614
    /**
615
     * @param TimeLock $timelock
616
     */
617 15
    public function checkTimeLock(TimeLock $timelock)
618
    {
619 15
        $info = $timelock->getInfo();
620 15
        if (($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY) != 0 && $info instanceof CheckLocktimeVerify) {
621 8
            $verifyLocktime = $info->getLocktime();
622 8
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
623 5
                $input = $this->tx->getInput($this->nInput);
624 5
                if ($input->isFinal()) {
625 2
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
626
                }
627
628 3
                $locktime = $this->tx->getLockTime();
629 3
                $cmp = $this->compareRangeAgainstThreshold($verifyLocktime, $locktime, Locktime::BLOCK_MAX);
630 3
                if ($cmp === -1) {
631 1
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
632 2
                } else if ($cmp === 1) {
633 1
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
634
                }
635
636 1
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
637 1
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
638
            }
639
        }
640
641 10
        if (($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY) != 0 && $info instanceof CheckSequenceVerify) {
642
            // Future soft-fork extensibility, NOP if disabled flag
643 7
            if (($info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
644
                return;
645
            }
646
647 7
            if (!$this->signatureChecker->checkSequence(Number::int($info->getRelativeLockTime()))) {
648 5
                if ($this->tx->getVersion() < 2) {
649 2
                    throw new \RuntimeException("Transaction version must be 2 or greater for CSV");
650
                }
651
652 3
                $input = $this->tx->getInput($this->nInput);
653 3
                if ($input->isFinal()) {
654 1
                    throw new \RuntimeException("Sequence LOCKTIME_DISABLE_FLAG is set - not allowed on CSV output");
655
                }
656
657 2
                $cmp = $this->compareRangeAgainstThreshold($info->getRelativeLockTime(), $input->getSequence(), TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG);
658 2
                if ($cmp === -1) {
659 1
                    throw new \RuntimeException("CSV was for block height, but txin sequence was in timestamp range");
660 1
                } else if ($cmp === 1) {
661 1
                    throw new \RuntimeException("CSV was for timestamp, but txin sequence was in block range");
662
                }
663
664
                $masked = $info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_MASK;
665
                $requiredLock = "{$masked} " . ($info->isRelativeToBlock() ? " (blocks)" : "(seconds after txOut)");
666
                throw new \RuntimeException("Output unspendable with this sequence, must be locked for {$requiredLock}");
667
            }
668
        }
669 5
    }
670
671
    /**
672
     * This function is strictly for $canSign types.
673
     * It will extract signatures/publicKeys when given $outputData, and $stack.
674
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
675
     *
676
     * @param ScriptInterface $script
677
     * @param Checksig $checksig
678
     * @param Stack $stack
679
     * @param int $sigVersion
680
     * @param bool $expectFalse
681
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
682
     * @throws \Exception
683
     */
684 73
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, $sigVersion, $expectFalse)
685
    {
686 73
        $size = count($stack);
687
688 73
        if ($checksig->getType() === ScriptType::P2PKH) {
689 21
            if ($size > 1) {
690 19
                $vchPubKey = $stack->pop();
691 19
                $vchSig = $stack->pop();
692
693 19
                $value = false;
694 19
                if (!$expectFalse) {
695 19
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $vchPubKey, $this->fqs->sigVersion(), $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 691 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 690 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...
696 19
                    if (!$value) {
697 1
                        throw new \RuntimeException('Existing signatures are invalid!');
698
                    }
699
                }
700
701 18
                if (!$checksig->isVerify()) {
702 18
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
703
                }
704
705 18
                if (!$expectFalse) {
706
                    $checksig
707 18
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 691 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...
708 20
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 690 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...
709
                    ;
710
                }
711
            }
712 55
        } else if ($checksig->getType() === ScriptType::P2PK) {
713 30
            if ($size > 0) {
714 23
                $vchSig = $stack->pop();
715
716 23
                $value = false;
717 23
                if (!$expectFalse) {
718 23
                    $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 714 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...
719 23
                    if (!$value) {
720 1
                        throw new \RuntimeException('Existing signatures are invalid!');
721
                    }
722
                }
723
724 22
                if (!$checksig->isVerify()) {
725 20
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
726
                }
727
728 22
                if (!$expectFalse) {
729 22
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 714 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...
730
                }
731
            }
732
733 29
            $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...
734 32
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
735
            /** @var Multisig $info */
736 32
            $info = $checksig->getInfo();
737 32
            $keyBuffers = $info->getKeyBuffers();
738 32
            foreach ($keyBuffers as $idx => $keyBuf) {
739 32
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
740
            }
741
742 30
            $value = false;
743 30
            if ($this->padUnsignedMultisigs) {
744
                // Multisig padding is only used for partially signed transactions,
745
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
746
                // values (including the dummy), with one for each candidate signature,
747
                // such that $this->signatures state is captured.
748
                // The feature serves to skip validation/sorting an incomplete multisig.
749
750 14
                if ($size === 1 + $info->getKeyCount()) {
751 2
                    $sigBufCount = 0;
752 2
                    $null = new Buffer();
753 2
                    $keyToSigMap = new \SplObjectStorage();
754
755
                    // Reproduce $keyToSigMap and $sigBufCount
756 2
                    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...
757 2
                        if (!$stack[-1 - $i]->equals($null)) {
758 2
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
759 2
                            $sigBufCount++;
760
                        }
761
                    }
762
763
                    // We observed $this->requiredSigs sigs, therefore we can
764
                    // say the implementation is incompatible
765 2
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
766 2
                        throw new \RuntimeException("Padding is forbidden for a fully signed multisig script");
767
                    }
768
769
                    $toDelete = 1 + $info->getKeyCount();
770
                    $value = true;
771
                }
772
            }
773
774 30
            if (!isset($toDelete) || !isset($keyToSigMap)) {
775
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
776 30
                $sigBufs = [];
777 30
                $max = min($checksig->getRequiredSigs(), $size - 1);
778 30
                for ($i = 0; $i < $max; $i++) {
779 26
                    $vchSig = $stack[-1 - $i];
780 26
                    $sigBufs[] = $vchSig;
781
                }
782
783 30
                $sigBufs = array_reverse($sigBufs);
784 30
                $sigBufCount = count($sigBufs);
785
786 30
                if (!$expectFalse) {
787 29
                    if ($sigBufCount > 0) {
788 25
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
789
                        // Here we learn if any signatures were invalid, it won't be in the map.
790 25
                        if ($sigBufCount !== count($keyToSigMap)) {
791 1
                            throw new \RuntimeException('Existing signatures are invalid!');
792
                        }
793 24
                        $toDelete = 1 + count($keyToSigMap);
794
                    } else {
795 28
                        $toDelete = 0;
796 28
                        $keyToSigMap = new \SplObjectStorage();
797
                    }
798 28
                    $value = true;
799
                } else {
800
                    // should check that all signatures are zero
801 1
                    $keyToSigMap = new \SplObjectStorage();
802 1
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
803 1
                    $value = false;
804
                }
805
            }
806
807 29
            while ($toDelete--) {
808 25
                $stack->pop();
809
            }
810
811 29
            foreach ($keyBuffers as $idx => $key) {
812 29
                if (isset($keyToSigMap[$key])) {
813 29
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
814
                }
815
            }
816
817 29
            if (!$checksig->isVerify()) {
818 29
                $stack->push($value ? new Buffer("\x01") : new Buffer());
819
            }
820
        } else {
821
            throw new UnsupportedScript('Unsupported output type passed to extractFromValues');
822
        }
823 68
    }
824
825
    /**
826
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
827
     *
828
     * @param ScriptInterface $scriptCode
829
     * @param int $sigHashType
830
     * @param int $sigVersion
831
     * @return BufferInterface
832
     */
833 64
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
834
    {
835 64
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
836 1
            throw new \RuntimeException('Invalid sigHashType requested');
837
        }
838
839 63
        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
     */
848 1
    public function getSigHash($sigHashType)
849
    {
850 1
        return $this->calculateSigHashUnsafe($this->fqs->signScript()->getScript(), $sigHashType, $this->fqs->sigVersion());
851
    }
852
853
    /**
854
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
855
     *
856
     * @param PrivateKeyInterface $key
857
     * @param ScriptInterface $scriptCode
858
     * @param int $sigHashType
859
     * @param int $sigVersion
860
     * @return TransactionSignatureInterface
861
     */
862 63
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
863
    {
864 63
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
865 63
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
866 63
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
867
    }
868
869
    /**
870
     * Returns whether all required signatures have been provided.
871
     *
872
     * @return bool
873
     */
874 37
    public function isFullySigned()
875
    {
876 37
        foreach ($this->steps as $step) {
877 37
            if ($step instanceof Conditional) {
878
                if (!$step->hasValue()) {
879
                    return false;
880
                }
881 37
            } else if ($step instanceof Checksig) {
882 37
                if (!$step->isFullySigned()) {
883 37
                    return false;
884
                }
885
            }
886
        }
887
888 31
        return true;
889
    }
890
891
    /**
892
     * Returns the required number of signatures for this input.
893
     *
894
     * @return int
895
     */
896 37
    public function getRequiredSigs()
897
    {
898 37
        $count = 0;
899 37
        foreach ($this->steps as $step) {
900 37
            if ($step instanceof Checksig) {
901 37
                $count += $step->getRequiredSigs();
902
            }
903
        }
904 37
        return $count;
905
    }
906
907
    /**
908
     * Returns an array where the values are either null,
909
     * or a TransactionSignatureInterface.
910
     *
911
     * @return TransactionSignatureInterface[]
912
     */
913 37
    public function getSignatures()
914
    {
915 37
        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...
916
    }
917
918
    /**
919
     * Returns an array where the values are either null,
920
     * or a PublicKeyInterface.
921
     *
922
     * @return PublicKeyInterface[]
923
     */
924 26
    public function getPublicKeys()
925
    {
926 26
        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...
927
    }
928
929
    /**
930
     * Returns a FullyQualifiedScript since we
931
     * have solved all scripts to do with this input
932
     *
933
     * @return FullyQualifiedScript
934
     */
935 25
    public function getInputScripts()
936
    {
937 25
        return $this->fqs;
938
    }
939
940
    /**
941
     * @param int $stepIdx
942
     * @param PrivateKeyInterface $privateKey
943
     * @param int $sigHashType
944
     * @return $this
945
     */
946 66
    public function signStep($stepIdx, PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
947
    {
948 66
        if (!array_key_exists($stepIdx, $this->steps)) {
949
            throw new \RuntimeException("Unknown step index");
950
        }
951
952 66
        $checksig = $this->steps[$stepIdx];
953
954 66
        if (!($checksig instanceof Checksig)) {
955
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
956
        }
957
958 66
        if ($checksig->isFullySigned()) {
959
            return $this;
960
        }
961
962 66
        if (SigHash::V1 === $this->fqs->sigVersion() && !$privateKey->isCompressed()) {
963
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
964
        }
965
966 66
        $signScript = $this->fqs->signScript()->getScript();
967 66
        if ($checksig->getType() === ScriptType::P2PK) {
968 29
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
969 1
                throw new \RuntimeException('Signing with the wrong private key');
970
            }
971
972 28
            if (!$checksig->hasSignature(0)) {
973 28
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
974 28
                $checksig->setSignature(0, $signature);
975
            }
976 44
        } else if ($checksig->getType() === ScriptType::P2PKH) {
977 19
            $publicKey = $privateKey->getPublicKey();
978 19
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
979 1
                throw new \RuntimeException('Signing with the wrong private key');
980
            }
981
982 18
            if (!$checksig->hasSignature(0)) {
983 18
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
984 18
                $checksig->setSignature(0, $signature);
985
            }
986
987 18
            if (!$checksig->hasKey(0)) {
988 18
                $checksig->setKey(0, $publicKey);
989
            }
990 27
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
991 27
            $signed = false;
992 27
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
993 27
                if (!$checksig->hasSignature($keyIdx)) {
994 27
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
995 26
                        $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
996 26
                        $checksig->setSignature($keyIdx, $signature);
997 27
                        $signed = true;
998
                    }
999
                }
1000
            }
1001
1002 27
            if (!$signed) {
1003 27
                throw new \RuntimeException('Signing with the wrong private key');
1004
            }
1005
        } else {
1006
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1007
        }
1008
1009 63
        return $this;
1010
    }
1011
1012
    /**
1013
     * Sign the input using $key and $sigHashTypes
1014
     *
1015
     * @param PrivateKeyInterface $privateKey
1016
     * @param int $sigHashType
1017
     * @return $this
1018
     */
1019 43
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1020
    {
1021 43
        return $this->signStep(0, $privateKey, $sigHashType);
1022
    }
1023
1024
    /**
1025
     * Verifies the input using $flags for script verification
1026
     *
1027
     * @param int $flags
1028
     * @return bool
1029
     */
1030 48
    public function verify($flags = null)
1031
    {
1032 48
        $consensus = ScriptFactory::consensus();
1033
1034 48
        if ($flags === null) {
1035 30
            $flags = $this->flags;
1036
        }
1037
1038 48
        $flags |= Interpreter::VERIFY_P2SH;
1039 48
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1040 16
            $flags |= Interpreter::VERIFY_WITNESS;
1041
        }
1042
1043 48
        $sig = $this->serializeSignatures();
1044
1045
        // Take serialized signatures, and use mutator to add this inputs sig data
1046 48
        $mutator = TransactionFactory::mutate($this->tx);
1047 48
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1048
1049 48
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1050 16
            $witness = [];
1051 16
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1052 16
                if ($i === $this->nInput) {
1053 16
                    $witness[] = $sig->getScriptWitness();
1054
                } else {
1055 1
                    $witness[] = new ScriptWitness([]);
1056
                }
1057
            }
1058
1059 16
            $mutator->witness($witness);
1060
        }
1061
1062 48
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1063
    }
1064
1065
    /**
1066
     * @return Stack
1067
     */
1068 63
    private function serializeSteps()
1069
    {
1070 63
        $results = [];
1071 63
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1072 63
            $step = $this->steps[$i];
1073
1074 63
            if ($step instanceof Conditional) {
1075 11
                $results[] = $step->serialize();
1076 63
            } else if ($step instanceof Checksig) {
1077 63
                if ($step->isRequired()) {
1078 63
                    if (count($step->getSignatures()) === 0) {
1079 25
                        break;
1080
                    }
1081
                }
1082
1083 63
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1084
1085 63
                if (!$step->isFullySigned()) {
1086 6
                    break;
1087
                }
1088
            }
1089
        }
1090
1091 63
        $values = [];
1092 63
        foreach (array_reverse($results) as $v) {
1093 63
            foreach ($v as $value) {
1094 63
                $values[] = $value;
1095
            }
1096
        }
1097
1098 63
        return new Stack($values);
1099
    }
1100
1101
    /**
1102
     * Produces a SigValues instance containing the scriptSig & script witness
1103
     *
1104
     * @return SigValues
1105
     */
1106 63
    public function serializeSignatures()
1107
    {
1108 63
        return $this->fqs->encodeStack($this->serializeSteps());
1109
    }
1110
1111 25
    public function getSteps()
1112
    {
1113 25
        return $this->steps;
1114
    }
1115
}
1116