Completed
Push — master ( e12674...2b8220 )
by thomas
28:40
created

InputSigner::extract()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6.0493

Importance

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