InputSigner::extractScript()   D
last analyzed

Complexity

Conditions 22
Paths 52

Size

Total Lines 98
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 22.0041

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 22
eloc 56
c 3
b 0
f 0
nc 52
nop 3
dl 0
loc 98
ccs 48
cts 49
cp 0.9796
crap 22.0041
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
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 CheckerBase
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 129
    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 129
        $this->ecAdapter = $ecAdapter;
173 129
        $this->tx = $tx;
174 129
        $this->nInput = $nInput;
175 129
        $this->txOut = $txOut;
176 129
        $this->signData = $signData;
177
178 129
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
179 129
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
180
181 129
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
182 129
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
183 129
        $this->interpreter = new Interpreter($this->ecAdapter);
184 129
        $this->signatureChecker = $checker;
185 129
    }
186
187
    /**
188
     * Ensures a FullyQualifiedScript will be accepted
189
     * by the InputSigner.
190
     *
191
     * @param FullyQualifiedScript $script
192
     */
193 87
    public static function ensureAcceptableScripts(FullyQualifiedScript $script)
194
    {
195 87
        $spkType = $script->scriptPubKey()->getType();
196
197 87
        if ($spkType !== ScriptType::P2SH) {
198 59
            if (!in_array($spkType, self::$validP2sh)) {
199 1
                throw new UnsupportedScript("scriptPubKey not supported");
200
            }
201 58
            $hasWitnessScript = $spkType === ScriptType::P2WSH;
202
        } else {
203 28
            $rsType = $script->redeemScript()->getType();
204 28
            if (!in_array($rsType, self::$validP2sh)) {
205 1
                throw new UnsupportedScript("Unsupported pay-to-script-hash script");
206
            }
207 27
            $hasWitnessScript = $rsType === ScriptType::P2WSH;
208
        }
209
210 85
        if ($hasWitnessScript) {
211 22
            $wsType = $script->witnessScript()->getType();
212 22
            if (!in_array($wsType, self::$canSign)) {
213 1
                throw new UnsupportedScript('Unsupported witness-script-hash script');
214
            }
215
        }
216 84
    }
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 129
    public function extract()
229
    {
230 129
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
231 128
        $witnesses = $this->tx->getWitnesses();
232 128
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput] : new ScriptWitness();
233
234 128
        $fqs = FullyQualifiedScript::fromTxData($this->txOut->getScript(), $scriptSig, $witness, $this->signData);
235 120
        if (!$this->allowComplexScripts) {
236 87
            self::ensureAcceptableScripts($fqs);
237
        }
238
239 117
        $this->fqs = $fqs;
240 117
        $this->steps = $this->extractScript(
241 117
            $this->fqs->signScript(),
242 117
            $this->fqs->extractStack($scriptSig, $witness),
243 117
            $this->signData
244
        );
245
246 102
        return $this;
247
    }
248
249
    /**
250
     * @param bool $setting
251
     * @return $this
252
     */
253 114
    public function padUnsignedMultisigs(bool $setting)
254
    {
255 114
        $this->padUnsignedMultisigs = $setting;
256 114
        return $this;
257
    }
258
259
    /**
260
     * @param bool $setting
261
     * @return $this
262
     */
263 114
    public function tolerateInvalidPublicKey(bool $setting)
264
    {
265 114
        $this->tolerateInvalidPublicKey = $setting;
266 114
        return $this;
267
    }
268
269
    /**
270
     * @param bool $setting
271
     * @return $this
272
     */
273 114
    public function allowComplexScripts(bool $setting)
274
    {
275 114
        $this->allowComplexScripts = $setting;
276 114
        return $this;
277
    }
278
279
    /**
280
     * @param BufferInterface $vchPubKey
281
     * @return PublicKeyInterface|null
282
     * @throws \Exception
283
     */
284 95
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
285
    {
286
        try {
287 95
            return $this->pubKeySerializer->parse($vchPubKey);
288 3
        } catch (\Exception $e) {
289 3
            if ($this->tolerateInvalidPublicKey) {
290 1
                return null;
291
            }
292
293 2
            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 30
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, int $sigVersion): \SplObjectStorage
306
    {
307 30
        $sigCount = count($signatures);
308 30
        $keyCount = count($publicKeys);
309 30
        $ikey = $isig = 0;
310 30
        $fSuccess = true;
311 30
        $result = new \SplObjectStorage;
312
313 30
        while ($fSuccess && $sigCount > 0) {
314
            // Fetch the signature and public key
315 30
            $sig = $signatures[$isig];
316 30
            $pubkey = $publicKeys[$ikey];
317
318 30
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
319 29
                $result[$pubkey] = $sig;
320 29
                $isig++;
321 29
                $sigCount--;
322
            }
323
324 30
            $ikey++;
325 30
            $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 30
            if ($sigCount > $keyCount) {
331 1
                $fSuccess = false;
332
            }
333
        }
334
335 30
        return $result;
336
    }
337
338
    /**
339
     * @param array $decoded
340
     * @param null $solution
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $solution is correct as it would always require null to be passed?
Loading history...
341
     * @return null|TimeLock|Checksig
342
     */
343 117
    private function classifySignStep(array $decoded, &$solution = null)
344
    {
345
        try {
346 117
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
347 38
            $solution = $details->getKeyBuffers();
348 38
            return new Checksig($details);
349 117
        } 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 117
            $details = PayToPubkey::fromDecodedScript($decoded, true);
354 45
            $solution = $details->getKeyBuffer();
355 45
            return new Checksig($details);
356 117
        } 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 117
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
361 45
            $solution = $details->getPubKeyHash();
362 45
            return new Checksig($details);
363 117
        } 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 117
            $details = CheckLocktimeVerify::fromDecodedScript($decoded);
368 8
            return new TimeLock($details);
369 117
        } 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 117
            $details = CheckSequenceVerify::fromDecodedScript($decoded);
374 7
            return new TimeLock($details);
375 117
        } 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 117
        return null;
379
    }
380
381
    /**
382
     * @param Operation[] $scriptOps
383
     * @return Checksig[]
384
     */
385 117
    public function parseSequence(array $scriptOps)
386
    {
387 117
        $j = 0;
388 117
        $l = count($scriptOps);
389 117
        $result = [];
390 117
        while ($j < $l) {
391 117
            $step = null;
392 117
            $slice = null;
393
394
            // increment the $last, and break if it's valid
395 117
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
396 117
                $slice = array_slice($scriptOps, $j, $i);
397 117
                $step = $this->classifySignStep($slice, $solution);
398 117
                if ($step !== null) {
399 117
                    break;
400
                }
401
            }
402
403 117
            if (null === $step) {
404
                throw new \RuntimeException("Invalid script");
405
            } else {
406 117
                $j += $i;
407 117
                $result[] = $step;
408
            }
409
        }
410
411 117
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns an array which contains values of type BitWasp\Bitcoin\Transaction\Factory\TimeLock which are incompatible with the documented value type BitWasp\Bitcoin\Transaction\Factory\Checksig.
Loading history...
412
    }
413
414
    /**
415
     * @param Operation $operation
416
     * @param Stack $mainStack
417
     * @param bool[] $pathData
418
     * @return Conditional
419
     */
420 11
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData): Conditional
421
    {
422 11
        $opValue = null;
423
424 11
        if (!$mainStack->isEmpty()) {
425 11
            if (count($pathData) === 0) {
426
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
427
            }
428
429 11
            $opValue = $this->interpreter->castToBool($mainStack->pop());
430 11
            $dataValue = array_shift($pathData);
431 11
            if ($opValue !== $dataValue) {
432 11
                throw new \RuntimeException("Current stack doesn't follow branch path");
433
            }
434
        } else {
435 9
            if (count($pathData) === 0) {
436
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
437
            }
438
439 9
            $opValue = array_shift($pathData);
440
        }
441
442 11
        $conditional = new Conditional($operation->getOp());
443
444 11
        if ($opValue !== null) {
445 11
            if (!is_bool($opValue)) {
446
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
447
            }
448
449 11
            $conditional->setValue($opValue);
450
        }
451
452 11
        return $conditional;
453
    }
454
455
    /**
456
     * @param int $idx
457
     * @return Checksig|Conditional
458
     */
459 18
    public function step(int $idx)
460
    {
461 18
        if (!array_key_exists($idx, $this->steps)) {
462
            throw new \RuntimeException("Out of range index for input sign step");
463
        }
464
465 18
        return $this->steps[$idx];
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 117
    public function extractScript(OutputData $signScript, Stack $stack, SignData $signData): array
478
    {
479 117
        $logicInterpreter = new BranchInterpreter();
480 117
        $tree = $logicInterpreter->getScriptTree($signScript->getScript());
481
482 117
        if ($tree->hasMultipleBranches()) {
483 11
            $logicalPath = $signData->getLogicalPath();
484
            // we need a function like findWitnessScript to 'check'
485
            // partial signatures against _our_ path
486
        } else {
487 106
            $logicalPath = [];
488
        }
489
490
        $scriptSections = $tree
491 117
            ->getBranchByPath($logicalPath)
492 117
            ->getScriptSections();
493
494 117
        $vfStack = new Stack();
495
496 117
        $pathCopy = $logicalPath;
497 117
        $steps = [];
498 117
        foreach ($scriptSections as $i => $scriptSection) {
499
            /** @var Operation[] $scriptSection */
500 117
            $fExec = !$this->interpreter->checkExec($vfStack, false);
501 117
            if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) {
502 11
                $op = $scriptSection[0];
503 11
                switch ($op->getOp()) {
504
                    case Opcodes::OP_IF:
505
                    case Opcodes::OP_NOTIF:
506 11
                        $value = false;
507 11
                        if ($fExec) {
508
                            // Pop from mainStack if $fExec
509 11
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
510
511
                            // the Conditional has a value in this case:
512 11
                            $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 11
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
518 4
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
519 4
                                    $step->providedBy($steps[$j]);
520 4
                                    break;
521
                                }
522
                            }
523
                        } else {
524 1
                            $step = new Conditional($op->getOp());
525
                        }
526
527 11
                        $steps[] = $step;
528
529 11
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
530 5
                            $value = !$value;
0 ignored issues
show
introduced by
The condition $value is always false.
Loading history...
531
                        }
532
533 11
                        $vfStack->push($value);
534 11
                        break;
535
                    case Opcodes::OP_ENDIF:
536 11
                        $vfStack->pop();
537 11
                        break;
538
                    case Opcodes::OP_ELSE:
539 9
                        $vfStack->push(!$vfStack->pop());
540 11
                        break;
541
                }
542
            } else {
543 117
                $templateTypes = $this->parseSequence($scriptSection);
544
545
                // Detect if effect on mainStack is `false`
546 117
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
547 117
                if ($resolvesFalse) {
548 2
                    if (count($templateTypes) > 1) {
549
                        throw new UnsupportedScript("Unsupported script, multiple steps to segment which is negated");
550
                    }
551
                }
552
553 117
                foreach ($templateTypes as $k => $checksig) {
554 117
                    if ($fExec) {
555 117
                        if ($checksig instanceof Checksig) {
556 107
                            $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 102
                            if ($resolvesFalse) {
562 102
                                $checksig->setRequired(false);
563
                            }
564 15
                        } else if ($checksig instanceof TimeLock) {
565 15
                            $this->checkTimeLock($checksig);
566
                        }
567
568 102
                        $steps[] = $checksig;
569
                    }
570
                }
571
            }
572
        }
573
574 102
        return $steps;
575
    }
576
577
    /**
578
     * @param int $verify
579
     * @param int $input
580
     * @param int $threshold
581
     * @return int
582
     */
583 5
    private function compareRangeAgainstThreshold($verify, $input, $threshold)
584
    {
585 5
        if ($verify <= $threshold && $input > $threshold) {
586 2
            return -1;
587
        }
588
589 3
        if ($verify > $threshold && $input <= $threshold) {
590 2
            return 1;
591
        }
592
593 1
        return 0;
594
    }
595
596
    /**
597
     * @param TimeLock $timelock
598
     */
599 15
    public function checkTimeLock(TimeLock $timelock)
600
    {
601 15
        $info = $timelock->getInfo();
602 15
        if (($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY) != 0 && $info instanceof CheckLocktimeVerify) {
603 8
            $verifyLocktime = $info->getLocktime();
604 8
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
605 5
                $input = $this->tx->getInput($this->nInput);
606 5
                if ($input->isFinal()) {
607 2
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
608
                }
609
610 3
                $locktime = $this->tx->getLockTime();
611 3
                $cmp = $this->compareRangeAgainstThreshold($verifyLocktime, $locktime, Locktime::BLOCK_MAX);
612 3
                if ($cmp === -1) {
613 1
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
614 2
                } else if ($cmp === 1) {
615 1
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
616
                }
617
618 1
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
619 1
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
620
            }
621
        }
622
623 10
        if (($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY) != 0 && $info instanceof CheckSequenceVerify) {
624
            // Future soft-fork extensibility, NOP if disabled flag
625 7
            if (($info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
626
                return;
627
            }
628
629 7
            if (!$this->signatureChecker->checkSequence(Number::int($info->getRelativeLockTime()))) {
630 5
                if ($this->tx->getVersion() < 2) {
631 2
                    throw new \RuntimeException("Transaction version must be 2 or greater for CSV");
632
                }
633
634 3
                $input = $this->tx->getInput($this->nInput);
635 3
                if ($input->isFinal()) {
636 1
                    throw new \RuntimeException("Sequence LOCKTIME_DISABLE_FLAG is set - not allowed on CSV output");
637
                }
638
639 2
                $cmp = $this->compareRangeAgainstThreshold($info->getRelativeLockTime(), $input->getSequence(), TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG);
640 2
                if ($cmp === -1) {
641 1
                    throw new \RuntimeException("CSV was for block height, but txin sequence was in timestamp range");
642 1
                } else if ($cmp === 1) {
643 1
                    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 5
    }
652
653
    /**
654
     * @param ScriptInterface $script
655
     * @param BufferInterface $vchSig
656
     * @param BufferInterface $vchKey
657
     * @return bool
658
     */
659 35
    private function checkSignature(ScriptInterface $script, BufferInterface $vchSig, BufferInterface $vchKey)
660
    {
661
        try {
662 35
            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 107
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, int $sigVersion, bool $expectFalse)
683
    {
684 107
        $size = count($stack);
685
686 107
        if ($checksig->getType() === ScriptType::P2PKH) {
687 45
            if ($size > 1) {
688 35
                $vchPubKey = $stack->pop();
689 35
                $vchSig = $stack->pop();
690
691 35
                $value = false;
692 35
                if (!$expectFalse) {
693 35
                    $value = $this->checkSignature($script, $vchSig, $vchPubKey);
694
695 35
                    if (!$value) {
696 1
                        throw new SignerException('Existing signatures are invalid!');
697
                    }
698
                }
699
700 34
                if (!$checksig->isVerify()) {
701 34
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
702
                }
703
704 34
                if (!$expectFalse) {
705
                    $checksig
706 34
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
707 44
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
708
                    ;
709
                }
710
            }
711 66
        } else if ($checksig->getType() === ScriptType::P2PK) {
712 35
            if ($size > 0) {
713 28
                $vchSig = $stack->pop();
714
715 28
                $value = false;
716 28
                if (!$expectFalse) {
717 28
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $checksig->getSolution(), $this->fqs->sigVersion(), $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $checksig->getSolution() can also be of type BitWasp\Buffertools\BufferInterface[]; however, parameter $keyBuf of BitWasp\Bitcoin\Script\I...CheckerBase::checkSig() does only seem to accept BitWasp\Buffertools\BufferInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

717
                    $value = $this->signatureChecker->checkSig($script, $vchSig, /** @scrutinizer ignore-type */ $checksig->getSolution(), $this->fqs->sigVersion(), $this->flags);
Loading history...
718 28
                    if (!$value) {
719 1
                        throw new SignerException('Existing signatures are invalid!');
720
                    }
721
                }
722
723 27
                if (!$checksig->isVerify()) {
724 25
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
725
                }
726
727 27
                if (!$expectFalse) {
728 27
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
729
                }
730
            }
731
732 34
            $checksig->setKey(0, $this->parseStepPublicKey($checksig->getSolution()));
0 ignored issues
show
Bug introduced by
It seems like $checksig->getSolution() can also be of type BitWasp\Buffertools\BufferInterface[]; however, parameter $vchPubKey of BitWasp\Bitcoin\Transact...r::parseStepPublicKey() does only seem to accept BitWasp\Buffertools\BufferInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

732
            $checksig->setKey(0, $this->parseStepPublicKey(/** @scrutinizer ignore-type */ $checksig->getSolution()));
Loading history...
733 38
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
734
            /** @var Multisig $info */
735 38
            $info = $checksig->getInfo();
736 38
            $keyBuffers = $info->getKeyBuffers();
737 38
            foreach ($keyBuffers as $idx => $keyBuf) {
738 38
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
739
            }
740
741 36
            $value = false;
742 36
            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 14
                if ($size === 1 + $info->getKeyCount()) {
750 2
                    $sigBufCount = 0;
751 2
                    $null = new Buffer();
752 2
                    $keyToSigMap = new \SplObjectStorage();
753
754
                    // Reproduce $keyToSigMap and $sigBufCount
755 2
                    for ($i = 0; $i < $info->getKeyCount(); $i++) {
756 2
                        if (!$stack[-1 - $i]->equals($null)) {
757 2
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
758 2
                            $sigBufCount++;
759
                        }
760
                    }
761
762
                    // We observed $this->requiredSigs sigs, therefore we can
763
                    // say the implementation is incompatible
764 2
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
765 2
                        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 36
            if (!isset($toDelete) || !isset($keyToSigMap)) {
774
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
775 36
                $sigBufs = [];
776 36
                $max = min($checksig->getRequiredSigs(), $size - 1);
777 36
                for ($i = 0; $i < $max; $i++) {
778 31
                    $vchSig = $stack[-1 - $i];
779 31
                    $sigBufs[] = $vchSig;
780
                }
781
782 36
                $sigBufs = array_reverse($sigBufs);
783 36
                $sigBufCount = count($sigBufs);
784
785 36
                if (!$expectFalse) {
786 35
                    if ($sigBufCount > 0) {
787 30
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
788
                        // Here we learn if any signatures were invalid, it won't be in the map.
789 30
                        if ($sigBufCount !== count($keyToSigMap)) {
790 1
                            throw new SignerException('Existing signatures are invalid!');
791
                        }
792 29
                        $toDelete = 1 + count($keyToSigMap);
793
                    } else {
794 34
                        $toDelete = 0;
795 34
                        $keyToSigMap = new \SplObjectStorage();
796
                    }
797 34
                    $value = true;
798
                } else {
799
                    // todo: should check that all signatures are zero
800 1
                    $keyToSigMap = new \SplObjectStorage();
801 1
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
802 1
                    $value = false;
803
                }
804
            }
805
806 35
            while ($toDelete--) {
807 30
                $stack->pop();
808
            }
809
810 35
            foreach ($keyBuffers as $idx => $key) {
811 35
                if (isset($keyToSigMap[$key])) {
812 29
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
813
                }
814
            }
815
816 35
            if (!$checksig->isVerify()) {
817 35
                $stack->push($value ? new Buffer("\x01") : new Buffer());
818
            }
819
        } else {
820
            throw new UnsupportedScript('Unsupported output type passed to extractFromValues');
821
        }
822 102
    }
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 94
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, int $sigHashType, int $sigVersion): BufferInterface
834
    {
835 94
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
836 1
            throw new SignerException('Invalid sigHashType requested');
837
        }
838
839 93
        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 1
    public function getSigHash(int $sigHashType): BufferInterface
850
    {
851 1
        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 93
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, int $sigHashType, int $sigVersion)
865
    {
866 93
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
867 93
        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 62
    public function isFullySigned(): bool
876
    {
877 62
        foreach ($this->steps as $step) {
878 62
            if ($step instanceof Conditional) {
879
                if (!$step->hasValue()) {
880
                    return false;
881
                }
882 62
            } else if ($step instanceof Checksig) {
883 62
                if (!$step->isFullySigned()) {
884 56
                    return false;
885
                }
886
            }
887
        }
888
889 56
        return true;
890
    }
891
892
    /**
893
     * Returns the required number of signatures for this input.
894
     *
895
     * @return int
896
     */
897 62
    public function getRequiredSigs(): int
898
    {
899 62
        $count = 0;
900 62
        foreach ($this->steps as $step) {
901 62
            if ($step instanceof Checksig) {
902 62
                $count += $step->getRequiredSigs();
903
            }
904
        }
905 62
        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 62
    public function getSignatures(): array
915
    {
916 62
        return $this->steps[0]->getSignatures();
917
    }
918
919
    /**
920
     * Returns an array where the values are either null,
921
     * or a PublicKeyInterface.
922
     *
923
     * @return PublicKeyInterface[]
924
     */
925 51
    public function getPublicKeys(): array
926
    {
927 51
        return $this->steps[0]->getKeys();
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 50
    public function getInputScripts(): FullyQualifiedScript
937
    {
938 50
        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 99
    public function signStep(int $stepIdx, PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL)
949
    {
950 99
        if (!array_key_exists($stepIdx, $this->steps)) {
951
            throw new \RuntimeException("Unknown step index");
952
        }
953
954 99
        $checksig = $this->steps[$stepIdx];
955 99
        if (!($checksig instanceof Checksig)) {
956
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
957
        }
958
959 99
        if ($checksig->isFullySigned()) {
960
            return $this;
961
        }
962
963 99
        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 99
        $signScript = $this->fqs->signScript()->getScript();
968 99
        if ($checksig->getType() === ScriptType::P2PK) {
969 34
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
0 ignored issues
show
Bug introduced by
It seems like $checksig->getSolution() can also be of type BitWasp\Buffertools\BufferInterface[]; however, parameter $other of BitWasp\Buffertools\BufferInterface::equals() does only seem to accept BitWasp\Buffertools\BufferInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

969
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals(/** @scrutinizer ignore-type */ $checksig->getSolution())) {
Loading history...
970 2
                throw new \RuntimeException('Signing with the wrong private key');
971
            }
972
973 32
            if (!$checksig->hasSignature(0)) {
974 32
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
975 32
                $checksig->setSignature(0, $signature);
976
            }
977 73
        } else if ($checksig->getType() === ScriptType::P2PKH) {
978 42
            $publicKey = $privateKey->getPublicKey();
979 42
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
980 2
                throw new \RuntimeException('Signing with the wrong private key');
981
            }
982
983 40
            if (!$checksig->hasSignature(0)) {
984 40
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
985 40
                $checksig->setSignature(0, $signature);
986
            }
987
988 40
            if (!$checksig->hasKey(0)) {
989 40
                $checksig->setKey(0, $publicKey);
990
            }
991 33
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
992 33
            $signed = false;
993 33
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
994 33
                if (!$checksig->hasSignature($keyIdx)) {
995 33
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
996 31
                        $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
997 31
                        $checksig->setSignature($keyIdx, $signature);
998 31
                        $signed = true;
999
                    }
1000
                }
1001
            }
1002
1003 33
            if (!$signed) {
1004 33
                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 93
        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 76
    public function sign(PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL)
1022
    {
1023 76
        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 79
    public function verify(int $flags = null): bool
1033
    {
1034 79
        $consensus = ScriptFactory::consensus();
1035
1036 79
        if ($flags === null) {
1037 61
            $flags = $this->flags;
1038
        }
1039
1040 79
        $flags |= Interpreter::VERIFY_P2SH;
1041 79
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1042 31
            $flags |= Interpreter::VERIFY_WITNESS;
1043
        }
1044
1045 79
        $sig = $this->serializeSignatures();
1046
1047
        // Take serialized signatures, and use mutator to add this inputs sig data
1048 79
        $mutator = TransactionFactory::mutate($this->tx);
1049 79
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1050
1051 79
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1052 31
            $witness = [];
1053 31
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1054 31
                if ($i === $this->nInput) {
1055 31
                    $witness[] = $sig->getScriptWitness();
1056
                } else {
1057 2
                    $witness[] = new ScriptWitness();
1058
                }
1059
            }
1060
1061 31
            $mutator->witness($witness);
1062
        }
1063
1064 79
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1065
    }
1066
1067
    /**
1068
     * @return Stack
1069
     */
1070 93
    private function serializeSteps(): Stack
1071
    {
1072 93
        $results = [];
1073 93
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1074 93
            $step = $this->steps[$i];
1075
1076 93
            if ($step instanceof Conditional) {
1077 11
                $results[] = $step->serialize();
1078 93
            } else if ($step instanceof Checksig) {
1079 93
                if ($step->isRequired()) {
1080 93
                    if (count($step->getSignatures()) === 0) {
1081 50
                        break;
1082
                    }
1083
                }
1084
1085 93
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1086
1087 93
                if (!$step->isFullySigned()) {
1088 6
                    break;
1089
                }
1090
            }
1091
        }
1092
1093 93
        $values = [];
1094 93
        foreach (array_reverse($results) as $v) {
1095 93
            foreach ($v as $value) {
1096 93
                $values[] = $value;
1097
            }
1098
        }
1099
1100 93
        return new Stack($values);
1101
    }
1102
1103
    /**
1104
     * Produces a SigValues instance containing the scriptSig & script witness
1105
     *
1106
     * @return SigValues
1107
     */
1108 93
    public function serializeSignatures(): SigValues
1109
    {
1110 93
        return $this->fqs->encodeStack($this->serializeSteps());
1111
    }
1112
1113
    /**
1114
     * @return Checksig[]|Conditional[]|mixed
1115
     */
1116 50
    public function getSteps()
1117
    {
1118 50
        return $this->steps;
1119
    }
1120
}
1121