Completed
Push — master ( 7ac9fb...3b1821 )
by thomas
28:43
created

InputSigner::compareRangeAgainstThreshold()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 3
nop 3
dl 0
loc 12
ccs 6
cts 6
cp 1
crap 5
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\Locktime;
13
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
14
use BitWasp\Bitcoin\Script\Classifier\OutputData;
15
use BitWasp\Bitcoin\Script\Interpreter\BitcoinCashChecker;
16
use BitWasp\Bitcoin\Script\Interpreter\Checker;
17
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
18
use BitWasp\Bitcoin\Script\Interpreter\Number;
19
use BitWasp\Bitcoin\Script\Interpreter\Stack;
20
use BitWasp\Bitcoin\Script\Opcodes;
21
use BitWasp\Bitcoin\Script\Parser\Operation;
22
use BitWasp\Bitcoin\Script\Path\BranchInterpreter;
23
use BitWasp\Bitcoin\Script\Script;
24
use BitWasp\Bitcoin\Script\ScriptFactory;
25
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
26
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkey;
27
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkeyHash;
28
use BitWasp\Bitcoin\Script\ScriptInterface;
29
use BitWasp\Bitcoin\Script\ScriptType;
30
use BitWasp\Bitcoin\Script\ScriptWitness;
31
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
32
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
33
use BitWasp\Bitcoin\Signature\TransactionSignature;
34
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
35
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckLocktimeVerify;
36
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckSequenceVerify;
37
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
38
use BitWasp\Bitcoin\Transaction\TransactionFactory;
39
use BitWasp\Bitcoin\Transaction\TransactionInput;
40
use BitWasp\Bitcoin\Transaction\TransactionInterface;
41
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
42
use BitWasp\Buffertools\Buffer;
43
use BitWasp\Buffertools\BufferInterface;
44
45
class InputSigner implements InputSignerInterface
46
{
47
    /**
48
     * @var array
49
     */
50
    protected static $canSign = [
51
        ScriptType::P2PKH,
52
        ScriptType::P2PK,
53
        ScriptType::MULTISIG
54
    ];
55
56
    /**
57
     * @var array
58
     */
59
    protected static $validP2sh = [
60
        ScriptType::P2WKH,
61
        ScriptType::P2WSH,
62
        ScriptType::P2PKH,
63
        ScriptType::P2PK,
64
        ScriptType::MULTISIG
65
    ];
66
67
    /**
68
     * @var EcAdapterInterface
69
     */
70
    private $ecAdapter;
71
72
    /**
73
     * @var OutputData $scriptPubKey
74
     */
75
    private $scriptPubKey;
76
77
    /**
78
     * @var OutputData $redeemScript
79
     */
80
    private $redeemScript;
81
82
    /**
83
     * @var OutputData $witnessScript
84
     */
85
    private $witnessScript;
86
87
    /**
88
     * @var OutputData
89
     */
90
    private $signScript;
91
92
    /**
93
     * @var bool
94
     */
95
    private $padUnsignedMultisigs = false;
96
97
    /**
98
     * @var bool
99
     */
100
    private $tolerateInvalidPublicKey = false;
101
102
    /**
103
     * @var bool
104
     */
105
    private $redeemBitcoinCash = false;
106
107
    /**
108
     * @var bool
109
     */
110
    private $allowComplexScripts = false;
111
112
    /**
113
     * @var SignData
114
     */
115
    private $signData;
116
117
    /**
118
     * @var int
119
     */
120
    private $sigVersion;
121
122
    /**
123
     * @var int
124
     */
125
    private $flags;
126
127
    /**
128
     * @var TransactionInterface
129
     */
130
    private $tx;
131
132
    /**
133
     * @var int
134
     */
135
    private $nInput;
136
137
    /**
138
     * @var TransactionOutputInterface
139
     */
140
    private $txOut;
141
142
    /**
143
     * @var Interpreter
144
     */
145
    private $interpreter;
146
147
    /**
148
     * @var Checker
149
     */
150
    private $signatureChecker;
151
152
    /**
153
     * @var TransactionSignatureSerializer
154
     */
155
    private $txSigSerializer;
156
157
    /**
158
     * @var PublicKeySerializerInterface
159
     */
160
    private $pubKeySerializer;
161
162
    /**
163
     * @var Conditional[]|Checksig[]
164
     */
165
    private $steps = [];
166
167
    /**
168
     * InputSigner constructor.
169
     *
170
     * Note, the implementation of this class is considered internal
171
     * and only the methods exposed on InputSignerInterface should
172
     * be depended on to avoid BC breaks.
173
     *
174
     * The only recommended way to produce this class is using Signer::input()
175
     *
176
     * @param EcAdapterInterface $ecAdapter
177
     * @param TransactionInterface $tx
178
     * @param int $nInput
179
     * @param TransactionOutputInterface $txOut
180
     * @param SignData $signData
181
     * @param TransactionSignatureSerializer|null $sigSerializer
182
     * @param PublicKeySerializerInterface|null $pubKeySerializer
183
     */
184 190
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
185
    {
186 190
        $this->ecAdapter = $ecAdapter;
187 190
        $this->tx = $tx;
188 190
        $this->nInput = $nInput;
189 190
        $this->txOut = $txOut;
190 190
        $this->signData = $signData;
191
192 190
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
193 190
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
194 190
        $this->interpreter = new Interpreter($this->ecAdapter);
195 190
    }
196
197
    /**
198
     * @return InputSigner
199
     */
200 190
    public function extract()
201
    {
202 190
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
203 190
        $checker = new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
204
205 190
        if ($this->redeemBitcoinCash) {
206
            // unset VERIFY_WITNESS default
207 2
            $defaultFlags = $defaultFlags & (~Interpreter::VERIFY_WITNESS);
208
209 2
            if ($this->signData->hasSignaturePolicy()) {
210
                if ($this->signData->getSignaturePolicy() & Interpreter::VERIFY_WITNESS) {
211
                    throw new \RuntimeException("VERIFY_WITNESS is not possible for bitcoin cash");
212
                }
213
            }
214
215 2
            $checker = new BitcoinCashChecker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
216
        }
217
218 190
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
219 190
        $this->signatureChecker = $checker;
220
221 190
        $witnesses = $this->tx->getWitnesses();
222 190
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput]->all() : [];
223
224 190
        return $this->solve(
225 190
            $this->signData,
226 190
            $this->txOut->getScript(),
227 190
            $this->tx->getInput($this->nInput)->getScript(),
228 188
            $witness
229
        );
230
    }
231
232
    /**
233
     * @param bool $setting
234
     * @return $this
235
     */
236 160
    public function padUnsignedMultisigs($setting)
237
    {
238 160
        $this->padUnsignedMultisigs = (bool) $setting;
239 160
        return $this;
240
    }
241
242
    /**
243
     * @param bool $setting
244
     * @return $this
245
     */
246 160
    public function tolerateInvalidPublicKey($setting)
247
    {
248 160
        $this->tolerateInvalidPublicKey = (bool) $setting;
249 160
        return $this;
250
    }
251
252
    /**
253
     * @param bool $setting
254
     * @return $this
255
     */
256 160
    public function redeemBitcoinCash($setting)
257
    {
258 160
        $this->redeemBitcoinCash = (bool) $setting;
259 160
        return $this;
260
    }
261
262
    /**
263
     * @param bool $setting
264
     * @return $this
265
     */
266 160
    public function allowComplexScripts($setting)
267
    {
268 160
        $this->allowComplexScripts = (bool) $setting;
269 160
        return $this;
270
    }
271
272
    /**
273
     * @param BufferInterface $vchPubKey
274
     * @return PublicKeyInterface|null
275
     * @throws \Exception
276
     */
277 138
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
278
    {
279
        try {
280 138
            return $this->pubKeySerializer->parse($vchPubKey);
281 6
        } catch (\Exception $e) {
282 6
            if ($this->tolerateInvalidPublicKey) {
283 2
                return null;
284
            }
285
286 4
            throw $e;
287
        }
288
    }
289
290
    /**
291
     * A snippet from OP_CHECKMULTISIG - links keys to signatures
292
     *
293
     * @param ScriptInterface $script
294
     * @param BufferInterface[] $signatures
295
     * @param BufferInterface[] $publicKeys
296
     * @param int $sigVersion
297
     * @return \SplObjectStorage
298
     */
299 50
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
300
    {
301 50
        $sigCount = count($signatures);
302 50
        $keyCount = count($publicKeys);
303 50
        $ikey = $isig = 0;
304 50
        $fSuccess = true;
305 50
        $result = new \SplObjectStorage;
306
307 50
        while ($fSuccess && $sigCount > 0) {
308
            // Fetch the signature and public key
309 50
            $sig = $signatures[$isig];
310 50
            $pubkey = $publicKeys[$ikey];
311
312 50
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
313 48
                $result[$pubkey] = $sig;
314 48
                $isig++;
315 48
                $sigCount--;
316
            }
317
318 50
            $ikey++;
319 50
            $keyCount--;
320
321
            // If there are more signatures left than keys left,
322
            // then too many signatures have failed. Exit early,
323
            // without checking any further signatures.
324 50
            if ($sigCount > $keyCount) {
325 2
                $fSuccess = false;
326
            }
327
        }
328
329 50
        return $result;
330
    }
331
332
    /**
333
     * @param ScriptInterface $script
334
     * @return \BitWasp\Buffertools\BufferInterface[]
335
     */
336 186
    private function evalPushOnly(ScriptInterface $script)
337
    {
338 186
        $stack = new Stack();
339 186
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
340 186
        return $stack->all();
341
    }
342
343
    /**
344
     * Create a script consisting only of push-data operations.
345
     * Suitable for a scriptSig.
346
     *
347
     * @param BufferInterface[] $buffers
348
     * @return ScriptInterface
349
     */
350
    private function pushAll(array $buffers)
351
    {
352 126
        return ScriptFactory::sequence(array_map(function ($buffer) {
353 104
            if (!($buffer instanceof BufferInterface)) {
354
                throw new \RuntimeException('Script contained a non-push opcode');
355
            }
356
357 104
            $size = $buffer->getSize();
358 104
            if ($size === 0) {
359 44
                return Opcodes::OP_0;
360
            }
361
362 104
            $first = ord($buffer->getBinary()[0]);
363 104
            if ($size === 1 && $first >= 1 && $first <= 16) {
364 8
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
365
            } else {
366 104
                return $buffer;
367
            }
368 126
        }, $buffers));
369
    }
370
371
    /**
372
     * Verify a scriptSig / scriptWitness against a scriptPubKey.
373
     * Useful for checking the outcome of certain things, like hash locks (p2sh)
374
     *
375
     * @param int $flags
376
     * @param ScriptInterface $scriptSig
377
     * @param ScriptInterface $scriptPubKey
378
     * @param ScriptWitnessInterface|null $scriptWitness
379
     * @return bool
380
     */
381 70
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
382
    {
383 70
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
384
    }
385
386
    /**
387
     * @param array $decoded
388
     * @param null $solution
389
     * @return null|TimeLock|Checksig
390
     */
391 166
    private function classifySignStep(array $decoded, &$solution = null)
392
    {
393
        try {
394 166
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
395 64
            $solution = $details->getKeyBuffers();
396 64
            return new Checksig($details);
397 166
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
398
        }
399
400
        try {
401 166
            $details = PayToPubkey::fromDecodedScript($decoded, true);
402 80
            $solution = $details->getKeyBuffer();
403 80
            return new Checksig($details);
404 166
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
405
        }
406
407
        try {
408 166
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
409 42
            $solution = $details->getPubKeyHash();
410 42
            return new Checksig($details);
411 166
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
412
        }
413
414
        try {
415 166
            $details = CheckLocktimeVerify::fromDecodedScript($decoded);
416 16
            return new TimeLock($details);
417 166
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
418
        }
419
420
        try {
421 166
            $details = CheckSequenceVerify::fromDecodedScript($decoded);
422 14
            return new TimeLock($details);
423 166
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
424
        }
425
426 166
        return null;
427
    }
428
429
    /**
430
     * @param Operation[] $scriptOps
431
     * @return Checksig[]
432
     */
433 166
    public function parseSequence(array $scriptOps)
434
    {
435 166
        $j = 0;
436 166
        $l = count($scriptOps);
437 166
        $result = [];
438 166
        while ($j < $l) {
439 166
            $step = null;
440 166
            $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...
441
442
            // increment the $last, and break if it's valid
443 166
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
444 166
                $slice = array_slice($scriptOps, $j, $i);
445 166
                $step = $this->classifySignStep($slice, $solution);
446 166
                if ($step !== null) {
447 166
                    break;
448
                }
449
            }
450
451 166
            if (null === $step) {
452
                throw new \RuntimeException("Invalid script");
453
            } else {
454 166
                $j += $i;
455 166
                $result[] = $step;
456
            }
457
        }
458
459 166
        return $result;
460
    }
461
462
    /**
463
     * @param Operation $operation
464
     * @param Stack $mainStack
465
     * @param bool[] $pathData
466
     * @return Conditional
467
     */
468 22
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData)
469
    {
470 22
        $opValue = null;
0 ignored issues
show
Unused Code introduced by
$opValue is not used, you could remove the assignment.

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

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

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
478 22
            $dataValue = array_shift($pathData);
479 22
            if ($opValue !== $dataValue) {
480 22
                throw new \RuntimeException("Current stack doesn't follow branch path");
481
            }
482
        } else {
483 18
            if (count($pathData) === 0) {
484
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
485
            }
486
487 18
            $opValue = array_shift($pathData);
488
        }
489
490 22
        $conditional = new Conditional($operation->getOp());
491
492 22
        if ($opValue !== null) {
493 22
            if (!is_bool($opValue)) {
494
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
495
            }
496
497 22
            $conditional->setValue($opValue);
498
        }
499
500 22
        return $conditional;
501
    }
502
503
    /**
504
     * @param int $idx
505
     * @return Checksig|Conditional
506
     */
507 36
    public function step($idx)
508
    {
509 36
        if (!array_key_exists($idx, $this->steps)) {
510
            throw new \RuntimeException("Out of range index for input sign step");
511
        }
512
513 36
        return $this->steps[$idx];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->steps[$idx]; (BitWasp\Bitcoin\Transaction\Factory\Conditional) is incompatible with the return type declared by the interface BitWasp\Bitcoin\Transact...utSignerInterface::step of type array<BitWasp\Bitcoin\Tr...on\Factory\Conditional>.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
514
    }
515
516
    /**
517
     * @param OutputData $solution
518
     * @param array $sigChunks
519
     * @param SignData $signData
520
     */
521 166
    public function extractScript(OutputData $solution, array $sigChunks, SignData $signData)
522
    {
523 166
        $logicInterpreter = new BranchInterpreter();
524 166
        $tree = $logicInterpreter->getScriptTree($solution->getScript());
525
526 166
        if ($tree->hasMultipleBranches()) {
527 22
            $logicalPath = $signData->getLogicalPath();
528
            // we need a function like findWitnessScript to 'check'
529
            // partial signatures against _our_ path
530
        } else {
531 144
            $logicalPath = [];
532
        }
533
534
        $scriptSections = $tree
535 166
            ->getBranchByPath($logicalPath)
536 166
            ->getScriptSections();
537
538 166
        $vfStack = new Stack();
539 166
        $stack = new Stack($sigChunks);
540
541 166
        $pathCopy = $logicalPath;
542 166
        $steps = [];
543 166
        foreach ($scriptSections as $i => $scriptSection) {
544
            /** @var Operation[] $scriptSection */
545 166
            $fExec = !$this->interpreter->checkExec($vfStack, false);
546 166
            if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) {
547 22
                $op = $scriptSection[0];
548 22
                switch ($op->getOp()) {
549 22
                    case Opcodes::OP_IF:
550 22
                    case Opcodes::OP_NOTIF:
551 22
                        $value = false;
552 22
                        if ($fExec) {
553
                            // Pop from mainStack if $fExec
554 22
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
555
556
                            // the Conditional has a value in this case:
557 22
                            $value = $step->getValue();
558
559
                            // Connect the last operation (if there is one)
560
                            // with the last step with isRequired==$value
561
                            // todo: check this part out..
562 22
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
563 8
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
564 8
                                    $step->providedBy($steps[$j]);
565 8
                                    break;
566
                                }
567
                            }
568
                        } else {
569 2
                            $step = new Conditional($op->getOp());
570
                        }
571
572 22
                        $steps[] = $step;
573
574 22
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
575 10
                            $value = !$value;
576
                        }
577
578 22
                        $vfStack->push($value);
579 22
                        break;
580 22
                    case Opcodes::OP_ENDIF:
581 22
                        $vfStack->pop();
582 22
                        break;
583 18
                    case Opcodes::OP_ELSE:
584 18
                        $vfStack->push(!$vfStack->pop());
585 22
                        break;
586
                }
587
            } else {
588 166
                $templateTypes = $this->parseSequence($scriptSection);
589
590
                // Detect if effect on mainStack is `false`
591 166
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
592 166
                if ($resolvesFalse) {
593 4
                    if (count($templateTypes) > 1) {
594
                        throw new \RuntimeException("Unsupported script, multiple steps to segment which is negated");
595
                    }
596
                }
597
598 166
                foreach ($templateTypes as $k => $checksig) {
599 166
                    if ($fExec) {
600 166
                        if ($checksig instanceof Checksig) {
601 146
                            $this->extractChecksig($solution->getScript(), $checksig, $stack, $this->sigVersion, $resolvesFalse);
602
603
                            // If this statement results is later consumed
604
                            // by a conditional which would be false, mark
605
                            // this operation as not required
606 136
                            if ($resolvesFalse) {
607 136
                                $checksig->setRequired(false);
608
                            }
609 30
                        } else if ($checksig instanceof TimeLock) {
610 30
                            $this->checkTimeLock($checksig);
611
                        }
612
613 136
                        $steps[] = $checksig;
614
                    }
615
                }
616
            }
617
        }
618
619 136
        $this->steps = $steps;
620 136
    }
621
622
    /**
623
     * @param int $verify
624
     * @param int $input
625
     * @param int $threshold
626
     * @return int
627
     */
628 10
    private function compareRangeAgainstThreshold($verify, $input, $threshold)
629
    {
630 10
        if ($verify <= $threshold && $input > $threshold) {
631 4
            return -1;
632
        }
633
634 6
        if ($verify > $threshold && $input <= $threshold) {
635 4
            return 1;
636
        }
637
638 2
        return 0;
639
    }
640
641
    /**
642
     * @param TimeLock $timelock
643
     */
644 30
    public function checkTimeLock(TimeLock $timelock)
645
    {
646 30
        $info = $timelock->getInfo();
647 30
        if (($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY) != 0 && $info instanceof CheckLocktimeVerify) {
648 16
            $verifyLocktime = $info->getLocktime();
649 16
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
650 10
                $input = $this->tx->getInput($this->nInput);
651 10
                if ($input->isFinal()) {
652 4
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
653
                }
654
655 6
                $locktime = $this->tx->getLockTime();
656 6
                $cmp = $this->compareRangeAgainstThreshold($verifyLocktime, $locktime, Locktime::BLOCK_MAX);
657 6
                if ($cmp === -1) {
658 2
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
659 4
                } else if ($cmp === 1) {
660 2
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
661
                }
662
663 2
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
664 2
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
665
            }
666
        }
667
668 20
        if (($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY) != 0 && $info instanceof CheckSequenceVerify) {
669
            // Future soft-fork extensibility, NOP if disabled flag
670 14
            if (($info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
671
                return;
672
            }
673
674 14
            if (!$this->signatureChecker->checkSequence(Number::int($info->getRelativeLockTime()))) {
675 10
                if ($this->tx->getVersion() < 2) {
676 4
                    throw new \RuntimeException("Transaction version must be 2 or greater for CSV");
677
                }
678
679 6
                $input = $this->tx->getInput($this->nInput);
680 6
                if ($input->isFinal()) {
681 2
                    throw new \RuntimeException("Sequence LOCKTIME_DISABLE_FLAG is set - not allowed on CSV output");
682
                }
683
684 4
                $cmp = $this->compareRangeAgainstThreshold($info->getRelativeLockTime(), $input->getSequence(), TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG);
685 4
                if ($cmp === -1) {
686 2
                    throw new \RuntimeException("CSV was for block height, but txin sequence was in timestamp range");
687 2
                } else if ($cmp === 1) {
688 2
                    throw new \RuntimeException("CSV was for timestamp, but txin sequence was in block range");
689
                }
690
691
                $masked = $info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_MASK;
692
                $requiredLock = "{$masked} " . ($info->isRelativeToBlock() ? " (blocks)" : "(seconds after txOut)");
693
                throw new \RuntimeException("Output unspendable with this sequence, must be locked for {$requiredLock}");
694
            }
695
        }
696 10
    }
697
698
    /**
699
     * This function is strictly for $canSign types.
700
     * It will extract signatures/publicKeys when given $outputData, and $stack.
701
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
702
     *
703
     * @param ScriptInterface $script
704
     * @param Checksig $checksig
705
     * @param Stack $stack
706
     * @param int $sigVersion
707
     * @param bool $expectFalse
708
     */
709 146
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, $sigVersion, $expectFalse)
710
    {
711 146
        $size = count($stack);
712
713 146
        if ($checksig->getType() === ScriptType::P2PKH) {
714 42
            if ($size > 1) {
715 38
                $vchPubKey = $stack->pop();
716 38
                $vchSig = $stack->pop();
717
718 38
                $value = false;
719 38
                if (!$expectFalse) {
720 38
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $vchPubKey, $this->sigVersion, $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 716 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 715 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...
721 38
                    if (!$value) {
722 2
                        throw new \RuntimeException('Existing signatures are invalid!');
723
                    }
724
                }
725
726 36
                if (!$checksig->isVerify()) {
727 36
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
728
                }
729
730 36
                if (!$expectFalse) {
731
                    $checksig
732 36
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 716 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...
733 40
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 715 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...
734
                    ;
735
                }
736
            }
737 110
        } else if ($checksig->getType() === ScriptType::P2PK) {
738 60
            if ($size > 0) {
739 46
                $vchSig = $stack->pop();
740
741 46
                $value = false;
742 46
                if (!$expectFalse) {
743 46
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $checksig->getSolution(), $this->sigVersion, $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 739 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...
744 46
                    if (!$value) {
745 2
                        throw new \RuntimeException('Existing signatures are invalid!');
746
                    }
747
                }
748
749 44
                if (!$checksig->isVerify()) {
750 40
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
751
                }
752
753 44
                if (!$expectFalse) {
754 44
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 739 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...
755
                }
756
            }
757
758 58
            $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...
759 64
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
760
            /** @var Multisig $info */
761 64
            $info = $checksig->getInfo();
762 64
            $keyBuffers = $info->getKeyBuffers();
763 64
            foreach ($keyBuffers as $idx => $keyBuf) {
764 64
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
765
            }
766
767 60
            $value = false;
768 60
            if ($this->padUnsignedMultisigs) {
769
                // Multisig padding is only used for partially signed transactions,
770
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
771
                // values (including the dummy), with one for each candidate signature,
772
                // such that $this->signatures state is captured.
773
                // The feature serves to skip validation/sorting an incomplete multisig.
774
775 28
                if ($size === 1 + $info->getKeyCount()) {
776 4
                    $sigBufCount = 0;
777 4
                    $null = new Buffer();
778 4
                    $keyToSigMap = new \SplObjectStorage();
779
780
                    // Reproduce $keyToSigMap and $sigBufCount
781 4
                    for ($i = 0; $i < $info->getKeyCount(); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

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

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

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
782 4
                        if (!$stack[-1 - $i]->equals($null)) {
783 4
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
784 4
                            $sigBufCount++;
785
                        }
786
                    }
787
788
                    // We observed $this->requiredSigs sigs, therefore we can
789
                    // say the implementation is incompatible
790 4
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
791 4
                        throw new \RuntimeException("Padding is forbidden for a fully signed multisig script");
792
                    }
793
794
                    $toDelete = 1 + $info->getKeyCount();
795
                    $value = true;
796
                }
797
            }
798
799 60
            if (!isset($toDelete) || !isset($keyToSigMap)) {
800
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
801 60
                $sigBufs = [];
802 60
                $max = min($checksig->getRequiredSigs(), $size - 1);
803 60
                for ($i = 0; $i < $max; $i++) {
804 52
                    $vchSig = $stack[-1 - $i];
805 52
                    $sigBufs[] = $vchSig;
806
                }
807
808 60
                $sigBufs = array_reverse($sigBufs);
809 60
                $sigBufCount = count($sigBufs);
810
811 60
                if (!$expectFalse) {
812 58
                    if ($sigBufCount > 0) {
813 50
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
814
                        // Here we learn if any signatures were invalid, it won't be in the map.
815 50
                        if ($sigBufCount !== count($keyToSigMap)) {
816 2
                            throw new \RuntimeException('Existing signatures are invalid!');
817
                        }
818 48
                        $toDelete = 1 + count($keyToSigMap);
819
                    } else {
820 56
                        $toDelete = 0;
821 56
                        $keyToSigMap = new \SplObjectStorage();
822
                    }
823 56
                    $value = true;
824
                } else {
825
                    // should check that all signatures are zero
826 2
                    $keyToSigMap = new \SplObjectStorage();
827 2
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
828 2
                    $value = false;
829
                }
830
            }
831
832 58
            while ($toDelete--) {
833 50
                $stack->pop();
834
            }
835
836 58
            foreach ($keyBuffers as $idx => $key) {
837 58
                if (isset($keyToSigMap[$key])) {
838 58
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
839
                }
840
            }
841
842 58
            if (!$checksig->isVerify()) {
843 58
                $stack->push($value ? new Buffer("\x01") : new Buffer());
844
            }
845
        } else {
846
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
847
        }
848 136
    }
849
850
    /**
851
     * Checks $chunks (a decompiled scriptSig) for it's last element,
852
     * or defers to SignData. If both are provided, it checks the
853
     * value from $chunks against SignData.
854
     *
855
     * @param BufferInterface[] $chunks
856
     * @param SignData $signData
857
     * @return ScriptInterface
858
     */
859 74
    private function findRedeemScript(array $chunks, SignData $signData)
860
    {
861 74
        if (count($chunks) > 0) {
862 36
            $redeemScript = new Script($chunks[count($chunks) - 1]);
863 36
            if ($signData->hasRedeemScript()) {
864 36
                if (!$redeemScript->equals($signData->getRedeemScript())) {
865 36
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
866
                }
867
            }
868
        } else {
869 70
            if (!$signData->hasRedeemScript()) {
870 2
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
871
            }
872 68
            $redeemScript = $signData->getRedeemScript();
873
        }
874
875 70
        return $redeemScript;
876
    }
877
878
    /**
879
     * Checks $witness (a witness structure) for it's last element,
880
     * or defers to SignData. If both are provided, it checks the
881
     * value from $chunks against SignData.
882
     *
883
     * @param BufferInterface[] $witness
884
     * @param SignData $signData
885
     * @return ScriptInterface
886
     */
887 70
    private function findWitnessScript(array $witness, SignData $signData)
888
    {
889 70
        if (count($witness) > 0) {
890 32
            $witnessScript = new Script($witness[count($witness) - 1]);
891 32
            if ($signData->hasWitnessScript()) {
892 32
                if (!$witnessScript->equals($signData->getWitnessScript())) {
893 32
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
894
                }
895
            }
896
        } else {
897 66
            if (!$signData->hasWitnessScript()) {
898 4
                throw new \RuntimeException('Witness script not provided in sign data or witness');
899
            }
900 62
            $witnessScript = $signData->getWitnessScript();
901
        }
902
903 62
        return $witnessScript;
904
    }
905
906
    /**
907
     * Needs to be called before using the instance. By `extract`.
908
     *
909
     * It ensures that violating the following prevents instance creation
910
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
911
     *  - the P2SH script covers signable types and P2WSH/P2WKH
912
     *  - the witnessScript covers signable types only
913
     *
914
     * @param SignData $signData
915
     * @param ScriptInterface $scriptPubKey
916
     * @param ScriptInterface $scriptSig
917
     * @param BufferInterface[] $witness
918
     * @return $this
919
     */
920 188
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
921
    {
922 188
        $classifier = new OutputClassifier();
923 188
        $sigVersion = SigHash::V0;
924 188
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
925
926 188
        if (!$this->allowComplexScripts) {
927 122
            if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
928 2
                throw new \RuntimeException('scriptPubKey not supported');
929
            }
930
        }
931
932 186
        $sigChunks = $this->evalPushOnly($scriptSig);
933
934 186
        if ($solution->getType() === ScriptType::P2SH) {
935 74
            $redeemScript = $this->findRedeemScript($sigChunks, $signData);
936 70
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
937 2
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
938
            }
939
940 68
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
941 68
            if (!$this->allowComplexScripts) {
942 38
                if (!in_array($solution->getType(), self::$validP2sh)) {
943 2
                    throw new \RuntimeException('Unsupported pay-to-script-hash script');
944
                }
945
            }
946
947 66
            $sigChunks = array_slice($sigChunks, 0, -1);
948
        }
949
950 178
        if ($solution->getType() === ScriptType::P2WKH) {
951 8
            $sigVersion = SigHash::V1;
952 8
            $solution = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
953 8
            $sigChunks = $witness;
954 172
        } else if ($solution->getType() === ScriptType::P2WSH) {
955 70
            $sigVersion = SigHash::V1;
956 70
            $witnessScript = $this->findWitnessScript($witness, $signData);
957
958
            // Essentially all the reference implementation does
959 62
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
960 2
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
961
            }
962
963 60
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
964 60
            if (!$this->allowComplexScripts) {
965 30
                if (!in_array($this->witnessScript->getType(), self::$canSign)) {
966 2
                    throw new \RuntimeException('Unsupported witness-script-hash script');
967
                }
968
            }
969
970 58
            $sigChunks = array_slice($witness, 0, -1);
971
        }
972
973 166
        $this->sigVersion = $sigVersion;
974 166
        $this->signScript = $solution;
975
976 166
        $this->extractScript($solution, $sigChunks, $signData);
977
978 136
        return $this;
979
    }
980
981
    /**
982
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
983
     *
984
     * @param ScriptInterface $scriptCode
985
     * @param int $sigHashType
986
     * @param int $sigVersion
987
     * @return BufferInterface
988
     */
989 128
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
990
    {
991 128
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
992 2
            throw new \RuntimeException('Invalid sigHashType requested');
993
        }
994
995 126
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
996
    }
997
998
    /**
999
     * Calculates the signature hash for the input for the given $sigHashType.
1000
     *
1001
     * @param int $sigHashType
1002
     * @return BufferInterface
1003
     */
1004 2
    public function getSigHash($sigHashType)
1005
    {
1006 2
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
1007
    }
1008
1009
    /**
1010
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
1011
     *
1012
     * @param PrivateKeyInterface $key
1013
     * @param ScriptInterface $scriptCode
1014
     * @param int $sigHashType
1015
     * @param int $sigVersion
1016
     * @return TransactionSignatureInterface
1017
     */
1018 126
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
1019
    {
1020 126
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
1021 126
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
1022 126
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
1023
    }
1024
1025
    /**
1026
     * Returns whether all required signatures have been provided.
1027
     *
1028
     * @return bool
1029
     */
1030 74
    public function isFullySigned()
1031
    {
1032 74
        foreach ($this->steps as $step) {
1033 74
            if ($step instanceof Conditional) {
1034
                if (!$step->hasValue()) {
1035
                    return false;
1036
                }
1037 74
            } else if ($step instanceof Checksig) {
1038 74
                if (!$step->isFullySigned()) {
1039 74
                    return false;
1040
                }
1041
            }
1042
        }
1043
1044 62
        return true;
1045
    }
1046
1047
    /**
1048
     * Returns the required number of signatures for this input.
1049
     *
1050
     * @return int
1051
     */
1052 74
    public function getRequiredSigs()
1053
    {
1054 74
        $count = 0;
1055 74
        foreach ($this->steps as $step) {
1056 74
            if ($step instanceof Checksig) {
1057 74
                $count += $step->getRequiredSigs();
1058
            }
1059
        }
1060 74
        return $count;
1061
    }
1062
1063
    /**
1064
     * Returns an array where the values are either null,
1065
     * or a TransactionSignatureInterface.
1066
     *
1067
     * @return TransactionSignatureInterface[]
1068
     */
1069 74
    public function getSignatures()
1070
    {
1071 74
        return $this->steps[0]->getSignatures();
0 ignored issues
show
Bug introduced by
The method getSignatures() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

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

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

Loading history...
1072
    }
1073
1074
    /**
1075
     * Returns an array where the values are either null,
1076
     * or a PublicKeyInterface.
1077
     *
1078
     * @return PublicKeyInterface[]
1079
     */
1080 52
    public function getPublicKeys()
1081
    {
1082 52
        return $this->steps[0]->getKeys();
0 ignored issues
show
Bug introduced by
The method getKeys() does not seem to exist on object<BitWasp\Bitcoin\T...on\Factory\Conditional>.

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

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

Loading history...
1083
    }
1084
1085
    /**
1086
     * OutputData for the script to be signed (will be
1087
     * equal to getScriptPubKey, or getRedeemScript, or
1088
     * getWitnessScript.
1089
     *
1090
     * @return OutputData
1091
     */
1092 50
    public function getSignScript()
1093
    {
1094 50
        return $this->signScript;
1095
    }
1096
1097
    /**
1098
     * OutputData for the txOut script.
1099
     *
1100
     * @return OutputData
1101
     */
1102 24
    public function getScriptPubKey()
1103
    {
1104 24
        return $this->scriptPubKey;
1105
    }
1106
1107
    /**
1108
     * Returns OutputData for the P2SH redeemScript.
1109
     *
1110
     * @return OutputData
1111
     */
1112 18
    public function getRedeemScript()
1113
    {
1114 18
        if (null === $this->redeemScript) {
1115
            throw new \RuntimeException("Input has no redeemScript, cannot call getRedeemScript");
1116
        }
1117
1118 18
        return $this->redeemScript;
1119
    }
1120
1121
    /**
1122
     * Returns OutputData for the P2WSH witnessScript.
1123
     *
1124
     * @return OutputData
1125
     */
1126 14
    public function getWitnessScript()
1127
    {
1128 14
        if (null === $this->witnessScript) {
1129
            throw new \RuntimeException("Input has no witnessScript, cannot call getWitnessScript");
1130
        }
1131
1132 14
        return $this->witnessScript;
1133
    }
1134
1135
    /**
1136
     * Returns whether the scriptPubKey is P2SH.
1137
     *
1138
     * @return bool
1139
     */
1140 50
    public function isP2SH()
1141
    {
1142 50
        if ($this->scriptPubKey->getType() === ScriptType::P2SH && ($this->redeemScript instanceof OutputData)) {
1143 18
            return true;
1144
        }
1145
1146 32
        return false;
1147
    }
1148
1149
    /**
1150
     * Returns whether the scriptPubKey or redeemScript is P2WSH.
1151
     *
1152
     * @return bool
1153
     */
1154 50
    public function isP2WSH()
1155
    {
1156 50
        if ($this->redeemScript instanceof OutputData) {
1157 18
            if ($this->redeemScript->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1158 8
                return true;
1159
            }
1160
        }
1161
1162 42
        if ($this->scriptPubKey->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1163 6
            return true;
1164
        }
1165
1166 36
        return false;
1167
    }
1168
1169
    /**
1170
     * @param int $stepIdx
1171
     * @param PrivateKeyInterface $privateKey
1172
     * @param int $sigHashType
1173
     * @return $this
1174
     */
1175 132
    public function signStep($stepIdx, PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1176
    {
1177 132
        if (!array_key_exists($stepIdx, $this->steps)) {
1178
            throw new \RuntimeException("Unknown step index");
1179
        }
1180
1181 132
        $checksig = $this->steps[$stepIdx];
1182
1183 132
        if (!($checksig instanceof Checksig)) {
1184
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
1185
        }
1186
1187 132
        if ($checksig->isFullySigned()) {
1188
            return $this;
1189
        }
1190
1191 132
        if (SigHash::V1 === $this->sigVersion && !$privateKey->isCompressed()) {
1192
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
1193
        }
1194
1195 132
        if ($checksig->getType() === ScriptType::P2PK) {
1196 58
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
1197 2
                throw new \RuntimeException('Signing with the wrong private key');
1198
            }
1199
1200 56
            if (!$checksig->hasSignature(0)) {
1201 56
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1202 56
                $checksig->setSignature(0, $signature);
1203
            }
1204 88
        } else if ($checksig->getType() === ScriptType::P2PKH) {
1205 38
            $publicKey = $privateKey->getPublicKey();
1206 38
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
1207 2
                throw new \RuntimeException('Signing with the wrong private key');
1208
            }
1209
1210 36
            if (!$checksig->hasSignature(0)) {
1211 36
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1212 36
                $checksig->setSignature(0, $signature);
1213
            }
1214
1215 36
            if (!$checksig->hasKey(0)) {
1216 36
                $checksig->setKey(0, $publicKey);
1217
            }
1218 54
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
1219 54
            $signed = false;
1220 54
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
1221 54
                if (!$checksig->hasSignature($keyIdx)) {
1222 54
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
1223 52
                        $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1224 52
                        $checksig->setSignature($keyIdx, $signature);
1225 54
                        $signed = true;
1226
                    }
1227
                }
1228
            }
1229
1230 54
            if (!$signed) {
1231 54
                throw new \RuntimeException('Signing with the wrong private key');
1232
            }
1233
        } else {
1234
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1235
        }
1236
1237 126
        return $this;
1238
    }
1239
1240
    /**
1241
     * Sign the input using $key and $sigHashTypes
1242
     *
1243
     * @param PrivateKeyInterface $privateKey
1244
     * @param int $sigHashType
1245
     * @return $this
1246
     */
1247 86
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1248
    {
1249 86
        return $this->signStep(0, $privateKey, $sigHashType);
1250
    }
1251
1252
    /**
1253
     * Verifies the input using $flags for script verification
1254
     *
1255
     * @param int $flags
1256
     * @return bool
1257
     */
1258 96
    public function verify($flags = null)
1259
    {
1260 96
        $consensus = ScriptFactory::consensus();
1261
1262 96
        if ($flags === null) {
1263 60
            $flags = $this->flags;
1264
        }
1265
1266 96
        $flags |= Interpreter::VERIFY_P2SH;
1267 96
        if (SigHash::V1 === $this->sigVersion) {
1268 32
            $flags |= Interpreter::VERIFY_WITNESS;
1269
        }
1270
1271 96
        $sig = $this->serializeSignatures();
1272
1273
        // Take serialized signatures, and use mutator to add this inputs sig data
1274 96
        $mutator = TransactionFactory::mutate($this->tx);
1275 96
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1276
1277 96
        if (SigHash::V1 === $this->sigVersion) {
1278 32
            $witness = [];
1279 32
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1280 32
                if ($i === $this->nInput) {
1281 32
                    $witness[] = $sig->getScriptWitness();
1282
                } else {
1283 2
                    $witness[] = new ScriptWitness([]);
1284
                }
1285
            }
1286
1287 32
            $mutator->witness($witness);
1288
        }
1289
1290 96
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1291
    }
1292
1293
    /**
1294
     * @return array
1295
     */
1296 126
    private function serializeSteps()
1297
    {
1298 126
        $results = [];
1299 126
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1300 126
            $step = $this->steps[$i];
1301
1302 126
            if ($step instanceof Conditional) {
1303 22
                $results[] = $step->serialize();
1304 126
            } else if ($step instanceof Checksig) {
1305 126
                if ($step->isRequired()) {
1306 126
                    if (count($step->getSignatures()) === 0) {
1307 50
                        break;
1308
                    }
1309
                }
1310
1311 126
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1312
1313 126
                if (!$step->isFullySigned()) {
1314 12
                    break;
1315
                }
1316
            }
1317
        }
1318
1319 126
        $values = [];
1320 126
        foreach (array_reverse($results) as $v) {
1321 126
            foreach ($v as $value) {
1322 126
                $values[] = $value;
1323
            }
1324
        }
1325
1326 126
        return $values;
1327
    }
1328
1329
    /**
1330
     * Produces a SigValues instance containing the scriptSig & script witness
1331
     *
1332
     * @return SigValues
1333
     */
1334 126
    public function serializeSignatures()
1335
    {
1336 126
        static $emptyScript = null;
1337 126
        static $emptyWitness = null;
1338 126
        if (is_null($emptyScript) || is_null($emptyWitness)) {
1339 2
            $emptyScript = new Script();
1340 2
            $emptyWitness = new ScriptWitness([]);
1341
        }
1342
1343 126
        $witness = [];
1344 126
        $scriptSigChunks = $this->serializeSteps();
1345
1346 126
        $solution = $this->scriptPubKey;
1347 126
        $p2sh = false;
1348 126
        if ($solution->getType() === ScriptType::P2SH) {
1349 42
            $p2sh = true;
1350 42
            $solution = $this->redeemScript;
1351
        }
1352
1353 126
        if ($solution->getType() === ScriptType::P2WKH) {
1354 8
            $witness = $scriptSigChunks;
1355 8
            $scriptSigChunks = [];
1356 120
        } else if ($solution->getType() === ScriptType::P2WSH) {
1357 38
            $witness = $scriptSigChunks;
1358 38
            $witness[] = $this->witnessScript->getScript()->getBuffer();
1359 38
            $scriptSigChunks = [];
1360
        }
1361
1362 126
        if ($p2sh) {
1363 42
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
1364
        }
1365
1366 126
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
1367
    }
1368
1369 50
    public function getSteps()
1370
    {
1371 50
        return $this->steps;
1372
    }
1373
}
1374