Completed
Pull Request — master (#538)
by thomas
70:51
created

InputSigner::verifySolution()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 4
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
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 TimeLock $timelock
624 2
     */
625
    public function checkTimeLock(TimeLock $timelock)
626
    {
627
        $info = $timelock->getInfo();
628 36
        if ($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY != 0 && $info instanceof CheckLocktimeVerify) {
629 36
            $verifyLocktime = $info->getLocktime();
630
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
631
                $input = $this->tx->getInput($this->nInput);
632 36
                if ($input->isFinal()) {
633
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
634 36
                }
635 40
636
                $locktime = $this->tx->getLockTime();
637
                if ($verifyLocktime <= Locktime::BLOCK_MAX && $locktime > Locktime::BLOCK_MAX) {
638
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
639 100
                }
640 50
641 46
                if ($verifyLocktime > Locktime::BLOCK_MAX && $locktime <= Locktime::BLOCK_MAX) {
642
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
643 46
                }
644 46
645 46
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
646 46
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
647 2
            }
648
        }
649
650
        if ($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY != 0 && $info instanceof CheckSequenceVerify) {
651 44
            $nSequence = $this->tx->getInput($this->nInput)->getSequence();
652 40
            // No CSV if input is final
653
            if ($nSequence & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG != 0) {
654
                return;
655 44
            }
656 44
657
            if (!$this->signatureChecker->checkSequence(Number::int($nSequence))) {
658
                $masked = $info->getLocktime() & TransactionInput::SEQUENCE_LOCKTIME_MASK;
0 ignored issues
show
Bug introduced by
The method getLocktime() does not seem to exist on object<BitWasp\Bitcoin\T...fo\CheckSequenceVerify>.

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...
659
                $requiredLock = "{$masked} " . ($info->isRelativeToBlock() ? " (blocks)" : "(seconds after txOut)");
660 48
                throw new \RuntimeException("Output unspendable with this sequence, must be locked for {$requiredLock}");
661 64
            }
662
        }
663 64
    }
664 64
665 64
    /**
666 64
     * This function is strictly for $canSign types.
667
     * It will extract signatures/publicKeys when given $outputData, and $stack.
668
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
669 60
     *
670 60
     * @param ScriptInterface $script
671
     * @param Checksig $checksig
672
     * @param Stack $stack
673
     * @param int $sigVersion
674
     * @param bool $expectFalse
675
     */
676
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, $sigVersion, $expectFalse)
677 28
    {
678 4
        $size = count($stack);
679 4
680 4
        if ($checksig->getType() === ScriptType::P2PKH) {
681
            if ($size > 1) {
682
                $vchPubKey = $stack->pop();
683 4
                $vchSig = $stack->pop();
684 4
685 4
                $value = false;
686 4
                if (!$expectFalse) {
687
                    $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 683 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 682 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...
688
                    if (!$value) {
689
                        throw new \RuntimeException('Existing signatures are invalid!');
690
                    }
691
                }
692 4
693 4
                if (!$checksig->isVerify()) {
694
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
695
                }
696
697
                if (!$expectFalse) {
698
                    $checksig
699
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 683 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...
700
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 682 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...
701 60
                    ;
702
                }
703 60
            }
704 60
        } else if ($checksig->getType() === ScriptType::P2PK) {
705 60
            if ($size > 0) {
706 52
                $vchSig = $stack->pop();
707 52
708
                $value = false;
709
                if (!$expectFalse) {
710 60
                    $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 706 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...
711 60
                    if (!$value) {
712
                        throw new \RuntimeException('Existing signatures are invalid!');
713 60
                    }
714 58
                }
715 50
716
                if (!$checksig->isVerify()) {
717 50
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
718 2
                }
719
720 48
                if (!$expectFalse) {
721
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 706 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...
722 56
                }
723 56
            }
724
725 56
            $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...
726
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
727
            /** @var Multisig $info */
728 2
            $info = $checksig->getInfo();
729 2
            $keyBuffers = $info->getKeyBuffers();
730 2
            foreach ($keyBuffers as $idx => $keyBuf) {
731
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
732
            }
733
734 58
            $value = false;
735 50
            if ($this->padUnsignedMultisigs) {
736
                // Multisig padding is only used for partially signed transactions,
737
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
738 58
                // values (including the dummy), with one for each candidate signature,
739 58
                // such that $this->signatures state is captured.
740 58
                // The feature serves to skip validation/sorting an incomplete multisig.
741
742
                if ($size === 1 + $info->getKeyCount()) {
743
                    $sigBufCount = 0;
744 58
                    $null = new Buffer();
745 58
                    $keyToSigMap = new \SplObjectStorage();
746
747
                    // Reproduce $keyToSigMap and $sigBufCount
748
                    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...
749
                        if (!$stack[-1 - $i]->equals($null)) {
750 126
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
751
                            $sigBufCount++;
752
                        }
753
                    }
754
755
                    // We observed $this->requiredSigs sigs, therefore we can
756
                    // say the implementation is incompatible
757
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
758
                        throw new \RuntimeException("Padding is forbidden for a fully signed multisig script");
759
                    }
760
761 44
                    $toDelete = 1 + $info->getKeyCount();
762
                    $value = true;
763 44
                }
764 36
            }
765 36
766 36
            if (!isset($toDelete) || !isset($keyToSigMap)) {
767 36
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
768
                $sigBufs = [];
769
                $max = min($checksig->getRequiredSigs(), $size - 1);
770
                for ($i = 0; $i < $max; $i++) {
771 40
                    $vchSig = $stack[-1 - $i];
772 2
                    $sigBufs[] = $vchSig;
773
                }
774 38
775
                $sigBufs = array_reverse($sigBufs);
776
                $sigBufCount = count($sigBufs);
777 40
778
                if (!$expectFalse) {
779
                    if ($sigBufCount > 0) {
780
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
781
                        // Here we learn if any signatures were invalid, it won't be in the map.
782
                        if ($sigBufCount !== count($keyToSigMap)) {
783
                            throw new \RuntimeException('Existing signatures are invalid!');
784
                        }
785
                        $toDelete = 1 + count($keyToSigMap);
786
                    } else {
787
                        $toDelete = 0;
788
                        $keyToSigMap = new \SplObjectStorage();
789 40
                    }
790
                    $value = true;
791 40
                } else {
792 32
                    // should check that all signatures are zero
793 32
                    $keyToSigMap = new \SplObjectStorage();
794 32
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
795 32
                    $value = false;
796
                }
797
            }
798
799 36
            while ($toDelete--) {
800 4
                $stack->pop();
801
            }
802 32
803
            foreach ($keyBuffers as $idx => $key) {
804
                if (isset($keyToSigMap[$key])) {
805 32
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
806
                }
807
            }
808
809
            if (!$checksig->isVerify()) {
810
                $stack->push($value ? new Buffer("\x01") : new Buffer());
811
            }
812
        } else {
813
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
814
        }
815
    }
816
817
    /**
818
     * Checks $chunks (a decompiled scriptSig) for it's last element,
819
     * or defers to SignData. If both are provided, it checks the
820
     * value from $chunks against SignData.
821
     *
822 158
     * @param BufferInterface[] $chunks
823
     * @param SignData $signData
824 158
     * @return ScriptInterface
825 158
     */
826 158
    private function findRedeemScript(array $chunks, SignData $signData)
827
    {
828 158
        if (count($chunks) > 0) {
829 122
            $redeemScript = new Script($chunks[count($chunks) - 1]);
830 2
            if ($signData->hasRedeemScript()) {
831
                if (!$redeemScript->equals($signData->getRedeemScript())) {
832
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
833
                }
834 156
            }
835
        } else {
836 156
            if (!$signData->hasRedeemScript()) {
837 44
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
838 40
            }
839 2
            $redeemScript = $signData->getRedeemScript();
840
        }
841
842 38
        return $redeemScript;
843 38
    }
844 38
845 2
    /**
846
     * Checks $witness (a witness structure) for it's last element,
847
     * or defers to SignData. If both are provided, it checks the
848
     * value from $chunks against SignData.
849 36
     *
850
     * @param BufferInterface[] $witness
851
     * @param SignData $signData
852 148
     * @return ScriptInterface
853 8
     */
854 8
    private function findWitnessScript(array $witness, SignData $signData)
855 8
    {
856 142
        if (count($witness) > 0) {
857 40
            $witnessScript = new Script($witness[count($witness) - 1]);
858 40
            if ($signData->hasWitnessScript()) {
859
                if (!$witnessScript->equals($signData->getWitnessScript())) {
860
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
861 32
                }
862 2
            }
863
        } else {
864
            if (!$signData->hasWitnessScript()) {
865 30
                throw new \RuntimeException('Witness script not provided in sign data or witness');
866 30
            }
867 30
            $witnessScript = $signData->getWitnessScript();
868 2
        }
869
870
        return $witnessScript;
871
    }
872 28
873
    /**
874
     * Needs to be called before using the instance. By `extract`.
875 136
     *
876 136
     * It ensures that violating the following prevents instance creation
877
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
878 136
     *  - the P2SH script covers signable types and P2WSH/P2WKH
879
     *  - the witnessScript covers signable types only
880 126
     *
881
     * @param SignData $signData
882
     * @param ScriptInterface $scriptPubKey
883
     * @param ScriptInterface $scriptSig
884
     * @param BufferInterface[] $witness
885
     * @return $this
886
     */
887
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
888
    {
889
        $classifier = new OutputClassifier();
890
        $sigVersion = SigHash::V0;
891 118
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
892
893 118
        if (!$this->allowComplexScripts) {
894 2
            if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
895
                throw new \RuntimeException('scriptPubKey not supported');
896
            }
897 116
        }
898
899
        $sigChunks = $this->evalPushOnly($scriptSig);
900
901
        if ($solution->getType() === ScriptType::P2SH) {
902
            $redeemScript = $this->findRedeemScript($sigChunks, $signData);
903
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
904
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
905
            }
906 2
907
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
908 2
            if (!$this->allowComplexScripts) {
909
                if (!in_array($solution->getType(), self::$validP2sh)) {
910
                    throw new \RuntimeException('Unsupported pay-to-script-hash script');
911
                }
912
            }
913
914
            $sigChunks = array_slice($sigChunks, 0, -1);
915
        }
916
917
        if ($solution->getType() === ScriptType::P2WKH) {
918
            $sigVersion = SigHash::V1;
919
            $solution = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
920 116
            $sigChunks = $witness;
921
        } else if ($solution->getType() === ScriptType::P2WSH) {
922 116
            $sigVersion = SigHash::V1;
923 116
            $witnessScript = $this->findWitnessScript($witness, $signData);
924 116
925
            // Essentially all the reference implementation does
926
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
927
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
928
            }
929
930
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
931
            if (!$this->allowComplexScripts) {
932 74
                if (!in_array($this->witnessScript->getType(), self::$canSign)) {
933
                    throw new \RuntimeException('Unsupported witness-script-hash script');
934 74
                }
935 74
            }
936
937
            $sigChunks = array_slice($witness, 0, -1);
938
        }
939 74
940 74
        $this->sigVersion = $sigVersion;
941 74
        $this->signScript = $solution;
942
943
        $this->extractScript($solution, $sigChunks, $signData);
944
945
        return $this;
946 62
    }
947
948
    /**
949
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
950
     *
951
     * @param ScriptInterface $scriptCode
952
     * @param int $sigHashType
953
     * @param int $sigVersion
954 74
     * @return BufferInterface
955
     */
956 74
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
957 74
    {
958 74
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
959 74
            throw new \RuntimeException('Invalid sigHashType requested');
960
        }
961
962 74
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
963
    }
964
965
    /**
966
     * Calculates the signature hash for the input for the given $sigHashType.
967
     *
968
     * @param int $sigHashType
969
     * @return BufferInterface
970
     */
971 74
    public function getSigHash($sigHashType)
972
    {
973 74
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
974
    }
975
976
    /**
977
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
978
     *
979
     * @param PrivateKeyInterface $key
980
     * @param ScriptInterface $scriptCode
981
     * @param int $sigHashType
982 52
     * @param int $sigVersion
983
     * @return TransactionSignatureInterface
984 52
     */
985
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
986
    {
987
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
988
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
989
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
990
    }
991
992
    /**
993
     * Returns whether all required signatures have been provided.
994 50
     *
995
     * @return bool
996 50
     */
997
    public function isFullySigned()
998
    {
999
        foreach ($this->steps as $step) {
1000
            if ($step instanceof Conditional) {
1001
                if (!$step->hasValue()) {
1002
                    return false;
1003
                }
1004 24
            } else if ($step instanceof Checksig) {
1005
                if (!$step->isFullySigned()) {
1006 24
                    return false;
1007
                }
1008
            }
1009
        }
1010
1011
        return true;
1012
    }
1013
1014 18
    /**
1015
     * Returns the required number of signatures for this input.
1016 18
     *
1017
     * @return int
1018
     */
1019
    public function getRequiredSigs()
1020 18
    {
1021
        $count = 0;
1022
        foreach ($this->steps as $step) {
1023
            if ($step instanceof Checksig) {
1024
                $count += $step->getRequiredSigs();
1025
            }
1026
        }
1027
        return $count;
1028 14
    }
1029
1030 14
    /**
1031
     * Returns an array where the values are either null,
1032
     * or a TransactionSignatureInterface.
1033
     *
1034 14
     * @return TransactionSignatureInterface[]
1035
     */
1036
    public function getSignatures()
1037
    {
1038
        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...
1039
    }
1040
1041
    /**
1042 50
     * Returns an array where the values are either null,
1043
     * or a PublicKeyInterface.
1044 50
     *
1045 18
     * @return PublicKeyInterface[]
1046
     */
1047
    public function getPublicKeys()
1048 32
    {
1049
        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...
1050
    }
1051
1052
    /**
1053
     * OutputData for the script to be signed (will be
1054
     * equal to getScriptPubKey, or getRedeemScript, or
1055
     * getWitnessScript.
1056 50
     *
1057
     * @return OutputData
1058 50
     */
1059 18
    public function getSignScript()
1060 8
    {
1061
        return $this->signScript;
1062
    }
1063
1064 42
    /**
1065 6
     * OutputData for the txOut script.
1066
     *
1067
     * @return OutputData
1068 36
     */
1069
    public function getScriptPubKey()
1070
    {
1071
        return $this->scriptPubKey;
1072
    }
1073
1074
    /**
1075
     * Returns OutputData for the P2SH redeemScript.
1076
     *
1077 122
     * @return OutputData
1078
     */
1079 122
    public function getRedeemScript()
1080
    {
1081
        if (null === $this->redeemScript) {
1082
            throw new \RuntimeException("Input has no redeemScript, cannot call getRedeemScript");
1083 122
        }
1084
1085 122
        return $this->redeemScript;
1086
    }
1087
1088
    /**
1089 122
     * Returns OutputData for the P2WSH witnessScript.
1090
     *
1091
     * @return OutputData
1092
     */
1093 122
    public function getWitnessScript()
1094
    {
1095
        if (null === $this->witnessScript) {
1096
            throw new \RuntimeException("Input has no witnessScript, cannot call getWitnessScript");
1097 122
        }
1098 48
1099 2
        return $this->witnessScript;
1100
    }
1101
1102 46
    /**
1103 46
     * Returns whether the scriptPubKey is P2SH.
1104 46
     *
1105
     * @return bool
1106 88
     */
1107 38
    public function isP2SH()
1108 38
    {
1109 2
        if ($this->scriptPubKey->getType() === ScriptType::P2SH && ($this->redeemScript instanceof OutputData)) {
1110
            return true;
1111
        }
1112 36
1113 36
        return false;
1114 36
    }
1115
1116
    /**
1117 36
     * Returns whether the scriptPubKey or redeemScript is P2WSH.
1118 36
     *
1119
     * @return bool
1120 54
     */
1121 54
    public function isP2WSH()
1122 54
    {
1123 54
        if ($this->redeemScript instanceof OutputData) {
1124 54
            if ($this->redeemScript->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1125 52
                return true;
1126 52
            }
1127 54
        }
1128
1129
        if ($this->scriptPubKey->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
1130
            return true;
1131
        }
1132 54
1133 54
        return false;
1134
    }
1135
1136
    /**
1137
     * @param int $stepIdx
1138
     * @param PrivateKeyInterface $privateKey
1139 116
     * @param int $sigHashType
1140
     * @return $this
1141
     */
1142
    public function signStep($stepIdx, PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1143
    {
1144
        if (!array_key_exists($stepIdx, $this->steps)) {
1145
            throw new \RuntimeException("Unknown step index");
1146
        }
1147
1148
        $checksig = $this->steps[$stepIdx];
1149 86
1150
        if (!($checksig instanceof Checksig)) {
1151 86
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
1152
        }
1153
1154
        if ($checksig->isFullySigned()) {
1155
            return $this;
1156
        }
1157
1158
        if (SigHash::V1 === $this->sigVersion && !$privateKey->isCompressed()) {
1159
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
1160 86
        }
1161
1162 86
        if ($checksig->getType() === ScriptType::P2PK) {
1163
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
1164 86
                throw new \RuntimeException('Signing with the wrong private key');
1165 50
            }
1166
1167
            if (!$checksig->hasSignature(0)) {
1168 86
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1169 86
                $checksig->setSignature(0, $signature);
1170 22
            }
1171
        } else if ($checksig->getType() === ScriptType::P2PKH) {
1172
            $publicKey = $privateKey->getPublicKey();
1173 86
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
1174
                throw new \RuntimeException('Signing with the wrong private key');
1175
            }
1176 86
1177 86
            if (!$checksig->hasSignature(0)) {
1178
                $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1179 86
                $checksig->setSignature(0, $signature);
1180 22
            }
1181 22
1182 22
            if (!$checksig->hasKey(0)) {
1183 22
                $checksig->setKey(0, $publicKey);
1184
            }
1185 2
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
1186
            $signed = false;
1187
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
1188
                if (!$checksig->hasSignature($keyIdx)) {
1189 22
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
1190
                        $signature = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
1191
                        $checksig->setSignature($keyIdx, $signature);
1192 86
                        $signed = true;
1193
                    }
1194
                }
1195
            }
1196
1197
            if (!$signed) {
1198 116
                throw new \RuntimeException('Signing with the wrong private key');
1199
            }
1200 116
        } else {
1201 116
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1202 116
        }
1203
1204 116
        return $this;
1205 22
    }
1206 116
1207 116
    /**
1208 116
     * Sign the input using $key and $sigHashTypes
1209 50
     *
1210
     * @param PrivateKeyInterface $privateKey
1211
     * @param int $sigHashType
1212
     * @return $this
1213 116
     */
1214
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
1215 116
    {
1216 12
        return $this->signStep(0, $privateKey, $sigHashType);
1217
    }
1218
1219
    /**
1220
     * Verifies the input using $flags for script verification
1221 116
     *
1222 116
     * @param int $flags
1223 116
     * @return bool
1224 116
     */
1225
    public function verify($flags = null)
1226
    {
1227
        $consensus = ScriptFactory::consensus();
1228 116
1229
        if ($flags === null) {
1230
            $flags = $this->flags;
1231
        }
1232
1233
        $flags |= Interpreter::VERIFY_P2SH;
1234
        if (SigHash::V1 === $this->sigVersion) {
1235
            $flags |= Interpreter::VERIFY_WITNESS;
1236 116
        }
1237
1238 116
        $sig = $this->serializeSignatures();
1239 116
1240 116
        // Take serialized signatures, and use mutator to add this inputs sig data
1241 2
        $mutator = TransactionFactory::mutate($this->tx);
1242 2
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1243
1244
        if (SigHash::V1 === $this->sigVersion) {
1245 116
            $witness = [];
1246 116
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1247
                if ($i === $this->nInput) {
1248 116
                    $witness[] = $sig->getScriptWitness();
1249 116
                } else {
1250 116
                    $witness[] = new ScriptWitness([]);
1251 32
                }
1252 32
            }
1253
1254
            $mutator->witness($witness);
1255 116
        }
1256 8
1257 8
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1258 110
    }
1259 28
1260 28
    /**
1261 28
     * @return array
1262
     */
1263
    private function serializeSteps()
1264 116
    {
1265 32
        $results = [];
1266
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1267
            $step = $this->steps[$i];
1268 116
1269
            if ($step instanceof Conditional) {
1270
                $results[] = $step->serialize();
1271 50
            } else if ($step instanceof Checksig) {
1272
                if ($step->isRequired()) {
1273 50
                    if (count($step->getSignatures()) === 0) {
1274
                        break;
1275
                    }
1276
                }
1277
1278
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1279
1280
                if (!$step->isFullySigned()) {
1281
                    break;
1282
                }
1283
            }
1284
        }
1285
1286
        $values = [];
1287
        foreach (array_reverse($results) as $v) {
1288
            foreach ($v as $value) {
1289
                $values[] = $value;
1290
            }
1291
        }
1292
1293
        return $values;
1294
    }
1295
1296
    /**
1297
     * Produces a SigValues instance containing the scriptSig & script witness
1298
     *
1299
     * @return SigValues
1300
     */
1301
    public function serializeSignatures()
1302
    {
1303
        static $emptyScript = null;
1304
        static $emptyWitness = null;
1305
        if (is_null($emptyScript) || is_null($emptyWitness)) {
1306
            $emptyScript = new Script();
1307
            $emptyWitness = new ScriptWitness([]);
1308
        }
1309
1310
        $witness = [];
1311
        $scriptSigChunks = $this->serializeSteps();
1312
1313
        $solution = $this->scriptPubKey;
1314
        $p2sh = false;
1315
        if ($solution->getType() === ScriptType::P2SH) {
1316
            $p2sh = true;
1317
            $solution = $this->redeemScript;
1318
        }
1319
1320
        if ($solution->getType() === ScriptType::P2WKH) {
1321
            $witness = $scriptSigChunks;
1322
            $scriptSigChunks = [];
1323
        } else if ($solution->getType() === ScriptType::P2WSH) {
1324
            $witness = $scriptSigChunks;
1325
            $witness[] = $this->witnessScript->getScript()->getBuffer();
1326
            $scriptSigChunks = [];
1327
        }
1328
1329
        if ($p2sh) {
1330
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
1331
        }
1332
1333
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
1334
    }
1335
1336
    public function getSteps()
1337
    {
1338
        return $this->steps;
1339
    }
1340
}
1341