InputSigner::extractChecksig()   F
last analyzed

Complexity

Conditions 33
Paths 317

Size

Total Lines 139
Code Lines 79

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 73
CRAP Score 33.0671

Importance

Changes 0
Metric Value
cc 33
eloc 79
nc 317
nop 5
dl 0
loc 139
ccs 73
cts 76
cp 0.9605
crap 33.0671
rs 1.8708
c 0
b 0
f 0

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