Completed
Pull Request — master (#524)
by thomas
71:45
created

InputSigner::isFullySigned()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6

Importance

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