Completed
Pull Request — master (#538)
by thomas
69:50
created

InputSigner::tolerateInvalidPublicKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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