Completed
Pull Request — master (#525)
by thomas
26:53
created

InputSigner::classifySignStep()   B

Complexity

Conditions 4
Paths 15

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 15
nop 2
dl 0
loc 25
ccs 14
cts 14
cp 1
crap 4
rs 8.5806
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
            $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());
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 ScriptInterface $script
414
     * @return Checksig[]
415
     */
416 136
    public function parseSequence(ScriptInterface $script)
417
    {
418 136
        $decoded = $script->getScriptParser()->decode();
419
420 136
        $j = 0;
421 136
        $l = count($decoded);
422 136
        $result = [];
423 136
        while ($j < $l) {
424 136
            $step = null;
425 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...
426
427
            // increment the $last, and break if it's valid
428 136
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
429 136
                $slice = array_slice($decoded, $j, $i);
430 136
                $step = $this->classifySignStep($slice, $solution);
431 136
                if ($step !== null) {
432 136
                    break;
433
                }
434
            }
435
436 136
            if (null === $step) {
437
                throw new \RuntimeException("Invalid script");
438
            } else {
439 136
                $j += $i;
440 136
                $result[] = new Checksig($step, $this->txSigSerializer, $this->pubKeySerializer);
441
            }
442
        }
443
444 136
        return $result;
445
    }
446
447
    /**
448
     * @param Operation $operation
449
     * @param Stack $mainStack
450
     * @param bool[] $pathData
451
     * @return Conditional
452
     */
453 22
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData)
454
    {
455 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...
456
457 22
        if (!$mainStack->isEmpty()) {
458 22
            if (count($pathData) === 0) {
459
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
460
            }
461
462 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...
463 22
            $dataValue = array_shift($pathData);
464 22
            if ($opValue !== $dataValue) {
465 22
                throw new \RuntimeException("Current stack doesn't follow branch path");
466
            }
467
        } else {
468 18
            if (count($pathData) === 0) {
469
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
470
            }
471
472 18
            $opValue = array_shift($pathData);
473
        }
474
475 22
        $conditional = new Conditional($operation->getOp());
476
477 22
        if ($opValue !== null) {
478 22
            if (!is_bool($opValue)) {
479
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
480
            }
481
482 22
            $conditional->setValue($opValue);
483
        }
484
485 22
        return $conditional;
486
    }
487
488
    /**
489
     * @param int $idx
490
     * @return Checksig|Conditional
491
     */
492 36
    public function step($idx)
493
    {
494 36
        if (!array_key_exists($idx, $this->steps)) {
495
            throw new \RuntimeException("Out of range index for input sign step");
496
        }
497
498 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...
499
    }
500
501
    /**
502
     * @param OutputData $solution
503
     * @param array $sigChunks
504
     * @param SignData $signData
505
     */
506 136
    public function extractScript(OutputData $solution, array $sigChunks, SignData $signData)
507
    {
508 136
        $logicInterpreter = new BranchInterpreter();
509 136
        $tree = $logicInterpreter->getScriptTree($solution->getScript());
510
511 136
        if ($tree->hasMultipleBranches()) {
512 22
            $logicalPath = $signData->getLogicalPath();
513
            // we need a function like findWitnessScript to 'check'
514
            // partial signatures against _our_ path
515
        } else {
516 114
            $logicalPath = [];
517
        }
518
519 136
        $branch = $tree->getBranchByDesc($logicalPath);
520 136
        $segments = $branch->getSegments();
521
522 136
        $vfStack = new Stack();
523 136
        $stack = new Stack($sigChunks);
524
525 136
        $pathCopy = $logicalPath;
526 136
        $steps = [];
527 136
        foreach ($segments as $i => $segment) {
528 136
            $fExec = !$this->interpreter->checkExec($vfStack, false);
529 136
            if ($segment->isLoneLogicalOp()) {
530 22
                $op = $segment[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 22
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
545 8
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
546 8
                                    $step->providedBy($steps[$j]);
547 8
                                    break;
548
                                }
549
                            }
550
                        } else {
551 2
                            $step = new Conditional($op->getOp());
552
                        }
553
554 22
                        $steps[] = $step;
555
556 22
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
557 10
                            $value = !$value;
558
                        }
559
560 22
                        $vfStack->push($value);
561 22
                        break;
562 22
                    case Opcodes::OP_ENDIF:
563 22
                        $vfStack->pop();
564 22
                        break;
565 18
                    case Opcodes::OP_ELSE:
566 18
                        $vfStack->push(!$vfStack->pop());
567 22
                        break;
568
                }
569
            } else {
570 136
                $segmentScript = $segment->makeScript();
571 136
                $templateTypes = $this->parseSequence($segmentScript);
572
573
                // Detect if effect on vfStack 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->setRequired(false);
634
                } else {
635
                    $checksig
636 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...
637 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...
638
                    ;
639
                }
640
            }
641 100
        } else if ($checksig->getType() === ScriptType::P2PK) {
642 50
            if ($size > 0) {
643 46
                $vchSig = $stack->pop();
644
645 46
                $value = false;
646 46
                if (!$expectFalse) {
647 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 643 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...
648 46
                    if (!$value) {
649 2
                        throw new \RuntimeException('Existing signatures are invalid!');
650
                    }
651
                }
652
653 44
                if (!$checksig->isVerify()) {
654 40
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
655
                }
656
657 44
                if ($expectFalse) {
658 2
                    $checksig->setRequired(false);
659
                } else {
660 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 643 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...
661
                }
662
            }
663
664 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...
665 64
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
666
            /** @var Multisig $info */
667 64
            $info = $checksig->getInfo();
668 64
            $keyBuffers = $info->getKeyBuffers();
669 64
            foreach ($keyBuffers as $idx => $keyBuf) {
670 64
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
671
            }
672
673 60
            if ($this->padUnsignedMultisigs) {
674
                // Multisig padding is only used for partially signed transactions,
675
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
676
                // values (including the dummy), with one for each candidate signature,
677
                // such that $this->signatures state is captured.
678
                // The feature serves to skip validation/sorting an incomplete multisig.
679
680 28
                if ($size === 1 + $info->getKeyCount()) {
681 4
                    $sigBufCount = 0;
682 4
                    $null = new Buffer();
683 4
                    $keyToSigMap = new \SplObjectStorage();
684
685
                    // Reproduce $keyToSigMap and $sigBufCount
686 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...
687 4
                        if (!$stack[-1 - $i]->equals($null)) {
688 4
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
689 4
                            $sigBufCount++;
690
                        }
691
                    }
692
693
                    // We observed $this->requiredSigs sigs, therefore we can
694
                    // say the implementation is incompatible
695 4
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
696 4
                        throw new \RuntimeException("Padding is forbidden for a fully signed multisig script");
697
                    }
698
699
                    $toDelete = 1 + $info->getKeyCount();
700
                }
701
            }
702
703 60
            if (!isset($keyToSigMap)) {
704
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
705 60
                $sigBufs = [];
706 60
                $max = min($checksig->getRequiredSigs(), $size - 1);
707 60
                for ($i = 0; $i < $max; $i++) {
708 52
                    $vchSig = $stack[-1 - $i];
709 52
                    $sigBufs[] = $vchSig;
710
                }
711
712 60
                $sigBufs = array_reverse($sigBufs);
713 60
                $sigBufCount = count($sigBufs);
714
715 60
                if (!$expectFalse) {
716 58
                    if ($sigBufCount > 0) {
717 50
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
718
                        // Here we learn if any signatures were invalid, it won't be in the map.
719 50
                        if ($sigBufCount !== count($keyToSigMap)) {
720 2
                            throw new \RuntimeException('Existing signatures are invalid!');
721
                        }
722 48
                        $toDelete = 1 + count($keyToSigMap);
723
                    } else {
724 56
                        $toDelete = 0;
725
                    }
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
                }
731
            }
732
            
733 58
            while ($toDelete--) {
0 ignored issues
show
Bug introduced by
The variable $toDelete does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
734 50
                $stack->pop();
735
            }
736
737 58
            foreach ($keyBuffers as $idx => $key) {
738 58
                if (isset($keyToSigMap[$key])) {
739 58
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
740
                }
741
            }
742
743 58
            $value = !$expectFalse;
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 122
        if ($checksig->isFullySigned()) {
0 ignored issues
show
Bug introduced by
The method isFullySigned() 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...
1085
            return $this;
1086
        }
1087
1088 122
        if (SigHash::V1 === $this->sigVersion && !$privateKey->isCompressed()) {
1089
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
1090
        }
1091
1092 122
        if ($checksig->getType() === ScriptType::P2PK) {
0 ignored issues
show
Bug introduced by
The method getType() 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...
1093 48
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
0 ignored issues
show
Bug introduced by
The method getSolution() 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...
1094 2
                throw new \RuntimeException('Signing with the wrong private key');
1095
            }
1096
1097 46
            if (!$checksig->hasSignature(0)) {
0 ignored issues
show
Bug introduced by
The method hasSignature() 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...
1098 46
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1099 46
                $checksig->setSignature(0, $signature);
0 ignored issues
show
Bug introduced by
The method setSignature() 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...
1100
            }
1101 88
        } else if ($checksig->getType() === ScriptType::P2PKH) {
0 ignored issues
show
Bug introduced by
The method getType() 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...
1102 38
            $publicKey = $privateKey->getPublicKey();
1103 38
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
0 ignored issues
show
Bug introduced by
The method getSolution() 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...
1104 2
                throw new \RuntimeException('Signing with the wrong private key');
1105
            }
1106
1107 36
            if (!$checksig->hasSignature(0)) {
0 ignored issues
show
Bug introduced by
The method hasSignature() 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...
1108 36
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1109 36
                $checksig->setSignature(0, $signature);
0 ignored issues
show
Bug introduced by
The method setSignature() 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...
1110
            }
1111
1112 36
            if (!$checksig->hasKey(0)) {
0 ignored issues
show
Bug introduced by
The method hasKey() 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...
1113 36
                $checksig->setKey(0, $publicKey);
0 ignored issues
show
Bug introduced by
The method setKey() 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...
1114
            }
1115 54
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
0 ignored issues
show
Bug introduced by
The method getType() 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...
1116 54
            $signed = false;
1117 54
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
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...
1118 54
                if (!$checksig->hasSignature($keyIdx)) {
0 ignored issues
show
Bug introduced by
The method hasSignature() 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...
1119 54
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
1120 52
                        $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1121 52
                        $checksig->setSignature($keyIdx, $signature);
0 ignored issues
show
Bug introduced by
The method setSignature() 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...
1122 54
                        $signed = true;
1123
                    }
1124
                }
1125
            }
1126
1127 54
            if (!$signed) {
1128 54
                throw new \RuntimeException('Signing with the wrong private key');
1129
            }
1130
        } else {
1131
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1132
        }
1133
1134 116
        return $this;
1135
    }
1136
1137
    /**
1138
     * Sign the input using $key and $sigHashTypes
1139
     *
1140
     * @param PrivateKeyInterface $privateKey
1141
     * @param int $sigHashType
1142
     * @return $this
1143
     */
1144 86
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1145
    {
1146 86
        return $this->signStep(0, $privateKey, $sigHashType);
1147
    }
1148
1149
    /**
1150
     * Verifies the input using $flags for script verification
1151
     *
1152
     * @param int $flags
1153
     * @return bool
1154
     */
1155 86
    public function verify($flags = null)
1156
    {
1157 86
        $consensus = ScriptFactory::consensus();
1158
1159 86
        if ($flags === null) {
1160 50
            $flags = $this->flags;
1161
        }
1162
1163 86
        $flags |= Interpreter::VERIFY_P2SH;
1164 86
        if (SigHash::V1 === $this->sigVersion) {
1165 22
            $flags |= Interpreter::VERIFY_WITNESS;
1166
        }
1167
1168 86
        $sig = $this->serializeSignatures();
1169
1170
        // Take serialized signatures, and use mutator to add this inputs sig data
1171 86
        $mutator = TransactionFactory::mutate($this->tx);
1172 86
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1173
1174 86
        if (SigHash::V1 === $this->sigVersion) {
1175 22
            $witness = [];
1176 22
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1177 22
                if ($i === $this->nInput) {
1178 22
                    $witness[] = $sig->getScriptWitness();
1179
                } else {
1180 2
                    $witness[] = new ScriptWitness([]);
1181
                }
1182
            }
1183
1184 22
            $mutator->witness($witness);
1185
        }
1186
1187 86
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1188
    }
1189
1190
    /**
1191
     * @return array
1192
     */
1193 116
    private function serializeSteps()
1194
    {
1195 116
        $results = [];
1196 116
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1197 116
            $step = $this->steps[$i];
1198
1199 116
            if ($step instanceof Conditional) {
1200 22
                $results[] = $step->serialize();
1201 116
            } else if ($step instanceof Checksig) {
1202 116
                if ($step->isRequired()) {
1203 116
                    if (count($step->getSignatures()) === 0) {
1204 50
                        break;
1205
                    }
1206
                }
1207
1208 116
                $results[] = $step->serialize();
1209
1210 116
                if (!$step->isFullySigned()) {
1211 12
                    break;
1212
                }
1213
            }
1214
        }
1215
1216 116
        $values = [];
1217 116
        foreach (array_reverse($results) as $v) {
1218 116
            foreach ($v as $value) {
1219 116
                $values[] = $value;
1220
            }
1221
        }
1222
1223 116
        return $values;
1224
    }
1225
1226
    /**
1227
     * Produces a SigValues instance containing the scriptSig & script witness
1228
     *
1229
     * @return SigValues
1230
     */
1231 116
    public function serializeSignatures()
1232
    {
1233 116
        static $emptyScript = null;
1234 116
        static $emptyWitness = null;
1235 116
        if (is_null($emptyScript) || is_null($emptyWitness)) {
1236 2
            $emptyScript = new Script();
1237 2
            $emptyWitness = new ScriptWitness([]);
1238
        }
1239
1240 116
        $witness = [];
1241 116
        $scriptSigChunks = $this->serializeSteps();
1242
1243 116
        $solution = $this->scriptPubKey;
1244 116
        $p2sh = false;
1245 116
        if ($solution->getType() === ScriptType::P2SH) {
1246 32
            $p2sh = true;
1247 32
            $solution = $this->redeemScript;
1248
        }
1249
1250 116
        if ($solution->getType() === ScriptType::P2WKH) {
1251 8
            $witness = $scriptSigChunks;
1252 8
            $scriptSigChunks = [];
1253 110
        } else if ($solution->getType() === ScriptType::P2WSH) {
1254 28
            $witness = $scriptSigChunks;
1255 28
            $witness[] = $this->witnessScript->getScript()->getBuffer();
1256 28
            $scriptSigChunks = [];
1257
        }
1258
1259 116
        if ($p2sh) {
1260 32
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
1261
        }
1262
1263 116
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
1264
    }
1265
1266 50
    public function getSteps()
1267
    {
1268 50
        return $this->steps;
1269
    }
1270
}
1271