Completed
Pull Request — master (#525)
by thomas
71:08
created

InputSigner::sortMultisigs()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5.005

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 5
nop 4
dl 0
loc 32
ccs 16
cts 17
cp 0.9412
crap 5.005
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
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
180
    {
181
        $this->ecAdapter = $ecAdapter;
182
        $this->tx = $tx;
183
        $this->nInput = $nInput;
184
        $this->txOut = $txOut;
185 124
        $this->signData = $signData;
186
187 124
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
188 124
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
189 124
        $this->interpreter = new Interpreter($this->ecAdapter);
190 124
    }
191 124
192 124
    /**
193 124
     * @return InputSigner
194
     */
195 124
    public function extract()
196 124
    {
197 124
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
198 124
        $checker = new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
199
200
        if ($this->redeemBitcoinCash) {
201
            // unset VERIFY_WITNESS default
202
            $defaultFlags = $defaultFlags & (~Interpreter::VERIFY_WITNESS);
203 124
204
            if ($this->signData->hasSignaturePolicy()) {
205 124
                if ($this->signData->getSignaturePolicy() & Interpreter::VERIFY_WITNESS) {
206 124
                    throw new \RuntimeException("VERIFY_WITNESS is not possible for bitcoin cash");
207
                }
208 124
            }
209
210 2
            $checker = new BitcoinCashChecker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
211
        }
212 2
213
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
214
        $this->signatureChecker = $checker;
215
216
        $witnesses = $this->tx->getWitnesses();
217
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput]->all() : [];
218 2
219
        return $this->solve(
220
            $this->signData,
221 124
            $this->txOut->getScript(),
222 124
            $this->tx->getInput($this->nInput)->getScript(),
223
            $witness
224 124
        );
225 124
    }
226
227 124
    /**
228 124
     * @param bool $setting
229 124
     * @return $this
230 124
     */
231
    public function padUnsignedMultisigs($setting)
232
    {
233
        $this->padUnsignedMultisigs = (bool) $setting;
234
        return $this;
235
    }
236
237
    /**
238
     * @param bool $setting
239 94
     * @return $this
240
     */
241 94
    public function tolerateInvalidPublicKey($setting)
242 94
    {
243
        $this->tolerateInvalidPublicKey = (bool) $setting;
244
        return $this;
245
    }
246
247
    /**
248
     * @param bool $setting
249 94
     * @return $this
250
     */
251 94
    public function redeemBitcoinCash($setting)
252 94
    {
253
        $this->redeemBitcoinCash = (bool) $setting;
254
        return $this;
255
    }
256
257
    /**
258
     * @param bool $setting
259 94
     * @return $this
260
     */
261 94
    public function allowComplexScripts($setting)
262 94
    {
263
        $this->allowComplexScripts = (bool) $setting;
264
        return $this;
265
    }
266
267
    /**
268
     * @param BufferInterface $vchPubKey
269
     * @return PublicKeyInterface|null
270 92
     * @throws \Exception
271
     */
272
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
273 92
    {
274 6
        try {
275 6
            return $this->pubKeySerializer->parse($vchPubKey);
276 2
        } catch (\Exception $e) {
277
            if ($this->tolerateInvalidPublicKey) {
278
                return null;
279 4
            }
280
281
            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 22
     */
294
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
295 22
    {
296 22
        $sigCount = count($signatures);
297 22
        $keyCount = count($publicKeys);
298 22
        $ikey = $isig = 0;
299 22
        $fSuccess = true;
300
        $result = new \SplObjectStorage;
301 22
302
        while ($fSuccess && $sigCount > 0) {
303 22
            // Fetch the signature and public key
304 22
            $sig = $signatures[$isig];
305
            $pubkey = $publicKeys[$ikey];
306 22
307 22
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
308 22
                $result[$pubkey] = $sig;
309 22
                $isig++;
310
                $sigCount--;
311
            }
312 22
313 22
            $ikey++;
314
            $keyCount--;
315
316
            // If there are more signatures left than keys left,
317
            // then too many signatures have failed. Exit early,
318 22
            // without checking any further signatures.
319
            if ($sigCount > $keyCount) {
320
                $fSuccess = false;
321
            }
322
        }
323 22
324
        return $result;
325
    }
326
327
    /**
328
     * @param ScriptInterface $script
329
     * @return \BitWasp\Buffertools\BufferInterface[]
330 90
     */
331
    private function evalPushOnly(ScriptInterface $script)
332 90
    {
333 90
        $stack = new Stack();
334 90
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
335
        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 80
    {
347 58
        return ScriptFactory::sequence(array_map(function ($buffer) {
348
            if (!($buffer instanceof BufferInterface)) {
349
                throw new \RuntimeException('Script contained a non-push opcode');
350
            }
351 58
352 58
            $size = $buffer->getSize();
353 18
            if ($size === 0) {
354
                return Opcodes::OP_0;
355
            }
356 58
357 58
            $first = ord($buffer->getBinary());
358
            if ($size === 1 && $first >= 1 && $first <= 16) {
359
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
360 58
            } else {
361
                return $buffer;
362 80
            }
363
        }, $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 40
     */
376
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
377 40
    {
378
        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
    private function classifySignStep(array $decoded, &$solution = null)
387
    {
388 88
        try {
389
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
390 88
            $solution = $details->getKeyBuffers();
391 88
            return $details;
392 44
        } 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 66
        try {
396
            $details = PayToPubkey::fromDecodedScript($decoded, true);
397
            $solution = $details->getKeyBuffer();
398
            return $details;
399 66
        } 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 4
        }
401
402
        try {
403 62
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
404
            $solution = $details->getPubKeyHash();
405
            return $details;
406
        } 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
        return null;
410
    }
411
412
    /**
413
     * @param ScriptInterface $script
414
     * @return Checksig[]
415
     */
416 100
    public function parseSequence(ScriptInterface $script)
417
    {
418 100
        $decoded = $script->getScriptParser()->decode();
419 100
420
        $j = 0;
421 100
        $l = count($decoded);
422 38
        $result = [];
423 38
        while ($j < $l) {
424 34
            $step = null;
425 2
            $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 32
            // increment the $last, and break if it's valid
428 36
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
429
                $slice = array_slice($decoded, $j, $i);
430 64
                $step = $this->classifySignStep($slice, $solution);
431 16
                if ($step !== null) {
432 16
                    break;
433 12
                }
434 2
            }
435
436 10
            if (null === $step) {
437
                throw new \RuntimeException("Invalid script");
438 14
            } else {
439 48
                $j += $i;
440 48
                $result[] = new Checksig($step, $this->txSigSerializer, $this->pubKeySerializer);
441
            }
442 48
        }
443 48
444 48
        return $result;
445 48
    }
446 48
447
    /**
448
     * @param Operation $operation
449 44
     * @param Stack $mainStack
450
     * @param bool[] $pathData
451
     * @return Conditional
452
     */
453
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData)
454
    {
455
        $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 28
457 16
        if (!$mainStack->isEmpty()) {
458 16
            if (count($pathData) === 0) {
459 16
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
460
            }
461
462 16
            $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 16
            $dataValue = array_shift($pathData);
464 16
            if ($opValue !== $dataValue) {
465 16
                throw new \RuntimeException("Current stack doesn't follow branch path");
466
            }
467
        } else {
468
            if (count($pathData) === 0) {
469
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
470
            }
471 16
472 4
            $opValue = array_shift($pathData);
473
        }
474
475
        $conditional = new Conditional($operation->getOp());
476
477 44
        if ($opValue !== null) {
478
            if (!is_bool($opValue)) {
479 44
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
480
            }
481 44
482 44
            $conditional->setValue($opValue);
483
        }
484
485 44
        return $conditional;
486 2
    }
487
488
    /**
489 42
     * @param int $idx
490 22
     * @return Checksig|Conditional
491
     */
492 22
    public function step($idx)
493 22
    {
494
        if (!array_key_exists($idx, $this->steps)) {
495
            throw new \RuntimeException("Out of range index for input sign step");
496 42
        }
497
498
        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 42
501 42
    /**
502 42
     * @param OutputData $solution
503
     * @param array $sigChunks
504
     * @param SignData $signData
505
     */
506
    public function extractScript(OutputData $solution, array $sigChunks, SignData $signData)
507
    {
508
        $logicInterpreter = new BranchInterpreter();
509 90
        $tree = $logicInterpreter->getScriptTree($solution->getScript());
510
511
        if ($tree->hasMultipleBranches()) {
512
            $logicalPath = $signData->getLogicalPath();
513
            // we need a function like findWitnessScript to 'check'
514
            // partial signatures against _our_ path
515
        } else {
516
            $logicalPath = [];
517
        }
518
519
        $branch = $tree->getBranchByDesc($logicalPath);
520
        $segments = $branch->getSegments();
521 44
522
        $vfStack = new Stack();
523 44
        $stack = new Stack($sigChunks);
524 36
525 36
        $pathCopy = $logicalPath;
526 36
        $steps = [];
527 36
        foreach ($segments as $i => $segment) {
528
            $fExec = !$this->interpreter->checkExec($vfStack, false);
529
            if ($segment->isLoneLogicalOp()) {
530
                $op = $segment[0];
531 40
                switch ($op->getOp()) {
532 2
                    case Opcodes::OP_IF:
533
                    case Opcodes::OP_NOTIF:
534 38
                        $value = false;
535
                        if ($fExec) {
536
                            // Pop from mainStack if $fExec
537 40
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
538
539
                            // the Conditional has a value in this case:
540
                            $value = $step->getValue();
541
542
                            // Connect the last operation (if there is one)
543
                            // with the last step with isRequired==$value
544
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
545
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
546
                                    $step->providedBy($steps[$j]);
547
                                    break;
548
                                }
549 40
                            }
550
                        } else {
551 40
                            $step = new Conditional($op->getOp());
552 32
                        }
553 32
554 32
                        $steps[] = $step;
555 32
556
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
557
                            $value = !$value;
558
                        }
559 36
560 4
                        $vfStack->push($value);
561
                        break;
562 32
                    case Opcodes::OP_ENDIF:
563
                        $vfStack->pop();
564
                        break;
565 32
                    case Opcodes::OP_ELSE:
566
                        $vfStack->push(!$vfStack->pop());
567
                        break;
568
                }
569
            } else {
570
                $segmentScript = $segment->makeScript();
571
                $templateTypes = $this->parseSequence($segmentScript);
572
573
                // Detect if effect on vfStack is `false`
574
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
575
                if ($resolvesFalse) {
576
                    if (count($templateTypes) > 1) {
577
                        throw new \RuntimeException("Unsupported script, multiple steps to segment which is negated");
578
                    }
579
                }
580
581
                foreach ($templateTypes as $k => $checksig) {
582 122
                    if ($fExec) {
583
                        $this->extractFromValues($solution->getScript(), $checksig, $stack, $this->sigVersion, $resolvesFalse);
584 122
585 122
                        // If this statement results is later consumed
586 122
                        // by a conditional which would be false, mark
587 122
                        // this operation as not required
588 122
                        if ($resolvesFalse) {
589 2
                            $checksig->setRequired(false);
590
                        }
591
                        $steps[] = $checksig;
592 120
                    }
593 46
                }
594
            }
595
        }
596 120
597 44
        $this->steps = $steps;
598 44
    }
599 40
600 2
    /**
601
     * This function is strictly for $canSign types.
602
     * It will extract signatures/publicKeys when given $outputData, and $stack.
603 38
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
604 38
     *
605 2
     * @param ScriptInterface $script
606
     * @param Checksig $checksig
607
     * @param Stack $stack
608 36
     * @param int $sigVersion
609
     * @param bool $expectFalse
610
     */
611 112
    public function extractFromValues(ScriptInterface $script, Checksig $checksig, Stack $stack, $sigVersion, $expectFalse)
612 8
    {
613 8
        $size = count($stack);
614 8
615 106
        if ($checksig->getType() === ScriptType::P2PKH) {
616 40
            if ($size > 1) {
617 40
                $vchPubKey = $stack->pop();
618
                $vchSig = $stack->pop();
619
620 32
                $value = false;
621 2
                if (!$expectFalse) {
622
                    $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
                    if (!$value) {
624 30
                        throw new \RuntimeException('Existing signatures are invalid!');
625 30
                    }
626 2
                }
627
628
                if (!$checksig->isVerify()) {
629 28
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
630
                }
631
632 100
                if ($expectFalse) {
633 100
                    $checksig->setRequired(false);
634
                } else {
635 100
                    $checksig
636
                        ->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 90
                        ->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
        } else if ($checksig->getType() === ScriptType::P2PK) {
642
            if ($size > 0) {
643
                $vchSig = $stack->pop();
644
645
                $value = false;
646
                if (!$expectFalse) {
647
                    $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 82
                    if (!$value) {
649
                        throw new \RuntimeException('Existing signatures are invalid!');
650 82
                    }
651 2
                }
652
653
                if (!$checksig->isVerify()) {
654 80
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
655
                }
656
657
                if ($expectFalse) {
658
                    $checksig->setRequired(false);
659
                } else {
660
                    $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 2
664
            $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 2
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
666
            /** @var Multisig $info */
667
            $info = $checksig->getInfo();
668
            $keyBuffers = $info->getKeyBuffers();
669
            foreach ($keyBuffers as $idx => $keyBuf) {
670
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
671
            }
672
673
            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 80
                // such that $this->signatures state is captured.
678
                // The feature serves to skip validation/sorting an incomplete multisig.
679 80
680 80
                if ($size === 1 + $info->getKeyCount()) {
681 80
                    $sigBufCount = 0;
682
                    $null = new Buffer();
683
                    $keyToSigMap = new \SplObjectStorage();
684
685
                    // Reproduce $keyToSigMap and $sigBufCount
686
                    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
                        if (!$stack[-1 - $i]->equals($null)) {
688
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
689 86
                            $sigBufCount++;
690
                        }
691 86
                    }
692
693
                    // We observed $this->requiredSigs sigs, therefore we can
694
                    // say the implementation is incompatible
695
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
696
                        throw new \RuntimeException("Padding is forbidden for a fully signed multisig script");
697
                    }
698
699 74
                    $toDelete = 1 + $info->getKeyCount();
700
                }
701 74
            }
702
703
            if (!isset($keyToSigMap)) {
704
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
705
                $sigBufs = [];
706
                $max = min($checksig->getRequiredSigs(), $size - 1);
707
                for ($i = 0; $i < $max; $i++) {
708
                    $vchSig = $stack[-1 - $i];
709
                    $sigBufs[] = $vchSig;
710 74
                }
711
712 74
                $sigBufs = array_reverse($sigBufs);
713
                $sigBufCount = count($sigBufs);
714
715
                if (!$expectFalse) {
716
                    if ($sigBufCount > 0) {
717
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
718
                        // Here we learn if any signatures were invalid, it won't be in the map.
719
                        if ($sigBufCount !== count($keyToSigMap)) {
720
                            throw new \RuntimeException('Existing signatures are invalid!');
721 52
                        }
722
                        $toDelete = 1 + count($keyToSigMap);
723 52
                    } else {
724
                        $toDelete = 0;
725
                    }
726
                } else {
727
                    // should check that all signatures are zero
728
                    $keyToSigMap = new \SplObjectStorage();
729
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
730
                }
731
            }
732
            
733 50
            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
                $stack->pop();
735 50
            }
736
737
            foreach ($keyBuffers as $idx => $key) {
738
                if (isset($keyToSigMap[$key])) {
739
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
740
                }
741
            }
742
743 24
            $value = !$expectFalse;
744
            if (!$checksig->isVerify()) {
745 24
                $stack->push($value ? new Buffer("\x01") : new Buffer());
746
            }
747
        } else {
748
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
749
        }
750
    }
751
752
    /**
753 18
     * Checks $chunks (a decompiled scriptSig) for it's last element,
754
     * or defers to SignData. If both are provided, it checks the
755 18
     * value from $chunks against SignData.
756
     *
757
     * @param BufferInterface[] $chunks
758
     * @param SignData $signData
759 18
     * @return ScriptInterface
760
     */
761
    private function findRedeemScript(array $chunks, SignData $signData)
762
    {
763
        if (count($chunks) > 0) {
764
            $redeemScript = new Script($chunks[count($chunks) - 1]);
765
            if ($signData->hasRedeemScript()) {
766
                if (!$redeemScript->equals($signData->getRedeemScript())) {
767 14
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
768
                }
769 14
            }
770
        } else {
771
            if (!$signData->hasRedeemScript()) {
772
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
773 14
            }
774
            $redeemScript = $signData->getRedeemScript();
775
        }
776
777
        return $redeemScript;
778
    }
779
780
    /**
781 50
     * Checks $witness (a witness structure) for it's last element,
782
     * or defers to SignData. If both are provided, it checks the
783 50
     * value from $chunks against SignData.
784 18
     *
785
     * @param BufferInterface[] $witness
786
     * @param SignData $signData
787 32
     * @return ScriptInterface
788
     */
789
    private function findWitnessScript(array $witness, SignData $signData)
790
    {
791
        if (count($witness) > 0) {
792
            $witnessScript = new Script($witness[count($witness) - 1]);
793
            if ($signData->hasWitnessScript()) {
794
                if (!$witnessScript->equals($signData->getWitnessScript())) {
795 50
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
796
                }
797 50
            }
798 18
        } else {
799 8
            if (!$signData->hasWitnessScript()) {
800
                throw new \RuntimeException('Witness script not provided in sign data or witness');
801
            }
802
            $witnessScript = $signData->getWitnessScript();
803 42
        }
804 6
805
        return $witnessScript;
806
    }
807 36
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 86
     * @param ScriptInterface $scriptPubKey
818
     * @param ScriptInterface $scriptSig
819 86
     * @param BufferInterface[] $witness
820
     * @return $this
821
     */
822
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
823 86
    {
824
        $classifier = new OutputClassifier();
825
        $sigVersion = SigHash::V0;
826
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
827 86
828 14
        if (!$this->allowComplexScripts) {
829 2
            if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
830
                throw new \RuntimeException('scriptPubKey not supported');
831 12
            }
832 74
        }
833 34
834 34
        $sigChunks = $this->evalPushOnly($scriptSig);
835 2
836
        if ($solution->getType() === ScriptType::P2SH) {
837
            $redeemScript = $this->findRedeemScript($sigChunks, $signData);
838 32
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
839 32
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
840
            }
841
842 32
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
843 40
            if (!$this->allowComplexScripts) {
844 40
                if (!in_array($solution->getType(), self::$validP2sh)) {
845 40
                    throw new \RuntimeException('Unsupported pay-to-script-hash script');
846 40
                }
847 40
            }
848 38
849 40
            $sigChunks = array_slice($sigChunks, 0, -1);
850
        }
851
852
        if ($solution->getType() === ScriptType::P2WKH) {
853
            $sigVersion = SigHash::V1;
854 40
            $solution = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
855 40
            $sigChunks = $witness;
856
        } else if ($solution->getType() === ScriptType::P2WSH) {
857
            $sigVersion = SigHash::V1;
858
            $witnessScript = $this->findWitnessScript($witness, $signData);
859
860
            // Essentially all the reference implementation does
861 80
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
862
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
863
            }
864
865
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
866
            if (!$this->allowComplexScripts) {
867
                if (!in_array($this->witnessScript->getType(), self::$canSign)) {
868
                    throw new \RuntimeException('Unsupported witness-script-hash script');
869
                }
870 50
            }
871
872 50
            $sigChunks = array_slice($witness, 0, -1);
873
        }
874 50
875 50
        $this->sigVersion = $sigVersion;
876
        $this->signScript = $solution;
877
878 50
        $this->extractScript($solution, $sigChunks, $signData);
879 50
880 22
        return $this;
881
    }
882
883 50
    /**
884
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
885
     *
886 50
     * @param ScriptInterface $scriptCode
887 50
     * @param int $sigHashType
888
     * @param int $sigVersion
889 50
     * @return BufferInterface
890 22
     */
891 22
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
892 22
    {
893 22
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
894
            throw new \RuntimeException('Invalid sigHashType requested');
895 2
        }
896
897
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
898
    }
899 22
900
    /**
901
     * Calculates the signature hash for the input for the given $sigHashType.
902 50
     *
903
     * @param int $sigHashType
904
     * @return BufferInterface
905
     */
906
    public function getSigHash($sigHashType)
907
    {
908
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
909
    }
910
911 80
    /**
912
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
913 80
     *
914 80
     * @param PrivateKeyInterface $key
915 12
     * @param ScriptInterface $scriptCode
916 12
     * @param int $sigHashType
917
     * @param int $sigVersion
918 70
     * @return TransactionSignatureInterface
919 32
     */
920 32
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
921
    {
922 38
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
923 38
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
924
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
925 38
    }
926 38
927 38
    /**
928 38
     * Returns whether all required signatures have been provided.
929 38
     *
930 12
     * @return bool
931
     */
932
    public function isFullySigned()
933
    {
934
        foreach ($this->steps as $step) {
935
            if ($step instanceof Conditional) {
936
                if (!$step->hasValue()) {
937 80
                    return false;
938
                }
939
            } else if ($step instanceof Checksig) {
940
                if (!$step->isFullySigned()) {
941
                    return false;
942
                }
943
            }
944
        }
945 80
946
        return true;
947 80
    }
948 80
949 80
    /**
950 2
     * Returns the required number of signatures for this input.
951 2
     *
952
     * @return int
953
     */
954 80
    public function getRequiredSigs()
955 80
    {
956 80
        $count = 0;
957 26
        foreach ($this->steps as $step) {
958
            if ($step instanceof Checksig) {
959
                $count += $step->getRequiredSigs();
960 80
            }
961 80
        }
962 80
        return $count;
963 32
    }
964 32
965 20
    /**
966
     * Returns an array where the values are either null,
967 32
     * or a TransactionSignatureInterface.
968
     *
969
     * @return TransactionSignatureInterface[]
970 80
     */
971 8
    public function getSignatures()
972 74
    {
973 28
        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 28
    }
975 28
976
    /**
977
     * Returns an array where the values are either null,
978
     * or a PublicKeyInterface.
979 80
     *
980 32
     * @return PublicKeyInterface[]
981
     */
982
    public function getPublicKeys()
983 80
    {
984
        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
    public function getSignScript()
995
    {
996
        return $this->signScript;
997
    }
998
999
    /**
1000
     * OutputData for the txOut script.
1001
     *
1002
     * @return OutputData
1003
     */
1004
    public function getScriptPubKey()
1005
    {
1006
        return $this->scriptPubKey;
1007
    }
1008
1009
    /**
1010
     * Returns OutputData for the P2SH redeemScript.
1011
     *
1012
     * @return OutputData
1013
     */
1014
    public function getRedeemScript()
1015
    {
1016
        if (null === $this->redeemScript) {
1017
            throw new \RuntimeException("Input has no redeemScript, cannot call getRedeemScript");
1018
        }
1019
1020
        return $this->redeemScript;
1021
    }
1022
1023
    /**
1024
     * Returns OutputData for the P2WSH witnessScript.
1025
     *
1026
     * @return OutputData
1027
     */
1028
    public function getWitnessScript()
1029
    {
1030
        if (null === $this->witnessScript) {
1031
            throw new \RuntimeException("Input has no witnessScript, cannot call getWitnessScript");
1032
        }
1033
1034
        return $this->witnessScript;
1035
    }
1036
1037
    /**
1038
     * Returns whether the scriptPubKey is P2SH.
1039
     *
1040
     * @return bool
1041
     */
1042
    public function isP2SH()
1043
    {
1044
        if ($this->scriptPubKey->getType() === ScriptType::P2SH && ($this->redeemScript instanceof OutputData)) {
1045
            return true;
1046
        }
1047
1048
        return false;
1049
    }
1050
1051
    /**
1052
     * Returns whether the scriptPubKey or redeemScript is P2WSH.
1053
     *
1054
     * @return bool
1055
     */
1056
    public function isP2WSH()
1057
    {
1058
        if ($this->redeemScript instanceof OutputData) {
1059
            if ($this->redeemScript->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1060
                return true;
1061
            }
1062
        }
1063
1064
        if ($this->scriptPubKey->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1065
            return true;
1066
        }
1067
1068
        return false;
1069
    }
1070
1071
    /**
1072
     * @param int $stepIdx
1073
     * @param PrivateKeyInterface $privateKey
1074
     * @param int $sigHashType
1075
     * @return $this
1076
     */
1077
    public function signStep($stepIdx, PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1078
    {
1079
        if (!array_key_exists($stepIdx, $this->steps)) {
1080
            throw new \RuntimeException("Unknown step index");
1081
        }
1082
1083
        $checksig = $this->steps[$stepIdx];
1084
        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
        if (SigHash::V1 === $this->sigVersion && !$privateKey->isCompressed()) {
1089
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
1090
        }
1091
1092
        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
            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
                throw new \RuntimeException('Signing with the wrong private key');
1095
            }
1096
1097
            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
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1099
                $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
        } 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
            $publicKey = $privateKey->getPublicKey();
1103
            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
                throw new \RuntimeException('Signing with the wrong private key');
1105
            }
1106
1107
            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
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1109
                $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
            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
                $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
        } 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
            $signed = false;
1117
            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
                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
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
1120
                        $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1121
                        $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
                        $signed = true;
1123
                    }
1124
                }
1125
            }
1126
1127
            if (!$signed) {
1128
                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
        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
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1145
    {
1146
        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
    public function verify($flags = null)
1156
    {
1157
        $consensus = ScriptFactory::consensus();
1158
1159
        if ($flags === null) {
1160
            $flags = $this->flags;
1161
        }
1162
1163
        $flags |= Interpreter::VERIFY_P2SH;
1164
        if (SigHash::V1 === $this->sigVersion) {
1165
            $flags |= Interpreter::VERIFY_WITNESS;
1166
        }
1167
1168
        $sig = $this->serializeSignatures();
1169
1170
        // Take serialized signatures, and use mutator to add this inputs sig data
1171
        $mutator = TransactionFactory::mutate($this->tx);
1172
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1173
1174
        if (SigHash::V1 === $this->sigVersion) {
1175
            $witness = [];
1176
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1177
                if ($i === $this->nInput) {
1178
                    $witness[] = $sig->getScriptWitness();
1179
                } else {
1180
                    $witness[] = new ScriptWitness([]);
1181
                }
1182
            }
1183
1184
            $mutator->witness($witness);
1185
        }
1186
1187
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1188
    }
1189
1190
    /**
1191
     * @return array
1192
     */
1193
    private function serializeSteps()
1194
    {
1195
        $results = [];
1196
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1197
            $step = $this->steps[$i];
1198
1199
            if ($step instanceof Conditional) {
1200
                $results[] = $step->serialize();
1201
            } else if ($step instanceof Checksig) {
1202
                if ($step->isRequired()) {
1203
                    if (count($step->getSignatures()) === 0) {
1204
                        break;
1205
                    }
1206
                }
1207
1208
                $results[] = $step->serialize();
1209
1210
                if (!$step->isFullySigned()) {
1211
                    break;
1212
                }
1213
            }
1214
        }
1215
1216
        $values = [];
1217
        foreach (array_reverse($results) as $v) {
1218
            foreach ($v as $value) {
1219
                $values[] = $value;
1220
            }
1221
        }
1222
1223
        return $values;
1224
    }
1225
1226
    /**
1227
     * Produces a SigValues instance containing the scriptSig & script witness
1228
     *
1229
     * @return SigValues
1230
     */
1231
    public function serializeSignatures()
1232
    {
1233
        static $emptyScript = null;
1234
        static $emptyWitness = null;
1235
        if (is_null($emptyScript) || is_null($emptyWitness)) {
1236
            $emptyScript = new Script();
1237
            $emptyWitness = new ScriptWitness([]);
1238
        }
1239
1240
        $witness = [];
1241
        $scriptSigChunks = $this->serializeSteps();
1242
1243
        $solution = $this->scriptPubKey;
1244
        $p2sh = false;
1245
        if ($solution->getType() === ScriptType::P2SH) {
1246
            $p2sh = true;
1247
            $solution = $this->redeemScript;
1248
        }
1249
1250
        if ($solution->getType() === ScriptType::P2WKH) {
1251
            $witness = $scriptSigChunks;
1252
            $scriptSigChunks = [];
1253
        } else if ($solution->getType() === ScriptType::P2WSH) {
1254
            $witness = $scriptSigChunks;
1255
            $witness[] = $this->witnessScript->getScript()->getBuffer();
1256
            $scriptSigChunks = [];
1257
        }
1258
1259
        if ($p2sh) {
1260
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
1261
        }
1262
1263
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
1264
    }
1265
1266
    public function getSteps()
1267
    {
1268
        return $this->steps;
1269
    }
1270
}
1271