Completed
Pull Request — master (#581)
by thomas
73:38
created

InputSigner::calculateSigHashUnsafe()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

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

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

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

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

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

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

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

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

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

Loading history...
443 166
444 166
        if (!$mainStack->isEmpty()) {
445 166
            if (count($pathData) === 0) {
446 166
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
447 166
            }
448
449
            $opValue = $this->interpreter->castToBool($mainStack->pop());
0 ignored issues
show
Bug introduced by
It seems like $mainStack->pop() can be null; however, castToBool() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
450
            $dataValue = array_shift($pathData);
451 166
            if ($opValue !== $dataValue) {
452
                throw new \RuntimeException("Current stack doesn't follow branch path");
453
            }
454 166
        } else {
455 166
            if (count($pathData) === 0) {
456
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
457
            }
458
459 166
            $opValue = array_shift($pathData);
460
        }
461
462
        $conditional = new Conditional($operation->getOp());
463
464
        if ($opValue !== null) {
465
            if (!is_bool($opValue)) {
466
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
467
            }
468 22
469
            $conditional->setValue($opValue);
470 22
        }
471
472 22
        return $conditional;
473 22
    }
474
475
    /**
476
     * @param int $idx
477 22
     * @return Checksig|Conditional
478 22
     */
479 22
    public function step($idx)
480 22
    {
481
        if (!array_key_exists($idx, $this->steps)) {
482
            throw new \RuntimeException("Out of range index for input sign step");
483 18
        }
484
485
        return $this->steps[$idx];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->steps[$idx]; (BitWasp\Bitcoin\Transaction\Factory\Conditional) is incompatible with the return type declared by the interface BitWasp\Bitcoin\Transact...utSignerInterface::step of type array<BitWasp\Bitcoin\Tr...on\Factory\Conditional>.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
486
    }
487 18
488
    /**
489
     * @param OutputData $signScript
490 22
     * @param Stack $stack
491
     * @param SignData $signData
492 22
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
493 22
     * @throws \Exception
494
     */
495
    public function extractScript(OutputData $signScript, Stack $stack, SignData $signData)
496
    {
497 22
        $logicInterpreter = new BranchInterpreter();
498
        $tree = $logicInterpreter->getScriptTree($signScript->getScript());
499
500 22
        if ($tree->hasMultipleBranches()) {
501
            $logicalPath = $signData->getLogicalPath();
502
            // we need a function like findWitnessScript to 'check'
503
            // partial signatures against _our_ path
504
        } else {
505
            $logicalPath = [];
506
        }
507 36
508
        $scriptSections = $tree
509 36
            ->getBranchByPath($logicalPath)
510
            ->getScriptSections();
511
512
        $vfStack = new Stack();
513 36
514
        $pathCopy = $logicalPath;
515
        $steps = [];
516
        foreach ($scriptSections as $i => $scriptSection) {
517
            /** @var Operation[] $scriptSection */
518
            $fExec = !$this->interpreter->checkExec($vfStack, false);
519
            if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) {
520
                $op = $scriptSection[0];
521 166
                switch ($op->getOp()) {
522
                    case Opcodes::OP_IF:
523 166
                    case Opcodes::OP_NOTIF:
524 166
                        $value = false;
525
                        if ($fExec) {
526 166
                            // Pop from mainStack if $fExec
527 22
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
528
529
                            // the Conditional has a value in this case:
530
                            $value = $step->getValue();
531 144
532
                            // Connect the last operation (if there is one)
533
                            // with the last step with isRequired==$value
534
                            // todo: check this part out..
535 166
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
536 166
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
537
                                    $step->providedBy($steps[$j]);
538 166
                                    break;
539 166
                                }
540
                            }
541 166
                        } else {
542 166
                            $step = new Conditional($op->getOp());
543 166
                        }
544
545 166
                        $steps[] = $step;
546 166
547 22
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
548 22
                            $value = !$value;
549 22
                        }
550 22
551 22
                        $vfStack->push($value);
552 22
                        break;
553
                    case Opcodes::OP_ENDIF:
554 22
                        $vfStack->pop();
555
                        break;
556
                    case Opcodes::OP_ELSE:
557 22
                        $vfStack->push(!$vfStack->pop());
558
                        break;
559
                }
560
            } else {
561
                $templateTypes = $this->parseSequence($scriptSection);
562 22
563 8
                // Detect if effect on mainStack is `false`
564 8
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
565 8
                if ($resolvesFalse) {
566
                    if (count($templateTypes) > 1) {
567
                        throw new UnsupportedScript("Unsupported script, multiple steps to segment which is negated");
568
                    }
569 2
                }
570
571
                foreach ($templateTypes as $k => $checksig) {
572 22
                    if ($fExec) {
573
                        if ($checksig instanceof Checksig) {
574 22
                            $this->extractChecksig($signScript->getScript(), $checksig, $stack, $this->fqs->sigVersion(), $resolvesFalse);
575 10
576
                            // If this statement results is later consumed
577
                            // by a conditional which would be false, mark
578 22
                            // this operation as not required
579 22
                            if ($resolvesFalse) {
580 22
                                $checksig->setRequired(false);
581 22
                            }
582 22
                        } else if ($checksig instanceof TimeLock) {
583 18
                            $this->checkTimeLock($checksig);
584 18
                        }
585 22
586
                        $steps[] = $checksig;
587
                    }
588 166
                }
589
            }
590
        }
591 166
592 166
        $this->steps = $steps;
593 4
    }
594
595
    /**
596
     * @param int $verify
597
     * @param int $input
598 166
     * @param int $threshold
599 166
     * @return int
600 166
     */
601 146
    private function compareRangeAgainstThreshold($verify, $input, $threshold)
602
    {
603
        if ($verify <= $threshold && $input > $threshold) {
604
            return -1;
605
        }
606 136
607 136
        if ($verify > $threshold && $input <= $threshold) {
608
            return 1;
609 30
        }
610 30
611
        return 0;
612
    }
613 136
614
    /**
615
     * @param TimeLock $timelock
616
     */
617
    public function checkTimeLock(TimeLock $timelock)
618
    {
619 136
        $info = $timelock->getInfo();
620 136
        if (($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY) != 0 && $info instanceof CheckLocktimeVerify) {
621
            $verifyLocktime = $info->getLocktime();
622
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
623
                $input = $this->tx->getInput($this->nInput);
624
                if ($input->isFinal()) {
625
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
626
                }
627
628 10
                $locktime = $this->tx->getLockTime();
629
                $cmp = $this->compareRangeAgainstThreshold($verifyLocktime, $locktime, Locktime::BLOCK_MAX);
630 10
                if ($cmp === -1) {
631 4
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
632
                } else if ($cmp === 1) {
633
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
634 6
                }
635 4
636
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
637
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
638 2
            }
639
        }
640
641
        if (($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY) != 0 && $info instanceof CheckSequenceVerify) {
642
            // Future soft-fork extensibility, NOP if disabled flag
643
            if (($info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
644 30
                return;
645
            }
646 30
647 30
            if (!$this->signatureChecker->checkSequence(Number::int($info->getRelativeLockTime()))) {
648 16
                if ($this->tx->getVersion() < 2) {
649 16
                    throw new \RuntimeException("Transaction version must be 2 or greater for CSV");
650 10
                }
651 10
652 4
                $input = $this->tx->getInput($this->nInput);
653
                if ($input->isFinal()) {
654
                    throw new \RuntimeException("Sequence LOCKTIME_DISABLE_FLAG is set - not allowed on CSV output");
655 6
                }
656 6
657 6
                $cmp = $this->compareRangeAgainstThreshold($info->getRelativeLockTime(), $input->getSequence(), TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG);
658 2
                if ($cmp === -1) {
659 4
                    throw new \RuntimeException("CSV was for block height, but txin sequence was in timestamp range");
660 2
                } else if ($cmp === 1) {
661
                    throw new \RuntimeException("CSV was for timestamp, but txin sequence was in block range");
662
                }
663 2
664 2
                $masked = $info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_MASK;
665
                $requiredLock = "{$masked} " . ($info->isRelativeToBlock() ? " (blocks)" : "(seconds after txOut)");
666
                throw new \RuntimeException("Output unspendable with this sequence, must be locked for {$requiredLock}");
667
            }
668 20
        }
669
    }
670 14
671
    /**
672
     * This function is strictly for $canSign types.
673
     * It will extract signatures/publicKeys when given $outputData, and $stack.
674 14
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
675 10
     *
676 4
     * @param ScriptInterface $script
677
     * @param Checksig $checksig
678
     * @param Stack $stack
679 6
     * @param int $sigVersion
680 6
     * @param bool $expectFalse
681 2
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
682
     * @throws \Exception
683
     */
684 4
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, $sigVersion, $expectFalse)
685 4
    {
686 2
        $size = count($stack);
687 2
688 2
        if ($checksig->getType() === ScriptType::P2PKH) {
689
            if ($size > 1) {
690
                $vchPubKey = $stack->pop();
691
                $vchSig = $stack->pop();
692
693
                $value = false;
694
                if (!$expectFalse) {
695
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $vchPubKey, $this->fqs->sigVersion(), $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 691 can be null; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 690 can be null; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
696 10
                    if (!$value) {
697
                        throw new \RuntimeException('Existing signatures are invalid!');
698
                    }
699
                }
700
701
                if (!$checksig->isVerify()) {
702
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
703
                }
704
705
                if (!$expectFalse) {
706
                    $checksig
707
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 691 can be null; however, BitWasp\Bitcoin\Serializ...tureSerializer::parse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
708
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 690 can be null; however, BitWasp\Bitcoin\Transact...r::parseStepPublicKey() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
709 146
                    ;
710
                }
711 146
            }
712
        } else if ($checksig->getType() === ScriptType::P2PK) {
713 146
            if ($size > 0) {
714 42
                $vchSig = $stack->pop();
715 38
716 38
                $value = false;
717
                if (!$expectFalse) {
718 38
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $checksig->getSolution(), $this->fqs->sigVersion(), $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 714 can be null; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $checksig->getSolution() targeting BitWasp\Bitcoin\Transact...Checksig::getSolution() can also be of type array<integer,object<Bit...tools\BufferInterface>>; however, BitWasp\Bitcoin\Script\I...ter\Checker::checkSig() does only seem to accept object<BitWasp\Buffertools\BufferInterface>, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
719 38
                    if (!$value) {
720 38
                        throw new \RuntimeException('Existing signatures are invalid!');
721 38
                    }
722 2
                }
723
724
                if (!$checksig->isVerify()) {
725
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
726 36
                }
727 36
728
                if (!$expectFalse) {
729
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 714 can be null; however, BitWasp\Bitcoin\Serializ...tureSerializer::parse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
730 36
                }
731
            }
732 36
733 40
            $checksig->setKey(0, $this->parseStepPublicKey($checksig->getSolution()));
0 ignored issues
show
Bug introduced by
It seems like $checksig->getSolution() targeting BitWasp\Bitcoin\Transact...Checksig::getSolution() can also be of type array<integer,object<Bit...tools\BufferInterface>>; however, BitWasp\Bitcoin\Transact...r::parseStepPublicKey() does only seem to accept object<BitWasp\Buffertools\BufferInterface>, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
734
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
735
            /** @var Multisig $info */
736
            $info = $checksig->getInfo();
737 110
            $keyBuffers = $info->getKeyBuffers();
738 60
            foreach ($keyBuffers as $idx => $keyBuf) {
739 46
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
740
            }
741 46
742 46
            $value = false;
743 46
            if ($this->padUnsignedMultisigs) {
744 46
                // Multisig padding is only used for partially signed transactions,
745 2
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
746
                // values (including the dummy), with one for each candidate signature,
747
                // such that $this->signatures state is captured.
748
                // The feature serves to skip validation/sorting an incomplete multisig.
749 44
750 40
                if ($size === 1 + $info->getKeyCount()) {
751
                    $sigBufCount = 0;
752
                    $null = new Buffer();
753 44
                    $keyToSigMap = new \SplObjectStorage();
754 44
755
                    // Reproduce $keyToSigMap and $sigBufCount
756
                    for ($i = 0; $i < $info->getKeyCount(); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

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

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

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
757
                        if (!$stack[-1 - $i]->equals($null)) {
758 58
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
759 64
                            $sigBufCount++;
760
                        }
761 64
                    }
762 64
763 64
                    // We observed $this->requiredSigs sigs, therefore we can
764 64
                    // say the implementation is incompatible
765
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
766
                        throw new \RuntimeException("Padding is forbidden for a fully signed multisig script");
767 60
                    }
768 60
769
                    $toDelete = 1 + $info->getKeyCount();
770
                    $value = true;
771
                }
772
            }
773
774
            if (!isset($toDelete) || !isset($keyToSigMap)) {
775 28
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
776 4
                $sigBufs = [];
777 4
                $max = min($checksig->getRequiredSigs(), $size - 1);
778 4
                for ($i = 0; $i < $max; $i++) {
779
                    $vchSig = $stack[-1 - $i];
780
                    $sigBufs[] = $vchSig;
781 4
                }
782 4
783 4
                $sigBufs = array_reverse($sigBufs);
784 4
                $sigBufCount = count($sigBufs);
785
786
                if (!$expectFalse) {
787
                    if ($sigBufCount > 0) {
788
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
789
                        // Here we learn if any signatures were invalid, it won't be in the map.
790 4
                        if ($sigBufCount !== count($keyToSigMap)) {
791 4
                            throw new \RuntimeException('Existing signatures are invalid!');
792
                        }
793
                        $toDelete = 1 + count($keyToSigMap);
794
                    } else {
795
                        $toDelete = 0;
796
                        $keyToSigMap = new \SplObjectStorage();
797
                    }
798
                    $value = true;
799 60
                } else {
800
                    // should check that all signatures are zero
801 60
                    $keyToSigMap = new \SplObjectStorage();
802 60
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
803 60
                    $value = false;
804 52
                }
805 52
            }
806
807
            while ($toDelete--) {
808 60
                $stack->pop();
809 60
            }
810
811 60
            foreach ($keyBuffers as $idx => $key) {
812 58
                if (isset($keyToSigMap[$key])) {
813 50
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
814
                }
815 50
            }
816 2
817
            if (!$checksig->isVerify()) {
818 48
                $stack->push($value ? new Buffer("\x01") : new Buffer());
819
            }
820 56
        } else {
821 56
            throw new UnsupportedScript('Unsupported output type passed to extractFromValues');
822
        }
823 56
    }
824
825
    /**
826 2
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
827 2
     *
828 2
     * @param ScriptInterface $scriptCode
829
     * @param int $sigHashType
830
     * @param int $sigVersion
831
     * @return BufferInterface
832 58
     */
833 50
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
834
    {
835
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
836 58
            throw new \RuntimeException('Invalid sigHashType requested');
837 58
        }
838 58
839
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
840
    }
841
842 58
    /**
843 58
     * Calculates the signature hash for the input for the given $sigHashType.
844
     *
845
     * @param int $sigHashType
846
     * @return BufferInterface
847
     */
848 136
    public function getSigHash($sigHashType)
849
    {
850
        return $this->calculateSigHashUnsafe($this->fqs->signScript()->getScript(), $sigHashType, $this->fqs->sigVersion());
851
    }
852
853
    /**
854
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
855
     *
856
     * @param PrivateKeyInterface $key
857
     * @param ScriptInterface $scriptCode
858
     * @param int $sigHashType
859 74
     * @param int $sigVersion
860
     * @return TransactionSignatureInterface
861 74
     */
862 36
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
863 36
    {
864 36
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
865 36
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
866
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
867
    }
868
869 70
    /**
870 2
     * Returns whether all required signatures have been provided.
871
     *
872 68
     * @return bool
873
     */
874
    public function isFullySigned()
875 70
    {
876
        foreach ($this->steps as $step) {
877
            if ($step instanceof Conditional) {
878
                if (!$step->hasValue()) {
879
                    return false;
880
                }
881
            } else if ($step instanceof Checksig) {
882
                if (!$step->isFullySigned()) {
883
                    return false;
884
                }
885
            }
886
        }
887 70
888
        return true;
889 70
    }
890 32
891 32
    /**
892 32
     * Returns the required number of signatures for this input.
893 32
     *
894
     * @return int
895
     */
896
    public function getRequiredSigs()
897 66
    {
898 4
        $count = 0;
899
        foreach ($this->steps as $step) {
900 62
            if ($step instanceof Checksig) {
901
                $count += $step->getRequiredSigs();
902
            }
903 62
        }
904
        return $count;
905
    }
906
907
    /**
908
     * Returns an array where the values are either null,
909
     * or a TransactionSignatureInterface.
910
     *
911
     * @return TransactionSignatureInterface[]
912
     */
913
    public function getSignatures()
914
    {
915
        return $this->steps[0]->getSignatures();
0 ignored issues
show
Bug introduced by
The method getSignatures() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

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

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

Loading history...
916
    }
917
918
    /**
919
     * Returns an array where the values are either null,
920 188
     * or a PublicKeyInterface.
921
     *
922 188
     * @return PublicKeyInterface[]
923 188
     */
924 188
    public function getPublicKeys()
925
    {
926 188
        return $this->steps[0]->getKeys();
0 ignored issues
show
Bug introduced by
The method getKeys() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

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

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

Loading history...
927 122
    }
928 2
929
    /**
930
     * Returns a FullyQualifiedScript since we
931
     * have solved all scripts to do with this input
932 186
     *
933
     * @return FullyQualifiedScript
934 186
     */
935 74
    public function getInputScripts()
936 70
    {
937 2
        return $this->fqs;
938
    }
939
940 68
    /**
941 68
     * @param int $stepIdx
942 38
     * @param PrivateKeyInterface $privateKey
943 2
     * @param int $sigHashType
944
     * @return $this
945
     */
946
    public function signStep($stepIdx, PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
947 66
    {
948
        if (!array_key_exists($stepIdx, $this->steps)) {
949
            throw new \RuntimeException("Unknown step index");
950 178
        }
951 8
952 8
        $checksig = $this->steps[$stepIdx];
953 8
954 172
        if (!($checksig instanceof Checksig)) {
955 70
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
956 70
        }
957
958
        if ($checksig->isFullySigned()) {
959 62
            return $this;
960 2
        }
961
962
        if (SigHash::V1 === $this->fqs->sigVersion() && !$privateKey->isCompressed()) {
963 60
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
964 60
        }
965 30
966 2
        $signScript = $this->fqs->signScript()->getScript();
967
        if ($checksig->getType() === ScriptType::P2PK) {
968
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
969
                throw new \RuntimeException('Signing with the wrong private key');
970 58
            }
971
972
            if (!$checksig->hasSignature(0)) {
973 166
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
974 166
                $checksig->setSignature(0, $signature);
975
            }
976 166
        } else if ($checksig->getType() === ScriptType::P2PKH) {
977
            $publicKey = $privateKey->getPublicKey();
978 136
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
979
                throw new \RuntimeException('Signing with the wrong private key');
980
            }
981
982
            if (!$checksig->hasSignature(0)) {
983
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
984
                $checksig->setSignature(0, $signature);
985
            }
986
987
            if (!$checksig->hasKey(0)) {
988
                $checksig->setKey(0, $publicKey);
989 128
            }
990
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
991 128
            $signed = false;
992 2
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
993
                if (!$checksig->hasSignature($keyIdx)) {
994
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
995 126
                        $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
996
                        $checksig->setSignature($keyIdx, $signature);
997
                        $signed = true;
998
                    }
999
                }
1000
            }
1001
1002
            if (!$signed) {
1003
                throw new \RuntimeException('Signing with the wrong private key');
1004 2
            }
1005
        } else {
1006 2
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1007
        }
1008
1009
        return $this;
1010
    }
1011
1012
    /**
1013
     * Sign the input using $key and $sigHashTypes
1014
     *
1015
     * @param PrivateKeyInterface $privateKey
1016
     * @param int $sigHashType
1017
     * @return $this
1018 126
     */
1019
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1020 126
    {
1021 126
        return $this->signStep(0, $privateKey, $sigHashType);
1022 126
    }
1023
1024
    /**
1025
     * Verifies the input using $flags for script verification
1026
     *
1027
     * @param int $flags
1028
     * @return bool
1029
     */
1030 74
    public function verify($flags = null)
1031
    {
1032 74
        $consensus = ScriptFactory::consensus();
1033 74
1034
        if ($flags === null) {
1035
            $flags = $this->flags;
1036
        }
1037 74
1038 74
        $flags |= Interpreter::VERIFY_P2SH;
1039 74
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1040
            $flags |= Interpreter::VERIFY_WITNESS;
1041
        }
1042
1043
        $sig = $this->serializeSignatures();
1044 62
1045
        // Take serialized signatures, and use mutator to add this inputs sig data
1046
        $mutator = TransactionFactory::mutate($this->tx);
1047
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1048
1049
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1050
            $witness = [];
1051
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1052 74
                if ($i === $this->nInput) {
1053
                    $witness[] = $sig->getScriptWitness();
1054 74
                } else {
1055 74
                    $witness[] = new ScriptWitness([]);
1056 74
                }
1057 74
            }
1058
1059
            $mutator->witness($witness);
1060 74
        }
1061
1062
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1063
    }
1064
1065
    /**
1066
     * @return Stack
1067
     */
1068
    private function serializeSteps()
1069 74
    {
1070
        $results = [];
1071 74
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1072
            $step = $this->steps[$i];
1073
1074
            if ($step instanceof Conditional) {
1075
                $results[] = $step->serialize();
1076
            } else if ($step instanceof Checksig) {
1077
                if ($step->isRequired()) {
1078
                    if (count($step->getSignatures()) === 0) {
1079
                        break;
1080 52
                    }
1081
                }
1082 52
1083
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1084
1085
                if (!$step->isFullySigned()) {
1086
                    break;
1087
                }
1088
            }
1089
        }
1090
1091
        $values = [];
1092 50
        foreach (array_reverse($results) as $v) {
1093
            foreach ($v as $value) {
1094 50
                $values[] = $value;
1095
            }
1096
        }
1097
1098
        return new Stack($values);
1099
    }
1100
1101
    /**
1102 24
     * Produces a SigValues instance containing the scriptSig & script witness
1103
     *
1104 24
     * @return SigValues
1105
     */
1106
    public function serializeSignatures()
1107
    {
1108
        return $this->fqs->encodeStack($this->serializeSteps());
1109
    }
1110
1111
    public function getSteps()
1112 18
    {
1113
        return $this->steps;
1114 18
    }
1115
}
1116