Completed
Push — master ( c780c0...f2b04e )
by thomas
60:38 queued 58:10
created

InputSigner   F

Complexity

Total Complexity 176

Size/Duplication

Total Lines 1074
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 37

Test Coverage

Coverage 94.67%

Importance

Changes 0
Metric Value
dl 0
loc 1074
ccs 391
cts 413
cp 0.9467
rs 1.304
c 0
b 0
f 0
wmc 176
lcom 1
cbo 37

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 24 4
B ensureAcceptableScripts() 0 24 6
A extract() 0 20 3
A padUnsignedMultisigs() 0 5 1
A tolerateInvalidPublicKey() 0 5 1
A allowComplexScripts() 0 5 1
A parseStepPublicKey() 0 12 3
A sortMultisigs() 0 32 5
B classifySignStep() 0 37 6
A parseSequence() 0 28 5
B extractConditionalOp() 0 34 7
A step() 0 8 2
A compareRangeAgainstThreshold() 0 12 5
C checkTimeLock() 0 53 17
A checkSignature() 0 8 2
F extractChecksig() 0 141 33
A calculateSigHashUnsafe() 0 8 2
A getSigHash() 0 4 1
A calculateSignature() 0 5 1
A isFullySigned() 0 16 6
A getRequiredSigs() 0 10 3
A getSignatures() 0 4 1
A getPublicKeys() 0 4 1
A getInputScripts() 0 4 1
D signStep() 0 64 19
A sign() 0 4 1
B verify() 0 34 6
B serializeSteps() 0 32 9
A serializeSignatures() 0 4 1
A getSteps() 0 4 1
D extractScript() 0 99 22

How to fix   Complexity   

Complex Class

Complex classes like InputSigner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InputSigner, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Transaction\Factory;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
12
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
13
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
14
use BitWasp\Bitcoin\Exceptions\SignerException;
15
use BitWasp\Bitcoin\Exceptions\UnsupportedScript;
16
use BitWasp\Bitcoin\Locktime;
17
use BitWasp\Bitcoin\Script\Classifier\OutputData;
18
use BitWasp\Bitcoin\Script\FullyQualifiedScript;
19
use BitWasp\Bitcoin\Script\Interpreter\Checker;
20
use BitWasp\Bitcoin\Script\Interpreter\CheckerBase;
21
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
22
use BitWasp\Bitcoin\Script\Interpreter\Number;
23
use BitWasp\Bitcoin\Script\Interpreter\Stack;
24
use BitWasp\Bitcoin\Script\Opcodes;
25
use BitWasp\Bitcoin\Script\Parser\Operation;
26
use BitWasp\Bitcoin\Script\Path\BranchInterpreter;
27
use BitWasp\Bitcoin\Script\ScriptFactory;
28
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
29
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkey;
30
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkeyHash;
31
use BitWasp\Bitcoin\Script\ScriptInterface;
32
use BitWasp\Bitcoin\Script\ScriptType;
33
use BitWasp\Bitcoin\Script\ScriptWitness;
34
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
35
use BitWasp\Bitcoin\Signature\TransactionSignature;
36
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
37
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckLocktimeVerify;
38
use BitWasp\Bitcoin\Transaction\Factory\ScriptInfo\CheckSequenceVerify;
39
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
40
use BitWasp\Bitcoin\Transaction\TransactionFactory;
41
use BitWasp\Bitcoin\Transaction\TransactionInput;
42
use BitWasp\Bitcoin\Transaction\TransactionInterface;
43
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
44
use BitWasp\Buffertools\Buffer;
45
use BitWasp\Buffertools\BufferInterface;
46
47
class InputSigner implements InputSignerInterface
48
{
49
    /**
50
     * @var array
51
     */
52
    protected static $canSign = [
53
        ScriptType::P2PKH,
54
        ScriptType::P2PK,
55
        ScriptType::MULTISIG
56
    ];
57
58
    /**
59
     * @var array
60
     */
61
    protected static $validP2sh = [
62
        ScriptType::P2WKH,
63
        ScriptType::P2WSH,
64
        ScriptType::P2PKH,
65
        ScriptType::P2PK,
66
        ScriptType::MULTISIG
67
    ];
68
69
    /**
70
     * @var EcAdapterInterface
71
     */
72
    private $ecAdapter;
73
74
    /**
75
     * @var FullyQualifiedScript
76
     */
77
    private $fqs;
78
79
    /**
80
     * @var bool
81
     */
82
    private $padUnsignedMultisigs = false;
83
84
    /**
85
     * @var bool
86
     */
87
    private $tolerateInvalidPublicKey = false;
88
89
    /**
90
     * @var bool
91
     */
92
    private $allowComplexScripts = false;
93
94
    /**
95
     * @var SignData
96
     */
97
    private $signData;
98
99
    /**
100
     * @var int
101
     */
102
    private $flags;
103
104
    /**
105
     * @var TransactionInterface
106
     */
107
    private $tx;
108
109
    /**
110
     * @var int
111
     */
112
    private $nInput;
113
114
    /**
115
     * @var TransactionOutputInterface
116
     */
117
    private $txOut;
118
119
    /**
120
     * @var Interpreter
121
     */
122
    private $interpreter;
123
124
    /**
125
     * @var Checker
126
     */
127
    private $signatureChecker;
128
129
    /**
130
     * @var TransactionSignatureSerializer
131
     */
132
    private $txSigSerializer;
133
134
    /**
135
     * @var PublicKeySerializerInterface
136
     */
137
    private $pubKeySerializer;
138
139
    /**
140
     * @var Conditional[]|Checksig[]
141
     */
142
    private $steps = [];
143
144
    /**
145
     * InputSigner constructor.
146
     *
147
     * Note, the implementation of this class is considered internal
148
     * and only the methods exposed on InputSignerInterface should
149
     * be depended on to avoid BC breaks.
150
     *
151
     * The only recommended way to produce this class is using Signer::input()
152
     *
153
     * @param EcAdapterInterface $ecAdapter
154
     * @param TransactionInterface $tx
155
     * @param int $nInput
156
     * @param TransactionOutputInterface $txOut
157
     * @param SignData $signData
158
     * @param CheckerBase $checker
159
     * @param TransactionSignatureSerializer|null $sigSerializer
160
     * @param PublicKeySerializerInterface|null $pubKeySerializer
161
     */
162 130
    public function __construct(
163
        EcAdapterInterface $ecAdapter,
164
        TransactionInterface $tx,
165
        int $nInput,
166
        TransactionOutputInterface $txOut,
167
        SignData $signData,
168
        CheckerBase $checker,
169
        TransactionSignatureSerializer $sigSerializer = null,
170
        PublicKeySerializerInterface $pubKeySerializer = null
171
    ) {
172 130
        $this->ecAdapter = $ecAdapter;
173 130
        $this->tx = $tx;
174 130
        $this->nInput = $nInput;
175 130
        $this->txOut = $txOut;
176 130
        $this->signData = $signData;
177
178 130
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
179 130
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
180
181 130
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
182 130
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
183 130
        $this->interpreter = new Interpreter($this->ecAdapter);
184 130
        $this->signatureChecker = $checker;
0 ignored issues
show
Documentation Bug introduced by
$checker is of type object<BitWasp\Bitcoin\S...nterpreter\CheckerBase>, but the property $signatureChecker was declared to be of type object<BitWasp\Bitcoin\S...pt\Interpreter\Checker>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
185 130
    }
186
187
    /**
188
     * Ensures a FullyQualifiedScript will be accepted
189
     * by the InputSigner.
190
     *
191
     * @param FullyQualifiedScript $script
192
     */
193 88
    public static function ensureAcceptableScripts(FullyQualifiedScript $script)
194
    {
195 88
        $spkType = $script->scriptPubKey()->getType();
196
197 88
        if ($spkType !== ScriptType::P2SH) {
198 60
            if (!in_array($spkType, self::$validP2sh)) {
199 1
                throw new UnsupportedScript("scriptPubKey not supported");
200
            }
201 59
            $hasWitnessScript = $spkType === ScriptType::P2WSH;
202
        } else {
203 28
            $rsType = $script->redeemScript()->getType();
204 28
            if (!in_array($rsType, self::$validP2sh)) {
205 1
                throw new UnsupportedScript("Unsupported pay-to-script-hash script");
206
            }
207 27
            $hasWitnessScript = $rsType === ScriptType::P2WSH;
208
        }
209
210 86
        if ($hasWitnessScript) {
211 22
            $wsType = $script->witnessScript()->getType();
212 22
            if (!in_array($wsType, self::$canSign)) {
213 1
                throw new UnsupportedScript('Unsupported witness-script-hash script');
214
            }
215
        }
216 85
    }
217
218
    /**
219
     *  It ensures that violating the following prevents instance creation
220
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
221
     *  - the P2SH script covers signable types and P2WSH/P2WKH
222
     *  - the witnessScript covers signable types only
223
     * @return $this|InputSigner
224
     * @throws ScriptRuntimeException
225
     * @throws SignerException
226
     * @throws \Exception
227
     */
228 130
    public function extract()
229
    {
230 130
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
231 129
        $witnesses = $this->tx->getWitnesses();
232 129
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput] : new ScriptWitness();
233
234 129
        $fqs = FullyQualifiedScript::fromTxData($this->txOut->getScript(), $scriptSig, $witness, $this->signData);
235 121
        if (!$this->allowComplexScripts) {
236 88
            self::ensureAcceptableScripts($fqs);
237
        }
238
239 118
        $this->fqs = $fqs;
240 118
        $this->steps = $this->extractScript(
241 118
            $this->fqs->signScript(),
242 118
            $this->fqs->extractStack($scriptSig, $witness),
243 118
            $this->signData
244
        );
245
246 103
        return $this;
247
    }
248
249
    /**
250
     * @param bool $setting
251
     * @return $this
252
     */
253 115
    public function padUnsignedMultisigs(bool $setting)
254
    {
255 115
        $this->padUnsignedMultisigs = $setting;
256 115
        return $this;
257
    }
258
259
    /**
260
     * @param bool $setting
261
     * @return $this
262
     */
263 115
    public function tolerateInvalidPublicKey(bool $setting)
264
    {
265 115
        $this->tolerateInvalidPublicKey = $setting;
266 115
        return $this;
267
    }
268
269
    /**
270
     * @param bool $setting
271
     * @return $this
272
     */
273 115
    public function allowComplexScripts(bool $setting)
274
    {
275 115
        $this->allowComplexScripts = $setting;
276 115
        return $this;
277
    }
278
279
    /**
280
     * @param BufferInterface $vchPubKey
281
     * @return PublicKeyInterface|null
282
     * @throws \Exception
283
     */
284 97
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
285
    {
286
        try {
287 97
            return $this->pubKeySerializer->parse($vchPubKey);
288 3
        } catch (\Exception $e) {
289 3
            if ($this->tolerateInvalidPublicKey) {
290 1
                return null;
291
            }
292
293 2
            throw $e;
294
        }
295
    }
296
297
    /**
298
     * @param ScriptInterface $script
299
     * @param BufferInterface[] $signatures
300
     * @param BufferInterface[] $publicKeys
301
     * @param int $sigVersion
302
     * @return \SplObjectStorage
303
     * @throws \BitWasp\Bitcoin\Exceptions\ScriptRuntimeException
304
     */
305 30
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, int $sigVersion): \SplObjectStorage
306
    {
307 30
        $sigCount = count($signatures);
308 30
        $keyCount = count($publicKeys);
309 30
        $ikey = $isig = 0;
310 30
        $fSuccess = true;
311 30
        $result = new \SplObjectStorage;
312
313 30
        while ($fSuccess && $sigCount > 0) {
314
            // Fetch the signature and public key
315 30
            $sig = $signatures[$isig];
316 30
            $pubkey = $publicKeys[$ikey];
317
318 30
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
319 29
                $result[$pubkey] = $sig;
320 29
                $isig++;
321 29
                $sigCount--;
322
            }
323
324 30
            $ikey++;
325 30
            $keyCount--;
326
327
            // If there are more signatures left than keys left,
328
            // then too many signatures have failed. Exit early,
329
            // without checking any further signatures.
330 30
            if ($sigCount > $keyCount) {
331 1
                $fSuccess = false;
332
            }
333
        }
334
335 30
        return $result;
336
    }
337
338
    /**
339
     * @param array $decoded
340
     * @param null $solution
341
     * @return null|TimeLock|Checksig
342
     */
343 118
    private function classifySignStep(array $decoded, &$solution = null)
344
    {
345
        try {
346 118
            $details = Multisig::fromDecodedScript($decoded, $this->pubKeySerializer, true);
347 38
            $solution = $details->getKeyBuffers();
348 38
            return new Checksig($details);
349 118
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
350
        }
351
352
        try {
353 118
            $details = PayToPubkey::fromDecodedScript($decoded, true);
354 47
            $solution = $details->getKeyBuffer();
355 47
            return new Checksig($details);
356 118
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
357
        }
358
359
        try {
360 118
            $details = PayToPubkeyHash::fromDecodedScript($decoded, true);
361 44
            $solution = $details->getPubKeyHash();
362 44
            return new Checksig($details);
363 118
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
364
        }
365
366
        try {
367 118
            $details = CheckLocktimeVerify::fromDecodedScript($decoded);
368 8
            return new TimeLock($details);
369 118
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
370
        }
371
372
        try {
373 118
            $details = CheckSequenceVerify::fromDecodedScript($decoded);
374 7
            return new TimeLock($details);
375 118
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
376
        }
377
378 118
        return null;
379
    }
380
381
    /**
382
     * @param Operation[] $scriptOps
383
     * @return Checksig[]
384
     */
385 118
    public function parseSequence(array $scriptOps)
386
    {
387 118
        $j = 0;
388 118
        $l = count($scriptOps);
389 118
        $result = [];
390 118
        while ($j < $l) {
391 118
            $step = null;
392 118
            $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...
393
394
            // increment the $last, and break if it's valid
395 118
            for ($i = 0; $i < ($l - $j) + 1; $i++) {
396 118
                $slice = array_slice($scriptOps, $j, $i);
397 118
                $step = $this->classifySignStep($slice, $solution);
398 118
                if ($step !== null) {
399 118
                    break;
400
                }
401
            }
402
403 118
            if (null === $step) {
404
                throw new \RuntimeException("Invalid script");
405
            } else {
406 118
                $j += $i;
407 118
                $result[] = $step;
408
            }
409
        }
410
411 118
        return $result;
412
    }
413
414
    /**
415
     * @param Operation $operation
416
     * @param Stack $mainStack
417
     * @param bool[] $pathData
418
     * @return Conditional
419
     */
420 11
    public function extractConditionalOp(Operation $operation, Stack $mainStack, array &$pathData): Conditional
421
    {
422 11
        $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...
423
424 11
        if (!$mainStack->isEmpty()) {
425 11
            if (count($pathData) === 0) {
426
                throw new \RuntimeException("Extracted conditional op (including mainstack) without corresponding element in path data");
427
            }
428
429 11
            $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...
430 11
            $dataValue = array_shift($pathData);
431 11
            if ($opValue !== $dataValue) {
432 11
                throw new \RuntimeException("Current stack doesn't follow branch path");
433
            }
434
        } else {
435 9
            if (count($pathData) === 0) {
436
                throw new \RuntimeException("Extracted conditional op without corresponding element in path data");
437
            }
438
439 9
            $opValue = array_shift($pathData);
440
        }
441
442 11
        $conditional = new Conditional($operation->getOp());
443
444 11
        if ($opValue !== null) {
445 11
            if (!is_bool($opValue)) {
446
                throw new \RuntimeException("Sanity check, path value (likely from pathData) was not a bool");
447
            }
448
449 11
            $conditional->setValue($opValue);
450
        }
451
452 11
        return $conditional;
453
    }
454
455
    /**
456
     * @param int $idx
457
     * @return Checksig|Conditional
458
     */
459 18
    public function step(int $idx)
460
    {
461 18
        if (!array_key_exists($idx, $this->steps)) {
462
            throw new \RuntimeException("Out of range index for input sign step");
463
        }
464
465 18
        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...
466
    }
467
468
    /**
469
     * @param OutputData $signScript
470
     * @param Stack $stack
471
     * @param SignData $signData
472
     * @return array
473
     * @throws ScriptRuntimeException
474
     * @throws SignerException
475
     * @throws \Exception
476
     */
477 118
    public function extractScript(OutputData $signScript, Stack $stack, SignData $signData): array
478
    {
479 118
        $logicInterpreter = new BranchInterpreter();
480 118
        $tree = $logicInterpreter->getScriptTree($signScript->getScript());
481
482 118
        if ($tree->hasMultipleBranches()) {
483 11
            $logicalPath = $signData->getLogicalPath();
484
            // we need a function like findWitnessScript to 'check'
485
            // partial signatures against _our_ path
486
        } else {
487 107
            $logicalPath = [];
488
        }
489
490
        $scriptSections = $tree
491 118
            ->getBranchByPath($logicalPath)
492 118
            ->getScriptSections();
493
494 118
        $vfStack = new Stack();
495
496 118
        $pathCopy = $logicalPath;
497 118
        $steps = [];
498 118
        foreach ($scriptSections as $i => $scriptSection) {
499
            /** @var Operation[] $scriptSection */
500 118
            $fExec = !$this->interpreter->checkExec($vfStack, false);
501 118
            if (count($scriptSection) === 1 && $scriptSection[0]->isLogical()) {
502 11
                $op = $scriptSection[0];
503 11
                switch ($op->getOp()) {
504
                    case Opcodes::OP_IF:
505
                    case Opcodes::OP_NOTIF:
506 11
                        $value = false;
507 11
                        if ($fExec) {
508
                            // Pop from mainStack if $fExec
509 11
                            $step = $this->extractConditionalOp($op, $stack, $pathCopy);
510
511
                            // the Conditional has a value in this case:
512 11
                            $value = $step->getValue();
513
514
                            // Connect the last operation (if there is one)
515
                            // with the last step with isRequired==$value
516
                            // todo: check this part out..
517 11
                            for ($j = count($steps) - 1; $j >= 0; $j--) {
518 4
                                if ($steps[$j] instanceof Checksig && $value === $steps[$j]->isRequired()) {
519 4
                                    $step->providedBy($steps[$j]);
520 4
                                    break;
521
                                }
522
                            }
523
                        } else {
524 1
                            $step = new Conditional($op->getOp());
525
                        }
526
527 11
                        $steps[] = $step;
528
529 11
                        if ($op->getOp() === Opcodes::OP_NOTIF) {
530 5
                            $value = !$value;
531
                        }
532
533 11
                        $vfStack->push($value);
534 11
                        break;
535
                    case Opcodes::OP_ENDIF:
536 11
                        $vfStack->pop();
537 11
                        break;
538
                    case Opcodes::OP_ELSE:
539 9
                        $vfStack->push(!$vfStack->pop());
540 11
                        break;
541
                }
542
            } else {
543 118
                $templateTypes = $this->parseSequence($scriptSection);
544
545
                // Detect if effect on mainStack is `false`
546 118
                $resolvesFalse = count($pathCopy) > 0 && !$pathCopy[0];
547 118
                if ($resolvesFalse) {
548 2
                    if (count($templateTypes) > 1) {
549
                        throw new UnsupportedScript("Unsupported script, multiple steps to segment which is negated");
550
                    }
551
                }
552
553 118
                foreach ($templateTypes as $k => $checksig) {
554 118
                    if ($fExec) {
555 118
                        if ($checksig instanceof Checksig) {
556 108
                            $this->extractChecksig($signScript->getScript(), $checksig, $stack, $this->fqs->sigVersion(), $resolvesFalse);
557
558
                            // If this statement results is later consumed
559
                            // by a conditional which would be false, mark
560
                            // this operation as not required
561 103
                            if ($resolvesFalse) {
562 103
                                $checksig->setRequired(false);
563
                            }
564 15
                        } else if ($checksig instanceof TimeLock) {
565 15
                            $this->checkTimeLock($checksig);
566
                        }
567
568 103
                        $steps[] = $checksig;
569
                    }
570
                }
571
            }
572
        }
573
574 103
        return $steps;
575
    }
576
577
    /**
578
     * @param int $verify
579
     * @param int $input
580
     * @param int $threshold
581
     * @return int
582
     */
583 5
    private function compareRangeAgainstThreshold($verify, $input, $threshold)
584
    {
585 5
        if ($verify <= $threshold && $input > $threshold) {
586 2
            return -1;
587
        }
588
589 3
        if ($verify > $threshold && $input <= $threshold) {
590 2
            return 1;
591
        }
592
593 1
        return 0;
594
    }
595
596
    /**
597
     * @param TimeLock $timelock
598
     */
599 15
    public function checkTimeLock(TimeLock $timelock)
600
    {
601 15
        $info = $timelock->getInfo();
602 15
        if (($this->flags & Interpreter::VERIFY_CHECKLOCKTIMEVERIFY) != 0 && $info instanceof CheckLocktimeVerify) {
603 8
            $verifyLocktime = $info->getLocktime();
604 8
            if (!$this->signatureChecker->checkLockTime(Number::int($verifyLocktime))) {
605 5
                $input = $this->tx->getInput($this->nInput);
606 5
                if ($input->isFinal()) {
607 2
                    throw new \RuntimeException("Input sequence is set to max, therefore CHECKLOCKTIMEVERIFY would fail");
608
                }
609
610 3
                $locktime = $this->tx->getLockTime();
611 3
                $cmp = $this->compareRangeAgainstThreshold($verifyLocktime, $locktime, Locktime::BLOCK_MAX);
612 3
                if ($cmp === -1) {
613 1
                    throw new \RuntimeException("CLTV was for block height, but tx locktime was in timestamp range");
614 2
                } else if ($cmp === 1) {
615 1
                    throw new \RuntimeException("CLTV was for timestamp, but tx locktime was in block range");
616
                }
617
618 1
                $requiredTime = ($info->isLockedToBlock() ? "block {$info->getLocktime()}" : "{$info->getLocktime()}s (median time past)");
619 1
                throw new \RuntimeException("Output is not yet spendable, must wait until {$requiredTime}");
620
            }
621
        }
622
623 10
        if (($this->flags & Interpreter::VERIFY_CHECKSEQUENCEVERIFY) != 0 && $info instanceof CheckSequenceVerify) {
624
            // Future soft-fork extensibility, NOP if disabled flag
625 7
            if (($info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
626
                return;
627
            }
628
629 7
            if (!$this->signatureChecker->checkSequence(Number::int($info->getRelativeLockTime()))) {
630 5
                if ($this->tx->getVersion() < 2) {
631 2
                    throw new \RuntimeException("Transaction version must be 2 or greater for CSV");
632
                }
633
634 3
                $input = $this->tx->getInput($this->nInput);
635 3
                if ($input->isFinal()) {
636 1
                    throw new \RuntimeException("Sequence LOCKTIME_DISABLE_FLAG is set - not allowed on CSV output");
637
                }
638
639 2
                $cmp = $this->compareRangeAgainstThreshold($info->getRelativeLockTime(), $input->getSequence(), TransactionInput::SEQUENCE_LOCKTIME_TYPE_FLAG);
640 2
                if ($cmp === -1) {
641 1
                    throw new \RuntimeException("CSV was for block height, but txin sequence was in timestamp range");
642 1
                } else if ($cmp === 1) {
643 1
                    throw new \RuntimeException("CSV was for timestamp, but txin sequence was in block range");
644
                }
645
646
                $masked = $info->getRelativeLockTime() & TransactionInput::SEQUENCE_LOCKTIME_MASK;
647
                $requiredLock = "{$masked} " . ($info->isRelativeToBlock() ? " (blocks)" : "(seconds after txOut)");
648
                throw new \RuntimeException("Output unspendable with this sequence, must be locked for {$requiredLock}");
649
            }
650
        }
651 5
    }
652
653
    /**
654
     * @param ScriptInterface $script
655
     * @param BufferInterface $vchSig
656
     * @param BufferInterface $vchKey
657
     * @return bool
658
     */
659 35
    private function checkSignature(ScriptInterface $script, BufferInterface $vchSig, BufferInterface $vchKey)
660
    {
661
        try {
662 35
            return $this->signatureChecker->checkSig($script, $vchSig, $vchKey, $this->fqs->sigVersion(), $this->flags);
663
        } catch (ScriptRuntimeException $e) {
664
            return false;
665
        }
666
    }
667
668
    /**
669
     * This function is strictly for $canSign types.
670
     * It will extract signatures/publicKeys when given $outputData, and $stack.
671
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
672
     *
673
     * @param ScriptInterface $script
674
     * @param Checksig $checksig
675
     * @param Stack $stack
676
     * @param int $sigVersion
677
     * @param bool $expectFalse
678
     * @throws ScriptRuntimeException
679
     * @throws SignerException
680
     * @throws \Exception
681
     */
682 108
    public function extractChecksig(ScriptInterface $script, Checksig $checksig, Stack $stack, int $sigVersion, bool $expectFalse)
683
    {
684 108
        $size = count($stack);
685
686 108
        if ($checksig->getType() === ScriptType::P2PKH) {
687 44
            if ($size > 1) {
688 35
                $vchPubKey = $stack->pop();
689 35
                $vchSig = $stack->pop();
690
691 35
                $value = false;
692 35
                if (!$expectFalse) {
693 35
                    $value = $this->checkSignature($script, $vchSig, $vchPubKey);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 689 can be null; however, BitWasp\Bitcoin\Transact...igner::checkSignature() 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 688 can be null; however, BitWasp\Bitcoin\Transact...igner::checkSignature() 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...
694
695 35
                    if (!$value) {
696 1
                        throw new SignerException('Existing signatures are invalid!');
697
                    }
698
                }
699
700 34
                if (!$checksig->isVerify()) {
701 34
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
702
                }
703
704 34
                if (!$expectFalse) {
705
                    $checksig
706 34
                        ->setSignature(0, $this->txSigSerializer->parse($vchSig))
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 689 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...
707 43
                        ->setKey(0, $this->parseStepPublicKey($vchPubKey))
0 ignored issues
show
Bug introduced by
It seems like $vchPubKey defined by $stack->pop() on line 688 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...
708
                    ;
709
                }
710
            }
711 68
        } else if ($checksig->getType() === ScriptType::P2PK) {
712 37
            if ($size > 0) {
713 28
                $vchSig = $stack->pop();
714
715 28
                $value = false;
716 28
                if (!$expectFalse) {
717 28
                    $value = $this->signatureChecker->checkSig($script, $vchSig, $checksig->getSolution(), $this->fqs->sigVersion(), $this->flags);
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 713 can be null; however, BitWasp\Bitcoin\Script\I...CheckerBase::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; however, BitWasp\Bitcoin\Script\I...CheckerBase::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...
718 28
                    if (!$value) {
719 1
                        throw new SignerException('Existing signatures are invalid!');
720
                    }
721
                }
722
723 27
                if (!$checksig->isVerify()) {
724 25
                    $stack->push($value ? new Buffer("\x01") : new Buffer());
725
                }
726
727 27
                if (!$expectFalse) {
728 27
                    $checksig->setSignature(0, $this->txSigSerializer->parse($vchSig));
0 ignored issues
show
Bug introduced by
It seems like $vchSig defined by $stack->pop() on line 713 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
                }
730
            }
731
732 36
            $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; 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...
733 38
        } else if (ScriptType::MULTISIG === $checksig->getType()) {
734
            /** @var Multisig $info */
735 38
            $info = $checksig->getInfo();
736 38
            $keyBuffers = $info->getKeyBuffers();
737 38
            foreach ($keyBuffers as $idx => $keyBuf) {
738 38
                $checksig->setKey($idx, $this->parseStepPublicKey($keyBuf));
739
            }
740
741 36
            $value = false;
742 36
            if ($this->padUnsignedMultisigs) {
743
                // Multisig padding is only used for partially signed transactions,
744
                // never fully signed. It is recognized by a scriptSig with $keyCount+1
745
                // values (including the dummy), with one for each candidate signature,
746
                // such that $this->signatures state is captured.
747
                // The feature serves to skip validation/sorting an incomplete multisig.
748
749 14
                if ($size === 1 + $info->getKeyCount()) {
750 2
                    $sigBufCount = 0;
751 2
                    $null = new Buffer();
752 2
                    $keyToSigMap = new \SplObjectStorage();
753
754
                    // Reproduce $keyToSigMap and $sigBufCount
755 2
                    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...
756 2
                        if (!$stack[-1 - $i]->equals($null)) {
757 2
                            $keyToSigMap[$keyBuffers[$i]] = $stack[-1 - $i];
758 2
                            $sigBufCount++;
759
                        }
760
                    }
761
762
                    // We observed $this->requiredSigs sigs, therefore we can
763
                    // say the implementation is incompatible
764 2
                    if ($sigBufCount === $checksig->getRequiredSigs()) {
765 2
                        throw new SignerException("Padding is forbidden for a fully signed multisig script");
766
                    }
767
768
                    $toDelete = 1 + $info->getKeyCount();
769
                    $value = true;
770
                }
771
            }
772
773 36
            if (!isset($toDelete) || !isset($keyToSigMap)) {
774
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
775 36
                $sigBufs = [];
776 36
                $max = min($checksig->getRequiredSigs(), $size - 1);
777 36
                for ($i = 0; $i < $max; $i++) {
778 31
                    $vchSig = $stack[-1 - $i];
779 31
                    $sigBufs[] = $vchSig;
780
                }
781
782 36
                $sigBufs = array_reverse($sigBufs);
783 36
                $sigBufCount = count($sigBufs);
784
785 36
                if (!$expectFalse) {
786 35
                    if ($sigBufCount > 0) {
787 30
                        $keyToSigMap = $this->sortMultiSigs($script, $sigBufs, $keyBuffers, $sigVersion);
788
                        // Here we learn if any signatures were invalid, it won't be in the map.
789 30
                        if ($sigBufCount !== count($keyToSigMap)) {
790 1
                            throw new SignerException('Existing signatures are invalid!');
791
                        }
792 29
                        $toDelete = 1 + count($keyToSigMap);
793
                    } else {
794 34
                        $toDelete = 0;
795 34
                        $keyToSigMap = new \SplObjectStorage();
796
                    }
797 34
                    $value = true;
798
                } else {
799
                    // todo: should check that all signatures are zero
800 1
                    $keyToSigMap = new \SplObjectStorage();
801 1
                    $toDelete = min($stack->count(), 1 + $info->getRequiredSigCount());
802 1
                    $value = false;
803
                }
804
            }
805
806 35
            while ($toDelete--) {
807 30
                $stack->pop();
808
            }
809
810 35
            foreach ($keyBuffers as $idx => $key) {
811 35
                if (isset($keyToSigMap[$key])) {
812 35
                    $checksig->setSignature($idx, $this->txSigSerializer->parse($keyToSigMap[$key]));
813
                }
814
            }
815
816 35
            if (!$checksig->isVerify()) {
817 35
                $stack->push($value ? new Buffer("\x01") : new Buffer());
818
            }
819
        } else {
820
            throw new UnsupportedScript('Unsupported output type passed to extractFromValues');
821
        }
822 103
    }
823
824
    /**
825
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
826
     *
827
     * @param ScriptInterface $scriptCode
828
     * @param int $sigHashType
829
     * @param int $sigVersion
830
     * @throws SignerException
831
     * @return BufferInterface
832
     */
833 96
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, int $sigHashType, int $sigVersion): BufferInterface
834
    {
835 96
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
836 1
            throw new SignerException('Invalid sigHashType requested');
837
        }
838
839 95
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
840
    }
841
842
    /**
843
     * Calculates the signature hash for the input for the given $sigHashType.
844
     *
845
     * @param int $sigHashType
846
     * @return BufferInterface
847
     * @throws SignerException
848
     */
849 1
    public function getSigHash(int $sigHashType): BufferInterface
850
    {
851 1
        return $this->calculateSigHashUnsafe($this->fqs->signScript()->getScript(), $sigHashType, $this->fqs->sigVersion());
852
    }
853
854
    /**
855
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
856
     *
857
     * @param PrivateKeyInterface $key
858
     * @param ScriptInterface $scriptCode
859
     * @param int $sigHashType
860
     * @param int $sigVersion
861
     * @return TransactionSignatureInterface
862
     * @throws SignerException
863
     */
864 95
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, int $sigHashType, int $sigVersion)
865
    {
866 95
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
867 95
        return new TransactionSignature($this->ecAdapter, $key->sign($hash), $sigHashType);
868
    }
869
870
    /**
871
     * Returns whether all required signatures have been provided.
872
     *
873
     * @return bool
874
     */
875 62
    public function isFullySigned(): bool
876
    {
877 62
        foreach ($this->steps as $step) {
878 62
            if ($step instanceof Conditional) {
879
                if (!$step->hasValue()) {
880
                    return false;
881
                }
882 62
            } else if ($step instanceof Checksig) {
883 62
                if (!$step->isFullySigned()) {
884 62
                    return false;
885
                }
886
            }
887
        }
888
889 56
        return true;
890
    }
891
892
    /**
893
     * Returns the required number of signatures for this input.
894
     *
895
     * @return int
896
     */
897 62
    public function getRequiredSigs(): int
898
    {
899 62
        $count = 0;
900 62
        foreach ($this->steps as $step) {
901 62
            if ($step instanceof Checksig) {
902 62
                $count += $step->getRequiredSigs();
903
            }
904
        }
905 62
        return $count;
906
    }
907
908
    /**
909
     * Returns an array where the values are either null,
910
     * or a TransactionSignatureInterface.
911
     *
912
     * @return TransactionSignatureInterface[]
913
     */
914 62
    public function getSignatures(): array
915
    {
916 62
        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...
917
    }
918
919
    /**
920
     * Returns an array where the values are either null,
921
     * or a PublicKeyInterface.
922
     *
923
     * @return PublicKeyInterface[]
924
     */
925 51
    public function getPublicKeys(): array
926
    {
927 51
        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...
928
    }
929
930
    /**
931
     * Returns a FullyQualifiedScript since we
932
     * have solved all scripts to do with this input
933
     *
934
     * @return FullyQualifiedScript
935
     */
936 50
    public function getInputScripts(): FullyQualifiedScript
937
    {
938 50
        return $this->fqs;
939
    }
940
941
    /**
942
     * @param int $stepIdx
943
     * @param PrivateKeyInterface $privateKey
944
     * @param int $sigHashType
945
     * @return $this
946
     * @throws SignerException
947
     */
948 101
    public function signStep(int $stepIdx, PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL)
949
    {
950 101
        if (!array_key_exists($stepIdx, $this->steps)) {
951
            throw new \RuntimeException("Unknown step index");
952
        }
953
954 101
        $checksig = $this->steps[$stepIdx];
955 101
        if (!($checksig instanceof Checksig)) {
956
            throw new \RuntimeException("That index is a conditional, so cannot be signed");
957
        }
958
959 101
        if ($checksig->isFullySigned()) {
960
            return $this;
961
        }
962
963 101
        if (SigHash::V1 === $this->fqs->sigVersion() && !$privateKey->isCompressed()) {
964
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
965
        }
966
967 101
        $signScript = $this->fqs->signScript()->getScript();
968 101
        if ($checksig->getType() === ScriptType::P2PK) {
969 36
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($checksig->getSolution())) {
970 2
                throw new \RuntimeException('Signing with the wrong private key');
971
            }
972
973 34
            if (!$checksig->hasSignature(0)) {
974 34
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
975 34
                $checksig->setSignature(0, $signature);
976
            }
977 73
        } else if ($checksig->getType() === ScriptType::P2PKH) {
978 42
            $publicKey = $privateKey->getPublicKey();
979 42
            if (!$publicKey->getPubKeyHash()->equals($checksig->getSolution())) {
980 2
                throw new \RuntimeException('Signing with the wrong private key');
981
            }
982
983 40
            if (!$checksig->hasSignature(0)) {
984 40
                $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
985 40
                $checksig->setSignature(0, $signature);
986
            }
987
988 40
            if (!$checksig->hasKey(0)) {
989 40
                $checksig->setKey(0, $publicKey);
990
            }
991 33
        } else if ($checksig->getType() === ScriptType::MULTISIG) {
992 33
            $signed = false;
993 33
            foreach ($checksig->getKeys() as $keyIdx => $publicKey) {
994 33
                if (!$checksig->hasSignature($keyIdx)) {
995 33
                    if ($publicKey instanceof PublicKeyInterface && $privateKey->getPublicKey()->equals($publicKey)) {
996 31
                        $signature = $this->calculateSignature($privateKey, $signScript, $sigHashType, $this->fqs->sigVersion());
997 31
                        $checksig->setSignature($keyIdx, $signature);
998 33
                        $signed = true;
999
                    }
1000
                }
1001
            }
1002
1003 33
            if (!$signed) {
1004 33
                throw new \RuntimeException('Signing with the wrong private key');
1005
            }
1006
        } else {
1007
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
1008
        }
1009
1010 95
        return $this;
1011
    }
1012
1013
    /**
1014
     * Sign the input using $key and $sigHashTypes
1015
     *
1016
     * @param PrivateKeyInterface $privateKey
1017
     * @param int $sigHashType
1018
     * @return $this
1019
     * @throws SignerException
1020
     */
1021 78
    public function sign(PrivateKeyInterface $privateKey, int $sigHashType = SigHash::ALL)
1022
    {
1023 78
        return $this->signStep(0, $privateKey, $sigHashType);
1024
    }
1025
1026
    /**
1027
     * Verifies the input using $flags for script verification
1028
     *
1029
     * @param int $flags
1030
     * @return bool
1031
     */
1032 79
    public function verify(int $flags = null): bool
1033
    {
1034 79
        $consensus = ScriptFactory::consensus();
1035
1036 79
        if ($flags === null) {
1037 61
            $flags = $this->flags;
1038
        }
1039
1040 79
        $flags |= Interpreter::VERIFY_P2SH;
1041 79
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1042 31
            $flags |= Interpreter::VERIFY_WITNESS;
1043
        }
1044
1045 79
        $sig = $this->serializeSignatures();
1046
1047
        // Take serialized signatures, and use mutator to add this inputs sig data
1048 79
        $mutator = TransactionFactory::mutate($this->tx);
1049 79
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
1050
1051 79
        if (SigHash::V1 === $this->fqs->sigVersion()) {
1052 31
            $witness = [];
1053 31
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
1054 31
                if ($i === $this->nInput) {
1055 31
                    $witness[] = $sig->getScriptWitness();
1056
                } else {
1057 2
                    $witness[] = new ScriptWitness();
1058
                }
1059
            }
1060
1061 31
            $mutator->witness($witness);
1062
        }
1063
1064 79
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
1065
    }
1066
1067
    /**
1068
     * @return Stack
1069
     */
1070 95
    private function serializeSteps(): Stack
1071
    {
1072 95
        $results = [];
1073 95
        for ($i = 0, $n = count($this->steps); $i < $n; $i++) {
1074 95
            $step = $this->steps[$i];
1075
1076 95
            if ($step instanceof Conditional) {
1077 11
                $results[] = $step->serialize();
1078 95
            } else if ($step instanceof Checksig) {
1079 95
                if ($step->isRequired()) {
1080 95
                    if (count($step->getSignatures()) === 0) {
1081 50
                        break;
1082
                    }
1083
                }
1084
1085 95
                $results[] = $step->serialize($this->txSigSerializer, $this->pubKeySerializer);
1086
1087 95
                if (!$step->isFullySigned()) {
1088 6
                    break;
1089
                }
1090
            }
1091
        }
1092
1093 95
        $values = [];
1094 95
        foreach (array_reverse($results) as $v) {
1095 95
            foreach ($v as $value) {
1096 95
                $values[] = $value;
1097
            }
1098
        }
1099
1100 95
        return new Stack($values);
1101
    }
1102
1103
    /**
1104
     * Produces a SigValues instance containing the scriptSig & script witness
1105
     *
1106
     * @return SigValues
1107
     */
1108 95
    public function serializeSignatures(): SigValues
1109
    {
1110 95
        return $this->fqs->encodeStack($this->serializeSteps());
1111
    }
1112
1113
    /**
1114
     * @return Checksig[]|Conditional[]|mixed
1115
     */
1116 50
    public function getSteps()
1117
    {
1118 50
        return $this->steps;
1119
    }
1120
}
1121