Completed
Push — 0.0.34 ( 61212b...2e9020 )
by thomas
20:55
created

InputSigner   F

Complexity

Total Complexity 130

Size/Duplication

Total Lines 899
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 31

Test Coverage

Coverage 95.42%

Importance

Changes 0
Metric Value
dl 0
loc 899
ccs 292
cts 306
cp 0.9542
rs 1.0434
c 0
b 0
f 0
wmc 130
lcom 1
cbo 31

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
A tolerateInvalidPublicKey() 0 5 1
A redeemBitcoinCash() 0 5 1
A parseStepPublicKey() 0 12 3
B sortMultisigs() 0 32 5
A evalPushOnly() 0 6 1
A verifySolution() 0 4 1
A evaluateSolution() 0 17 4
C extractFromValues() 0 63 15
B findRedeemScript() 0 18 5
B findWitnessScript() 0 18 5
C solve() 0 57 11
A calculateSigHashUnsafe() 0 8 2
A getSigHash() 0 4 1
A calculateSignature() 0 6 1
A isFullySigned() 0 4 2
A getRequiredSigs() 0 4 1
A getSignatures() 0 4 1
A getPublicKeys() 0 4 1
A getSignScript() 0 4 1
A getScriptPubKey() 0 4 1
A getRedeemScript() 0 8 2
A getWitnessScript() 0 8 2
A isP2SH() 0 8 3
B isP2WSH() 0 14 6
C sign() 0 46 14
B verify() 0 34 6
C serializeSolution() 0 24 9
D serializeSignatures() 0 40 10
B extract() 0 31 6
B pushAll() 0 20 6

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
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
11
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
12
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
13
use BitWasp\Bitcoin\Script\Classifier\OutputData;
14
use BitWasp\Bitcoin\Script\Interpreter\BitcoinCashChecker;
15
use BitWasp\Bitcoin\Script\Interpreter\Checker;
16
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
17
use BitWasp\Bitcoin\Script\Interpreter\Stack;
18
use BitWasp\Bitcoin\Script\Opcodes;
19
use BitWasp\Bitcoin\Script\Script;
20
use BitWasp\Bitcoin\Script\ScriptFactory;
21
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
22
use BitWasp\Bitcoin\Script\ScriptInterface;
23
use BitWasp\Bitcoin\Script\ScriptType;
24
use BitWasp\Bitcoin\Script\ScriptWitness;
25
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
26
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
27
use BitWasp\Bitcoin\Signature\TransactionSignature;
28
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
29
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
30
use BitWasp\Bitcoin\Transaction\TransactionFactory;
31
use BitWasp\Bitcoin\Transaction\TransactionInterface;
32
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
33
use BitWasp\Buffertools\Buffer;
34
use BitWasp\Buffertools\BufferInterface;
35
36
class InputSigner implements InputSignerInterface
37
{
38
    /**
39
     * @var array
40
     */
41
    protected static $canSign = [
42
        ScriptType::P2PKH,
43
        ScriptType::P2PK,
44
        ScriptType::MULTISIG
45
    ];
46
47
    /**
48
     * @var array
49
     */
50
    protected static $validP2sh = [
51
        ScriptType::P2WKH,
52
        ScriptType::P2WSH,
53
        ScriptType::P2PKH,
54
        ScriptType::P2PK,
55
        ScriptType::MULTISIG
56
    ];
57
58
    /**
59
     * @var EcAdapterInterface
60
     */
61
    private $ecAdapter;
62
63
    /**
64
     * @var OutputData $scriptPubKey
65
     */
66
    private $scriptPubKey;
67
68
    /**
69
     * @var OutputData $redeemScript
70
     */
71
    private $redeemScript;
72
73
    /**
74
     * @var OutputData $witnessScript
75
     */
76
    private $witnessScript;
77
78
    /**
79
     * @var OutputData
80
     */
81
    private $signScript;
82
83
    /**
84
     * @var bool
85
     */
86
    private $tolerateInvalidPublicKey = false;
87
88
    /**
89
     * @var bool
90
     */
91
    private $redeemBitcoinCash = false;
92
93
    /**
94
     * @var SignData
95
     */
96
    private $signData;
97
98
    /**
99
     * @var int
100
     */
101
    private $sigVersion;
102
103
    /**
104
     * @var int
105
     */
106
    private $flags;
107
108
    /**
109
     * @var OutputData $witnessKeyHash
110
     */
111
    private $witnessKeyHash;
112
113
    /**
114
     * @var TransactionInterface
115
     */
116
    private $tx;
117
118
    /**
119
     * @var int
120
     */
121
    private $nInput;
122
123
    /**
124
     * @var TransactionOutputInterface
125
     */
126
    private $txOut;
127
128
    /**
129
     * @var PublicKeyInterface[]
130
     */
131
    private $publicKeys = [];
132
133
    /**
134
     * @var TransactionSignatureInterface[]
135
     */
136
    private $signatures = [];
137
138
    /**
139
     * @var int
140
     */
141
    private $requiredSigs = 0;
142
143
    /**
144
     * @var Interpreter
145
     */
146
    private $interpreter;
147
148
    /**
149
     * @var Checker
150
     */
151
    private $signatureChecker;
152
153
    /**
154
     * @var TransactionSignatureSerializer
155
     */
156
    private $txSigSerializer;
157
158
    /**
159
     * @var PublicKeySerializerInterface
160
     */
161
    private $pubKeySerializer;
162
163
    /**
164
     * InputSigner constructor.
165
     *
166
     * Note, the implementation of this class is considered internal
167
     * and only the methods exposed on InputSignerInterface should
168
     * be depended on to avoid BC breaks.
169
     *
170
     * The only recommended way to produce this class is using Signer::input()
171
     *
172
     * @param EcAdapterInterface $ecAdapter
173
     * @param TransactionInterface $tx
174
     * @param int $nInput
175
     * @param TransactionOutputInterface $txOut
176
     * @param SignData $signData
177
     * @param TransactionSignatureSerializer|null $sigSerializer
178
     * @param PublicKeySerializerInterface|null $pubKeySerializer
179
     */
180 96
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
181
    {
182 96
        $this->ecAdapter = $ecAdapter;
183 96
        $this->tx = $tx;
184 96
        $this->nInput = $nInput;
185 96
        $this->txOut = $txOut;
186 96
        $this->signData = $signData;
187 96
        $this->publicKeys = [];
188 96
        $this->signatures = [];
189
190 96
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
191 96
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
192 96
        $this->interpreter = new Interpreter($this->ecAdapter);
193 96
    }
194
195
    /**
196
     * @return InputSigner
197
     */
198 96
    public function extract()
199
    {
200 96
        $defaultFlags = Interpreter::VERIFY_DERSIG | Interpreter::VERIFY_P2SH | Interpreter::VERIFY_CHECKLOCKTIMEVERIFY | Interpreter::VERIFY_CHECKSEQUENCEVERIFY | Interpreter::VERIFY_WITNESS;
201 96
        $checker = new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
202
203 96
        if ($this->redeemBitcoinCash) {
204
            // unset VERIFY_WITNESS default
205 2
            $defaultFlags = $defaultFlags & (~Interpreter::VERIFY_WITNESS);
206
207 2
            if ($this->signData->hasSignaturePolicy()) {
208
                if ($this->signData->getSignaturePolicy() & Interpreter::VERIFY_WITNESS) {
209
                    throw new \RuntimeException("VERIFY_WITNESS is not possible for bitcoin cash");
210
                }
211
            }
212
213 2
            $checker = new BitcoinCashChecker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
214
        }
215
216 96
        $this->flags = $this->signData->hasSignaturePolicy() ? $this->signData->getSignaturePolicy() : $defaultFlags;
217 96
        $this->signatureChecker = $checker;
218
219 96
        $witnesses = $this->tx->getWitnesses();
220 96
        $witness = array_key_exists($this->nInput, $witnesses) ? $witnesses[$this->nInput]->all() : [];
221
222 96
        return $this->solve(
223 96
            $this->signData,
224 96
            $this->txOut->getScript(),
225 96
            $this->tx->getInput($this->nInput)->getScript(),
226 94
            $witness
227
        );
228
    }
229
230
    /**
231
     * @param bool $setting
232
     * @return $this
233
     */
234 66
    public function tolerateInvalidPublicKey($setting)
235
    {
236 66
        $this->tolerateInvalidPublicKey = (bool) $setting;
237 66
        return $this;
238
    }
239
240
    /**
241
     * @param bool $setting
242
     * @return $this
243
     */
244 66
    public function redeemBitcoinCash($setting)
245
    {
246 66
        $this->redeemBitcoinCash = (bool) $setting;
247 66
        return $this;
248
    }
249
250
    /**
251
     * @param BufferInterface $vchPubKey
252
     * @return PublicKeyInterface|null
253
     * @throws \Exception
254
     */
255 64
    protected function parseStepPublicKey(BufferInterface $vchPubKey)
256
    {
257
        try {
258 64
            return $this->pubKeySerializer->parse($vchPubKey);
259 6
        } catch (\Exception $e) {
260 6
            if ($this->tolerateInvalidPublicKey) {
261 2
                return null;
262
            }
263
264 4
            throw $e;
265
        }
266
    }
267
268
    /**
269
     * A snipped from OP_CHECKMULTISIG - verifies signatures according to the
270
     * order of the given public keys (taken from the script).
271
     *
272
     * @param ScriptInterface $script
273
     * @param BufferInterface[] $signatures
274
     * @param BufferInterface[] $publicKeys
275
     * @param int $sigVersion
276
     * @return \SplObjectStorage
277
     */
278 10
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
279
    {
280 10
        $sigCount = count($signatures);
281 10
        $keyCount = count($publicKeys);
282 10
        $ikey = $isig = 0;
283 10
        $fSuccess = true;
284 10
        $result = new \SplObjectStorage;
285
286 10
        while ($fSuccess && $sigCount > 0) {
287
            // Fetch the signature and public key
288 10
            $sig = $signatures[$isig];
289 10
            $pubkey = $publicKeys[$ikey];
290
291 10
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
292 10
                $result[$pubkey] = $sig;
293 10
                $isig++;
294 10
                $sigCount--;
295
            }
296
297 10
            $ikey++;
298 10
            $keyCount--;
299
300
            // If there are more signatures left than keys left,
301
            // then too many signatures have failed. Exit early,
302
            // without checking any further signatures.
303 10
            if ($sigCount > $keyCount) {
304
                $fSuccess = false;
305
            }
306
        }
307
308 10
        return $result;
309
    }
310
311
    /**
312
     * @param ScriptInterface $script
313
     * @return \BitWasp\Buffertools\BufferInterface[]
314
     */
315 76
    private function evalPushOnly(ScriptInterface $script)
316
    {
317 76
        $stack = new Stack();
318 76
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
319 76
        return $stack->all();
320
    }
321
322
    /**
323
     * Create a script consisting only of push-data operations.
324
     * Suitable for a scriptSig.
325
     *
326
     * @param BufferInterface[] $buffers
327
     * @return ScriptInterface
328
     */
329
    private function pushAll(array $buffers)
330
    {
331 52
        return ScriptFactory::sequence(array_map(function ($buffer) {
332 44
            if (!($buffer instanceof BufferInterface)) {
333
                throw new \RuntimeException('Script contained a non-push opcode');
334
            }
335
336 44
            $size = $buffer->getSize();
337 44
            if ($size === 0) {
338 4
                return Opcodes::OP_0;
339
            }
340
341 44
            $first = ord($buffer->getBinary());
342 44
            if ($size === 1 && $first >= 1 && $first <= 16) {
343
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
344
            } else {
345 44
                return $buffer;
346
            }
347 52
        }, $buffers));
348
    }
349
350
    /**
351
     * Verify a scriptSig / scriptWitness against a scriptPubKey.
352
     * Useful for checking the outcome of certain things, like hash locks (p2sh)
353
     *
354
     * @param int $flags
355
     * @param ScriptInterface $scriptSig
356
     * @param ScriptInterface $scriptPubKey
357
     * @param ScriptWitnessInterface|null $scriptWitness
358
     * @return bool
359
     */
360 26
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
361
    {
362 26
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
363
    }
364
365
    /**
366
     * Evaluates a scriptPubKey against the provided chunks.
367
     *
368
     * @param ScriptInterface $scriptPubKey
369
     * @param array $chunks
370
     * @param int $sigVersion
371
     * @return bool
372
     */
373 56
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
374
    {
375 56
        $stack = new Stack($chunks);
376 56
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
377 2
            return false;
378
        }
379
380 54
        if ($stack->isEmpty()) {
381
            return false;
382
        }
383
384 54
        if (false === $this->interpreter->castToBool($stack[-1])) {
385 4
            return false;
386
        }
387
388 50
        return true;
389
    }
390
391
    /**
392
     * This function is strictly for $canSign types.
393
     * It will extract signatures/publicKeys when given $outputData, and $stack.
394
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
395
     *
396
     * @param OutputData $outputData
397
     * @param array $stack
398
     * @param int $sigVersion
399
     * @return string
400
     */
401 72
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
402
    {
403 72
        $type = $outputData->getType();
404 72
        $size = count($stack);
405
406 72
        if (ScriptType::P2PKH === $type) {
407 38
            $this->requiredSigs = 1;
408 38
            if ($size === 2) {
409 34
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
410 2
                    throw new \RuntimeException('Existing signatures are invalid!');
411
                }
412 32
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
413 36
                $this->publicKeys = [$this->parseStepPublicKey($stack[1])];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->parseStepPublicKey($stack[1])) of type array<integer,object<Bit...icKeyInterface>|null"}> is incompatible with the declared type array<integer,object<Bit...ey\PublicKeyInterface>> of property $publicKeys.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
414
            }
415 36
        } else if (ScriptType::P2PK === $type) {
416 16
            $this->requiredSigs = 1;
417 16
            if ($size === 1) {
418 12
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
419 2
                    throw new \RuntimeException('Existing signatures are invalid!');
420
                }
421 10
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
422
            }
423 14
            $this->publicKeys = [$this->parseStepPublicKey($outputData->getSolution())];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->parseStepPu...utData->getSolution())) of type array<integer,object<Bit...icKeyInterface>|null"}> is incompatible with the declared type array<integer,object<Bit...ey\PublicKeyInterface>> of property $publicKeys.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
424 20
        } else if (ScriptType::MULTISIG === $type) {
425 20
            $info = new Multisig($outputData->getScript(), $this->pubKeySerializer);
426 20
            $this->requiredSigs = $info->getRequiredSigCount();
427
428 20
            $keyBuffers = $info->getKeyBuffers();
429 20
            $this->publicKeys = [];
430 20
            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...
431 20
                $this->publicKeys[$i] = $this->parseStepPublicKey($keyBuffers[$i]);
432
            }
433
434 16
            if ($size > 1) {
435
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
436 12
                $check = $this->evaluateSolution($outputData->getScript(), $stack, $sigVersion);
437 12
                $sigBufs = array_slice($stack, 1, $size - 1);
438 12
                $sigBufCount = count($sigBufs);
439
440
                // If we seem to have all signatures but fail evaluation, abort
441 12
                if ($sigBufCount === $this->requiredSigs && !$check) {
442 2
                    throw new \RuntimeException('Existing signatures are invalid!');
443
                }
444
445 10
                $keyToSigMap = $this->sortMultiSigs($outputData->getScript(), $sigBufs, $keyBuffers, $sigVersion);
446
447
                // Here we learn if any signatures were invalid, it won't be in the map.
448 10
                if ($sigBufCount !== count($keyToSigMap)) {
449
                    throw new \RuntimeException('Existing signatures are invalid!');
450
                }
451
452 10
                foreach ($keyBuffers as $idx => $key) {
453 10
                    if (isset($keyToSigMap[$key])) {
454 14
                        $this->signatures[$idx] = $this->txSigSerializer->parse($keyToSigMap[$key]);
455
                    }
456
                }
457
            }
458
        } else {
459
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
460
        }
461
462 62
        return $type;
463
    }
464
465
    /**
466
     * Checks $chunks (a decompiled scriptSig) for it's last element,
467
     * or defers to SignData. If both are provided, it checks the
468
     * value from $chunks against SignData.
469
     *
470
     * @param BufferInterface[] $chunks
471
     * @param SignData $signData
472
     * @return ScriptInterface
473
     */
474 30
    private function findRedeemScript(array $chunks, SignData $signData)
475
    {
476 30
        if (count($chunks) > 0) {
477 22
            $redeemScript = new Script($chunks[count($chunks) - 1]);
478 22
            if ($signData->hasRedeemScript()) {
479 22
                if (!$redeemScript->equals($signData->getRedeemScript())) {
480 22
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
481
                }
482
            }
483
        } else {
484 26
            if (!$signData->hasRedeemScript()) {
485 2
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
486
            }
487 24
            $redeemScript = $signData->getRedeemScript();
488
        }
489
490 26
        return $redeemScript;
491
    }
492
493
    /**
494
     * Checks $witness (a witness structure) for it's last element,
495
     * or defers to SignData. If both are provided, it checks the
496
     * value from $chunks against SignData.
497
     *
498
     * @param BufferInterface[] $witness
499
     * @param SignData $signData
500
     * @return ScriptInterface
501
     */
502 26
    private function findWitnessScript(array $witness, SignData $signData)
503
    {
504 26
        if (count($witness) > 0) {
505 18
            $witnessScript = new Script($witness[count($witness) - 1]);
506 18
            if ($signData->hasWitnessScript()) {
507 18
                if (!$witnessScript->equals($signData->getWitnessScript())) {
508 18
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
509
                }
510
            }
511
        } else {
512 22
            if (!$signData->hasWitnessScript()) {
513 4
                throw new \RuntimeException('Witness script not provided in sign data or witness');
514
            }
515 18
            $witnessScript = $signData->getWitnessScript();
516
        }
517
518 18
        return $witnessScript;
519
    }
520
521
    /**
522
     * Needs to be called before using the instance. By `extract`.
523
     *
524
     * It ensures that violating the following prevents instance creation
525
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
526
     *  - the P2SH script covers signable types and P2WSH/P2WKH
527
     *  - the witnessScript covers signable types only
528
     *
529
     * @param SignData $signData
530
     * @param ScriptInterface $scriptPubKey
531
     * @param ScriptInterface $scriptSig
532
     * @param BufferInterface[] $witness
533
     * @return $this
534
     */
535 94
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
536
    {
537 94
        $classifier = new OutputClassifier();
538 94
        $sigVersion = SigHash::V0;
539 94
        $sigChunks = [];
540 94
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
541 94
        if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
542 2
            throw new \RuntimeException('scriptPubKey not supported');
543
        }
544
545 92
        if ($solution->canSign()) {
546 46
            $sigChunks = $this->evalPushOnly($scriptSig);
547
        }
548
549 92
        if ($solution->getType() === ScriptType::P2SH) {
550 30
            $chunks = $this->evalPushOnly($scriptSig);
551 30
            $redeemScript = $this->findRedeemScript($chunks, $signData);
552 26
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
553 2
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
554
            }
555
556 24
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
557 24
            if (!in_array($solution->getType(), self::$validP2sh)) {
558 2
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
559
            }
560
561 22
            $sigChunks = array_slice($chunks, 0, -1);
562
        }
563
564 84
        if ($solution->getType() === ScriptType::P2WKH) {
565 8
            $sigVersion = SigHash::V1;
566 8
            $solution = $this->witnessKeyHash = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
567 8
            $sigChunks = $witness;
568 78
        } else if ($solution->getType() === ScriptType::P2WSH) {
569 26
            $sigVersion = SigHash::V1;
570 26
            $witnessScript = $this->findWitnessScript($witness, $signData);
571
572
            // Essentially all the reference implementation does
573 18
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
574 2
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
575
            }
576
577 16
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
578 16
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
579 2
                throw new \RuntimeException('Unsupported witness-script-hash script');
580
            }
581
582 14
            $sigChunks = array_slice($witness, 0, -1);
583
        }
584
585 72
        $this->sigVersion = $sigVersion;
586 72
        $this->signScript = $solution;
587
588 72
        $this->extractFromValues($solution, $sigChunks, $this->sigVersion);
589
590 62
        return $this;
591
    }
592
593
    /**
594
     * Pure function to produce a signature hash for a given $scriptCode, $sigHashType, $sigVersion.
595
     *
596
     * @param ScriptInterface $scriptCode
597
     * @param int $sigHashType
598
     * @param int $sigVersion
599
     * @return BufferInterface
600
     */
601 54
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
602
    {
603 54
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
604 2
            throw new \RuntimeException('Invalid sigHashType requested');
605
        }
606
607 52
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
608
    }
609
610
    /**
611
     * Calculates the signature hash for the input for the given $sigHashType.
612
     *
613
     * @param int $sigHashType
614
     * @return BufferInterface
615
     */
616 2
    public function getSigHash($sigHashType)
617
    {
618 2
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
619
    }
620
621
    /**
622
     * Pure function to produce a signature for a given $key, $scriptCode, $sigHashType, $sigVersion.
623
     *
624
     * @param PrivateKeyInterface $key
625
     * @param ScriptInterface $scriptCode
626
     * @param int $sigHashType
627
     * @param int $sigVersion
628
     * @return TransactionSignatureInterface
629
     */
630 52
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
631
    {
632 52
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
633 52
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
634 52
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
635
    }
636
637
    /**
638
     * Returns whether all required signatures have been provided.
639
     *
640
     * @return bool
641
     */
642 58
    public function isFullySigned()
643
    {
644 58
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
645
    }
646
647
    /**
648
     * Returns the required number of signatures for this input.
649
     *
650
     * @return int
651
     */
652 50
    public function getRequiredSigs()
653
    {
654 50
        return $this->requiredSigs;
655
    }
656
657
    /**
658
     * Returns an array where the values are either null,
659
     * or a TransactionSignatureInterface.
660
     *
661
     * @return TransactionSignatureInterface[]
662
     */
663 50
    public function getSignatures()
664
    {
665 50
        return $this->signatures;
666
    }
667
668
    /**
669
     * Returns an array where the values are either null,
670
     * or a PublicKeyInterface.
671
     *
672
     * @return PublicKeyInterface[]
673
     */
674 52
    public function getPublicKeys()
675
    {
676 52
        return $this->publicKeys;
677
    }
678
679
    /**
680
     * OutputData for the script to be signed (will be
681
     * equal to getScriptPubKey, or getRedeemScript, or
682
     * getWitnessScript.
683
     *
684
     * @return OutputData
685
     */
686 50
    public function getSignScript()
687
    {
688 50
        return $this->signScript;
689
    }
690
691
    /**
692
     * OutputData for the txOut script.
693
     *
694
     * @return OutputData
695
     */
696 24
    public function getScriptPubKey()
697
    {
698 24
        return $this->scriptPubKey;
699
    }
700
701
    /**
702
     * Returns OutputData for the P2SH redeemScript.
703
     *
704
     * @return OutputData
705
     */
706 18
    public function getRedeemScript()
707
    {
708 18
        if (null === $this->redeemScript) {
709
            throw new \RuntimeException("Input has no redeemScript, cannot call getRedeemScript");
710
        }
711
712 18
        return $this->redeemScript;
713
    }
714
715
    /**
716
     * Returns OutputData for the P2WSH witnessScript.
717
     *
718
     * @return OutputData
719
     */
720 14
    public function getWitnessScript()
721
    {
722 14
        if (null === $this->witnessScript) {
723
            throw new \RuntimeException("Input has no witnessScript, cannot call getWitnessScript");
724
        }
725
726 14
        return $this->witnessScript;
727
    }
728
729
    /**
730
     * Returns whether the scriptPubKey is P2SH.
731
     *
732
     * @return bool
733
     */
734 50
    public function isP2SH()
735
    {
736 50
        if ($this->scriptPubKey->getType() === ScriptType::P2SH && ($this->redeemScript instanceof OutputData)) {
737 18
            return true;
738
        }
739
740 32
        return false;
741
    }
742
743
    /**
744
     * Returns whether the scriptPubKey or redeemScript is P2WSH.
745
     *
746
     * @return bool
747
     */
748 50
    public function isP2WSH()
749
    {
750 50
        if ($this->redeemScript instanceof OutputData) {
751 18
            if ($this->redeemScript->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
752 8
                return true;
753
            }
754
        }
755
756 42
        if ($this->scriptPubKey->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
757 6
            return true;
758
        }
759
760 36
        return false;
761
    }
762
763
    /**
764
     * Sign the input using $key and $sigHashTypes
765
     *
766
     * @param PrivateKeyInterface $privateKey
767
     * @param int $sigHashType
768
     * @return $this
769
     */
770 58
    public function sign(PrivateKeyInterface $privateKey, $sigHashType = SigHash::ALL)
771
    {
772 58
        if ($this->isFullySigned()) {
773
            return $this;
774
        }
775
776 58
        if (SigHash::V1 === $this->sigVersion && !$privateKey->isCompressed()) {
777
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
778
        }
779
780 58
        if ($this->signScript->getType() === ScriptType::P2PK) {
781 14
            if (!$this->pubKeySerializer->serialize($privateKey->getPublicKey())->equals($this->signScript->getSolution())) {
782 2
                throw new \RuntimeException('Signing with the wrong private key');
783
            }
784 12
            $this->signatures[0] = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
785 46
        } else if ($this->signScript->getType() === ScriptType::P2PKH) {
786 34
            $publicKey = $privateKey->getPublicKey();
787 34
            if (!$publicKey->getPubKeyHash()->equals($this->signScript->getSolution())) {
788 2
                throw new \RuntimeException('Signing with the wrong private key');
789
            }
790
791 32
            if (!array_key_exists(0, $this->signatures)) {
792 32
                $this->signatures[0] = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
793
            }
794
795 32
            $this->publicKeys[0] = $publicKey;
796 12
        } else if ($this->signScript->getType() === ScriptType::MULTISIG) {
797 12
            $signed = false;
798 12
            foreach ($this->publicKeys as $keyIdx => $publicKey) {
799 12
                if ($publicKey instanceof PublicKeyInterface) {
800 12
                    if ($privateKey->getPublicKey()->equals($publicKey)) {
801 10
                        $this->signatures[$keyIdx] = $this->calculateSignature($privateKey, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
802 12
                        $signed = true;
803
                    }
804
                }
805
            }
806
807 12
            if (!$signed) {
808 12
                throw new \RuntimeException('Signing with the wrong private key');
809
            }
810
        } else {
811
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
812
        }
813
814 52
        return $this;
815
    }
816
817
    /**
818
     * Verifies the input using $flags for script verification
819
     *
820
     * @param int $flags
821
     * @return bool
822
     */
823 50
    public function verify($flags = null)
824
    {
825 50
        $consensus = ScriptFactory::consensus();
826
827 50
        if ($flags === null) {
828 50
            $flags = $this->flags;
829
        }
830
831 50
        $flags |= Interpreter::VERIFY_P2SH;
832 50
        if (SigHash::V1 === $this->sigVersion) {
833 22
            $flags |= Interpreter::VERIFY_WITNESS;
834
        }
835
836 50
        $sig = $this->serializeSignatures();
837
838
        // Take serialized signatures, and use mutator to add this inputs sig data
839 50
        $mutator = TransactionFactory::mutate($this->tx);
840 50
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
841
842 50
        if (SigHash::V1 === $this->sigVersion) {
843 22
            $witness = [];
844 22
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
845 22
                if ($i === $this->nInput) {
846 22
                    $witness[] = $sig->getScriptWitness();
847
                } else {
848 2
                    $witness[] = new ScriptWitness([]);
849
                }
850
            }
851
852 22
            $mutator->witness($witness);
853
        }
854
855 50
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
856
    }
857
858
    /**
859
     * Produces the script stack that solves the $outputType
860
     *
861
     * @param string $outputType
862
     * @return BufferInterface[]
863
     */
864 52
    private function serializeSolution($outputType)
865
    {
866 52
        $result = [];
867 52
        if (ScriptType::P2PK === $outputType) {
868 12
            if (count($this->signatures) === 1) {
869 12
                $result = [$this->txSigSerializer->serialize($this->signatures[0])];
0 ignored issues
show
Compatibility introduced by
$this->signatures[0] of type object<BitWasp\Bitcoin\S...tionSignatureInterface> is not a sub-type of object<BitWasp\Bitcoin\S...e\TransactionSignature>. It seems like you assume a concrete implementation of the interface BitWasp\Bitcoin\Signatur...ctionSignatureInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
870
            }
871 42
        } else if (ScriptType::P2PKH === $outputType) {
872 32
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
873 32
                $result = [$this->txSigSerializer->serialize($this->signatures[0]), $this->pubKeySerializer->serialize($this->publicKeys[0])];
0 ignored issues
show
Compatibility introduced by
$this->signatures[0] of type object<BitWasp\Bitcoin\S...tionSignatureInterface> is not a sub-type of object<BitWasp\Bitcoin\S...e\TransactionSignature>. It seems like you assume a concrete implementation of the interface BitWasp\Bitcoin\Signatur...ctionSignatureInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
874
            }
875 10
        } else if (ScriptType::MULTISIG === $outputType) {
876 10
            $result[] = new Buffer();
877 10
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
878 10
                if (isset($this->signatures[$i])) {
879 10
                    $result[] = $this->txSigSerializer->serialize($this->signatures[$i]);
0 ignored issues
show
Compatibility introduced by
$this->signatures[$i] of type object<BitWasp\Bitcoin\S...tionSignatureInterface> is not a sub-type of object<BitWasp\Bitcoin\S...e\TransactionSignature>. It seems like you assume a concrete implementation of the interface BitWasp\Bitcoin\Signatur...ctionSignatureInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
880
                }
881
            }
882
        } else {
883
            throw new \RuntimeException('Parameter 0 for serializeSolution was a non-standard input type');
884
        }
885
886 52
        return $result;
887
    }
888
889
    /**
890
     * Produces a SigValues instance containing the scriptSig & script witness
891
     *
892
     * @return SigValues
893
     */
894 52
    public function serializeSignatures()
895
    {
896 52
        static $emptyScript = null;
897 52
        static $emptyWitness = null;
898 52
        if (is_null($emptyScript) || is_null($emptyWitness)) {
899 2
            $emptyScript = new Script();
900 2
            $emptyWitness = new ScriptWitness([]);
901
        }
902
903 52
        $scriptSigChunks = [];
904 52
        $witness = [];
905 52
        if ($this->scriptPubKey->canSign()) {
906 26
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
907
        }
908
909 52
        $solution = $this->scriptPubKey;
910 52
        $p2sh = false;
911 52
        if ($solution->getType() === ScriptType::P2SH) {
912 18
            $p2sh = true;
913 18
            if ($this->redeemScript->canSign()) {
914 6
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
915
            }
916 18
            $solution = $this->redeemScript;
917
        }
918
919 52
        if ($solution->getType() === ScriptType::P2WKH) {
920 8
            $witness = $this->serializeSolution(ScriptType::P2PKH);
921 46
        } else if ($solution->getType() === ScriptType::P2WSH) {
922 14
            if ($this->witnessScript->canSign()) {
923 14
                $witness = $this->serializeSolution($this->witnessScript->getType());
924 14
                $witness[] = $this->witnessScript->getScript()->getBuffer();
925
            }
926
        }
927
928 52
        if ($p2sh) {
929 18
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
930
        }
931
932 52
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
933
    }
934
}
935