Completed
Pull Request — master (#491)
by thomas
70:18
created

InputSigner::sortMultisigs()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.0042

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 5
nop 4
dl 0
loc 32
ccs 17
cts 18
cp 0.9444
crap 5.0042
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
11
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
12
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
13
use BitWasp\Bitcoin\Script\Classifier\OutputData;
14
use BitWasp\Bitcoin\Script\Interpreter\Checker;
15
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
16
use BitWasp\Bitcoin\Script\Interpreter\Stack;
17
use BitWasp\Bitcoin\Script\Opcodes;
18
use BitWasp\Bitcoin\Script\Script;
19
use BitWasp\Bitcoin\Script\ScriptFactory;
20
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
21
use BitWasp\Bitcoin\Script\ScriptInterface;
22
use BitWasp\Bitcoin\Script\ScriptType;
23
use BitWasp\Bitcoin\Script\ScriptWitness;
24
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
25
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
26
use BitWasp\Bitcoin\Signature\TransactionSignature;
27
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
28
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
29
use BitWasp\Bitcoin\Transaction\TransactionFactory;
30
use BitWasp\Bitcoin\Transaction\TransactionInterface;
31
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
32
use BitWasp\Buffertools\Buffer;
33
use BitWasp\Buffertools\BufferInterface;
34
35
class InputSigner
36
{
37
    /**
38
     * @var array
39
     */
40
    protected static $canSign = [
41
        ScriptType::P2PKH,
42
        ScriptType::P2PK,
43
        ScriptType::MULTISIG
44
    ];
45
46
    /**
47
     * @var array
48
     */
49
    protected static $validP2sh = [
50
        ScriptType::P2WKH,
51
        ScriptType::P2WSH,
52
        ScriptType::P2PKH,
53
        ScriptType::P2PK,
54
        ScriptType::MULTISIG
55
    ];
56
57
    /**
58
     * @var EcAdapterInterface
59
     */
60
    private $ecAdapter;
61
62
    /**
63
     * @var OutputData $scriptPubKey
64
     */
65
    private $scriptPubKey;
66
67
    /**
68
     * @var OutputData $redeemScript
69
     */
70
    private $redeemScript;
71
72
    /**
73
     * @var OutputData $witnessScript
74
     */
75
    private $witnessScript;
76
77
    /**
78
     * @var OutputData
79
     */
80
    private $signScript;
81
82
    /**
83
     * @var int
84
     */
85
    private $sigVersion;
86
87
    /**
88
     * @var OutputData $witnessKeyHash
89
     */
90
    private $witnessKeyHash;
91
92
    /**
93
     * @var TransactionInterface
94
     */
95
    private $tx;
96
97
    /**
98
     * @var int
99
     */
100
    private $nInput;
101
102
    /**
103
     * @var TransactionOutputInterface
104
     */
105
    private $txOut;
106
107
    /**
108
     * @var PublicKeyInterface[]
109
     */
110
    private $publicKeys = [];
111
112
    /**
113
     * @var TransactionSignatureInterface[]
114
     */
115
    private $signatures = [];
116
117
    /**
118
     * @var int
119
     */
120
    private $requiredSigs = 0;
121
122
    /**
123
     * @var Interpreter
124
     */
125
    private $interpreter;
126
127
    /**
128
     * @var Checker
129
     */
130
    private $signatureChecker;
131
132
    /**
133
     * @var TransactionSignatureSerializer
134
     */
135
    private $txSigSerializer;
136
137
    /**
138
     * @var PublicKeySerializerInterface
139
     */
140
    private $pubKeySerializer;
141
142
    /**
143
     * InputSigner constructor.
144
     * @param EcAdapterInterface $ecAdapter
145
     * @param TransactionInterface $tx
146
     * @param $nInput
147
     * @param TransactionOutputInterface $txOut
148
     * @param SignData $signData
149
     * @param TransactionSignatureSerializer|null $sigSerializer
150
     * @param PublicKeySerializerInterface|null $pubKeySerializer
151
     */
152 88
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
153
    {
154 88
        $inputs = $tx->getInputs();
155 88
        if (!isset($inputs[$nInput])) {
156 2
            throw new \RuntimeException('No input at this index');
157
        }
158
159 86
        $this->ecAdapter = $ecAdapter;
160 86
        $this->tx = $tx;
161 86
        $this->nInput = $nInput;
162 86
        $this->txOut = $txOut;
163 86
        $this->flags = $signData->hasSignaturePolicy() ? $signData->getSignaturePolicy() : Interpreter::VERIFY_NONE;
0 ignored issues
show
Bug introduced by
The property flags does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
164 86
        $this->publicKeys = [];
165 86
        $this->signatures = [];
166
167 86
        $this->txSigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
168 86
        $this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
169 86
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
170 86
        $this->interpreter = new Interpreter($this->ecAdapter);
171
172 86
        $this->solve($signData, $txOut->getScript(), $inputs[$nInput]->getScript(), isset($tx->getWitnesses()[$nInput]) ? $tx->getWitnesses()[$nInput]->all() : []);
173 58
    }
174
175
    /**
176
     * A snipped from OP_CHECKMULTISIG - verifies signatures according to the
177
     * order of the given public keys (taken from the script).
178
     *
179
     * @param ScriptInterface $script
180
     * @param BufferInterface[] $signatures
181
     * @param BufferInterface[] $publicKeys
182
     * @param int $sigVersion
183
     * @return \SplObjectStorage
184
     */
185 10
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
186
    {
187 10
        $sigCount = count($signatures);
188 10
        $keyCount = count($publicKeys);
189 10
        $ikey = $isig = 0;
190 10
        $fSuccess = true;
191 10
        $result = new \SplObjectStorage;
192
193 10
        while ($fSuccess && $sigCount > 0) {
194
            // Fetch the signature and public key
195 10
            $sig = $signatures[$isig];
196 10
            $pubkey = $publicKeys[$ikey];
197
198 10
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
199 10
                $result[$pubkey] = $sig;
200 10
                $isig++;
201 10
                $sigCount--;
202
            }
203
204 10
            $ikey++;
205 10
            $keyCount--;
206
207
            // If there are more signatures left than keys left,
208
            // then too many signatures have failed. Exit early,
209
            // without checking any further signatures.
210 10
            if ($sigCount > $keyCount) {
211
                $fSuccess = false;
212
            }
213
        }
214
215 10
        return $result;
216
    }
217
218
    /**
219
     * @param ScriptInterface $script
220
     * @return \BitWasp\Buffertools\BufferInterface[]
221
     */
222 68
    private function evalPushOnly(ScriptInterface $script)
223
    {
224 68
        $stack = new Stack();
225 68
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
226 68
        return $stack->all();
227
    }
228
229
    /**
230
     * Create a script consisting only of push-data operations.
231
     * Suitable for a scriptSig.
232
     *
233
     * @param BufferInterface[] $buffers
234
     * @return ScriptInterface
235
     */
236
    private function pushAll(array $buffers)
237
    {
238 50
        return ScriptFactory::sequence(array_map(function ($buffer) {
239 42
            if (!($buffer instanceof BufferInterface)) {
240
                throw new \RuntimeException('Script contained a non-push opcode');
241
            }
242
243 42
            $size = $buffer->getSize();
244 42
            if ($size === 0) {
245 4
                return Opcodes::OP_0;
246
            }
247
248 42
            $first = ord($buffer->getBinary());
249 42
            if ($size === 1 && $first >= 1 && $first <= 16) {
250
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
251
            } else {
252 42
                return $buffer;
253
            }
254 50
        }, $buffers));
255
    }
256
257
    /**
258
     * Verify a scriptSig / scriptWitness against a scriptPubKey.
259
     * Useful for checking the outcome of certain things, like hash locks (p2sh)
260
     *
261
     * @param int $flags
262
     * @param ScriptInterface $scriptSig
263
     * @param ScriptInterface $scriptPubKey
264
     * @param ScriptWitnessInterface|null $scriptWitness
265
     * @return bool
266
     */
267 26
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
268
    {
269 26
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
270
    }
271
272
    /**
273
     * Evaluates a scriptPubKey against the provided chunks.
274
     *
275
     * @param ScriptInterface $scriptPubKey
276
     * @param array $chunks
277
     * @param int $sigVersion
278
     * @return bool
279
     */
280 56
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
281
    {
282 56
        $stack = new Stack($chunks);
283 56
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
284 2
            return false;
285
        }
286
287 54
        if ($stack->isEmpty()) {
288
            return false;
289
        }
290
291 54
        if (false === $this->interpreter->castToBool($stack[-1])) {
292 4
            return false;
293
        }
294
295 50
        return true;
296
    }
297
298
    /**
299
     * This function is strictly for $canSign types.
300
     * It will extract signatures/publicKeys when given $outputData, and $stack.
301
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
302
     *
303
     * @param OutputData $outputData
304
     * @param array $stack
305
     * @param int $sigVersion
306
     * @return string
307
     */
308 64
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
309
    {
310 64
        $type = $outputData->getType();
311 64
        $size = count($stack);
312
313 64
        if (ScriptType::P2PKH === $type) {
314 38
            $this->requiredSigs = 1;
315 38
            if ($size === 2) {
316 34
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
317 2
                    throw new \RuntimeException('Existing signatures are invalid!');
318
                }
319 32
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
320 36
                $this->publicKeys = [$this->pubKeySerializer->parse($stack[1])];
321
            }
322 28
        } else if (ScriptType::P2PK === $type) {
323 14
            $this->requiredSigs = 1;
324 14
            if ($size === 1) {
325 12
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
326 2
                    throw new \RuntimeException('Existing signatures are invalid!');
327
                }
328 10
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
329
            }
330 12
            $this->publicKeys = [$this->pubKeySerializer->parse($outputData->getSolution())];
331 14
        } else if (ScriptType::MULTISIG === $type) {
332 14
            $info = new Multisig($outputData->getScript(), $this->pubKeySerializer);
333 14
            $this->requiredSigs = $info->getRequiredSigCount();
334 14
            $this->publicKeys = $info->getKeys();
335 14
            $keyBufs = $info->getKeyBuffers();
336 14
            if ($size > 1) {
337
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
338 12
                $check = $this->evaluateSolution($outputData->getScript(), $stack, $sigVersion);
339 12
                $sigBufs = array_slice($stack, 1, $size - 1);
340 12
                $sigBufCount = count($sigBufs);
341
342
                // If we seem to have all signatures but fail evaluation, abort
343 12
                if ($sigBufCount === $this->requiredSigs && !$check) {
344 2
                    throw new \RuntimeException('Existing signatures are invalid!');
345
                }
346
347 10
                $keyToSigMap = $this->sortMultiSigs($outputData->getScript(), $sigBufs, $keyBufs, $sigVersion);
348
349
                // Here we learn if any signatures were invalid, it won't be in the map.
350 10
                if ($sigBufCount !== count($keyToSigMap)) {
351
                    throw new \RuntimeException('Existing signatures are invalid!');
352
                }
353
354 10
                foreach ($keyBufs as $idx => $key) {
355 10
                    if (isset($keyToSigMap[$key])) {
356 12
                        $this->signatures[$idx] = $this->txSigSerializer->parse($keyToSigMap[$key]);
357
                    }
358
                }
359
            }
360
        } else {
361
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
362
        }
363
364 58
        return $type;
365
    }
366
367
    /**
368
     * Checks $chunks (a decompiled scriptSig) for it's last element,
369
     * or defers to SignData. If both are provided, it checks the
370
     * value from $chunks against SignData.
371
     *
372
     * @param BufferInterface[] $chunks
373
     * @param SignData $signData
374
     * @return ScriptInterface
375
     */
376 30
    private function findRedeemScript(array $chunks, SignData $signData)
377
    {
378 30
        if (count($chunks) > 0) {
379 22
            $redeemScript = new Script($chunks[count($chunks) - 1]);
380 22
            if ($signData->hasRedeemScript()) {
381 22
                if (!$redeemScript->equals($signData->getRedeemScript())) {
382 22
                    throw new \RuntimeException('Extracted redeemScript did not match sign data');
383
                }
384
            }
385
        } else {
386 26
            if (!$signData->hasRedeemScript()) {
387 2
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
388
            }
389 24
            $redeemScript = $signData->getRedeemScript();
390
        }
391
392 26
        return $redeemScript;
393
    }
394
395
    /**
396
     * Checks $witness (a witness structure) for it's last element,
397
     * or defers to SignData. If both are provided, it checks the
398
     * value from $chunks against SignData.
399
     *
400
     * @param BufferInterface[] $witness
401
     * @param SignData $signData
402
     * @return ScriptInterface
403
     */
404 26
    private function findWitnessScript(array $witness, SignData $signData)
405
    {
406 26
        if (count($witness) > 0) {
407 18
            $witnessScript = new Script($witness[count($witness) - 1]);
408 18
            if ($signData->hasWitnessScript()) {
409 18
                if (!$witnessScript->equals($signData->getWitnessScript())) {
410 18
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
411
                }
412
            }
413
        } else {
414 22
            if (!$signData->hasWitnessScript()) {
415 4
                throw new \RuntimeException('Witness script not provided in sign data or witness');
416
            }
417 18
            $witnessScript = $signData->getWitnessScript();
418
        }
419
420 18
        return $witnessScript;
421
    }
422
423
    /**
424
     * Called upon instance creation.
425
     *
426
     * It ensures that violating the following prevents instance creation
427
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
428
     *  - the P2SH script covers signable types and P2WSH/P2WKH
429
     *  - the witnessScript covers signable types only
430
     *
431
     * @param SignData $signData
432
     * @param ScriptInterface $scriptPubKey
433
     * @param ScriptInterface $scriptSig
434
     * @param BufferInterface[] $witness
435
     * @return $this
436
     */
437 86
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
438
    {
439 86
        $classifier = new OutputClassifier();
440 86
        $sigVersion = SigHash::V0;
441 86
        $sigChunks = [];
442 86
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
443 86
        if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
444 2
            throw new \RuntimeException('scriptPubKey not supported');
445
        }
446
447 84
        if ($solution->canSign()) {
448 38
            $sigChunks = $this->evalPushOnly($scriptSig);
449
        }
450
451 84
        if ($solution->getType() === ScriptType::P2SH) {
452 30
            $chunks = $this->evalPushOnly($scriptSig);
453 30
            $redeemScript = $this->findRedeemScript($chunks, $signData);
454 26
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
455 2
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
456
            }
457
458 24
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
459 24
            if (!in_array($solution->getType(), self::$validP2sh)) {
460 2
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
461
            }
462
463 22
            $sigChunks = array_slice($chunks, 0, -1);
464
        }
465
466 76
        if ($solution->getType() === ScriptType::P2WKH) {
467 8
            $sigVersion = SigHash::V1;
468 8
            $solution = $this->witnessKeyHash = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
469 8
            $sigChunks = $witness;
470 70
        } else if ($solution->getType() === ScriptType::P2WSH) {
471 26
            $sigVersion = SigHash::V1;
472 26
            $witnessScript = $this->findWitnessScript($witness, $signData);
473
474
            // Essentially all the reference implementation does
475 18
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
476 2
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
477
            }
478
479 16
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
480 16
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
481 2
                throw new \RuntimeException('Unsupported witness-script-hash script');
482
            }
483
484 14
            $sigChunks = array_slice($witness, 0, -1);
485
        }
486
487 64
        $this->sigVersion = $sigVersion;
488 64
        $this->signScript = $solution;
489
490 64
        $this->extractFromValues($solution, $sigChunks, $this->sigVersion);
491
492 58
        return $this;
493
    }
494
495
    /**
496
     * @param ScriptInterface $scriptCode
497
     * @param int $sigHashType
498
     * @param int $sigVersion
499
     * @return BufferInterface
500
     */
501 52
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
502
    {
503 52
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
504 2
            throw new \RuntimeException('Invalid sigHashType requested');
505
        }
506
507 50
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
508
    }
509
510
    /**
511
     * @param int $sigHashType
512
     * @return BufferInterface
513
     */
514 2
    public function getSigHash($sigHashType)
515
    {
516 2
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
517
    }
518
519
    /**
520
     * @param PrivateKeyInterface $key
521
     * @param ScriptInterface $scriptCode
522
     * @param int $sigHashType
523
     * @param int $sigVersion
524
     * @return TransactionSignatureInterface
525
     */
526 50
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
527
    {
528 50
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
529 50
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
530 50
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
531
    }
532
533
    /**
534
     * @return bool
535
     */
536 56
    public function isFullySigned()
537
    {
538 56
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
539
    }
540
541
    /**
542
     * @return int
543
     */
544 50
    public function getRequiredSigs()
545
    {
546 50
        return $this->requiredSigs;
547
    }
548
549
    /**
550
     * @return TransactionSignatureInterface[]
551
     */
552 50
    public function getSignatures()
553
    {
554 50
        return $this->signatures;
555
    }
556
557
    /**
558
     * @return PublicKeyInterface[]
559
     */
560 50
    public function getPublicKeys()
561
    {
562 50
        return $this->publicKeys;
563
    }
564
565
    /**
566
     * @return OutputData
567
     */
568
    public function getSignScript()
569
    {
570
        return $this->signScript;
571
    }
572 56
573
    /**
574 56
     * @return OutputData
575
     */
576
    public function getScriptPubKey()
577
    {
578 56
        return $this->scriptPubKey;
579
    }
580
581
    /**
582 56
     * @return OutputData
583 12
     */
584 2
    public function getRedeemScript()
585
    {
586 10
        if (null === $this->redeemScript) {
587 46
            throw new \RuntimeException("Input has no redeemScript, cannot call getRedeemScript");
588 34
        }
589 2
590
        return $this->redeemScript;
591 32
    }
592 32
593 12
    /**
594 12
     * @return OutputData
595 12
     */
596 12
    public function getWitnessScript()
597 10
    {
598 12
        if (null === $this->witnessScript) {
599
            throw new \RuntimeException("Input has no witnessScript, cannot call getWitnessScript");
600
        }
601
602 12
        return $this->witnessScript;
603 12
    }
604
605
    /**
606
     * @return bool
607
     */
608
    public function isP2SH()
609 50
    {
610
        if ($this->scriptPubKey->getType() === ScriptType::P2SH && ($this->redeemScript instanceof OutputData)) {
611
            return true;
612
        }
613
614
        return false;
615
    }
616
617
    /**
618 50
     * @return bool
619
     */
620 50
    public function isP2WSH()
621
    {
622 50
        if ($this->redeemScript instanceof OutputData) {
623 50
            if ($this->redeemScript->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
624
                return true;
625
            }
626 50
        }
627 50
628 22
        if ($this->scriptPubKey->getType() === ScriptType::P2WSH && ($this->witnessScript instanceof OutputData)) {
629
            return true;
630
        }
631 50
632
        return false;
633
    }
634 50
635 50
    /**
636
     * Sign the input using $key and $sigHashTypes
637 50
     *
638 22
     * @param PrivateKeyInterface $key
639 22
     * @param int $sigHashType
640 22
     * @return $this
641 22
     */
642
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
643 2
    {
644
        if ($this->isFullySigned()) {
645
            return $this;
646
        }
647 22
648
        if (SigHash::V1 === $this->sigVersion && !$key->isCompressed()) {
649
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
650 50
        }
651
652
        if ($this->signScript->getType() === ScriptType::P2PK) {
653
            if (!$this->pubKeySerializer->serialize($key->getPublicKey())->equals($this->signScript->getSolution())) {
654
                throw new \RuntimeException('Signing with the wrong private key');
655
            }
656
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
657
        } else if ($this->signScript->getType() === ScriptType::P2PKH) {
658
            if (!$key->getPubKeyHash($this->pubKeySerializer)->equals($this->signScript->getSolution())) {
659 50
                throw new \RuntimeException('Signing with the wrong private key');
660
            }
661 50
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
662 50
            $this->publicKeys[0] = $key->getPublicKey();
663 10
        } else if ($this->signScript->getType() === ScriptType::MULTISIG) {
664 10
            $signed = false;
665
            foreach ($this->publicKeys as $keyIdx => $publicKey) {
666 42
                if ($key->getPublicKey()->equals($publicKey)) {
667 32
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
668 32
                    $signed = true;
669
                }
670 10
            }
671 10
672 10
            if (!$signed) {
673 10
                throw new \RuntimeException('Signing with the wrong private key');
674 10
            }
675
        } else {
676
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
677
        }
678
679
        return $this;
680
    }
681 50
682
    /**
683
     * Verifies the input using $flags for script verification
684
     *
685
     * @param int $flags
686
     * @return bool
687
     */
688
    public function verify($flags = null)
689 50
    {
690
        $consensus = ScriptFactory::consensus();
691 50
692 50
        if ($flags === null) {
693 50
            $flags = $this->flags;
694 2
        }
695 2
696
        $flags |= Interpreter::VERIFY_P2SH;
697
        if (SigHash::V1 === $this->sigVersion) {
698 50
            $flags |= Interpreter::VERIFY_WITNESS;
699 50
        }
700 50
701 24
        $sig = $this->serializeSignatures();
702
703
        // Take serialized signatures, and use mutator to add this inputs sig data
704 50
        $mutator = TransactionFactory::mutate($this->tx);
705 50
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
706 50
707 18
        if (SigHash::V1 === $this->sigVersion) {
708 18
            $witness = [];
709 6
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
710
                if ($i === $this->nInput) {
711 18
                    $witness[] = $sig->getScriptWitness();
712
                } else {
713
                    $witness[] = new ScriptWitness([]);
714 50
                }
715 8
            }
716 44
717 14
            $mutator->witness($witness);
718 14
        }
719 14
720
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
721
    }
722
723 50
    /**
724 18
     * Produces the script stack that solves the $outputType
725
     *
726
     * @param string $outputType
727 50
     * @return BufferInterface[]
728
     */
729
    private function serializeSolution($outputType)
730
    {
731
        $result = [];
732
        if (ScriptType::P2PK === $outputType) {
733
            if (count($this->signatures) === 1) {
734
                $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...
735
            }
736
        } else if (ScriptType::P2PKH === $outputType) {
737
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
738
                $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...
739
            }
740
        } else if (ScriptType::MULTISIG === $outputType) {
741
            $result[] = new Buffer();
742
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
743
                if (isset($this->signatures[$i])) {
744
                    $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...
745
                }
746
            }
747
        } else {
748
            throw new \RuntimeException('Parameter 0 for serializeSolution was a non-standard input type');
749
        }
750
751
        return $result;
752
    }
753
754
    /**
755
     * Produces a SigValues instance containing the scriptSig & script witness
756
     *
757
     * @return SigValues
758
     */
759
    public function serializeSignatures()
760
    {
761
        static $emptyScript = null;
762
        static $emptyWitness = null;
763
        if (is_null($emptyScript) || is_null($emptyWitness)) {
764
            $emptyScript = new Script();
765
            $emptyWitness = new ScriptWitness([]);
766
        }
767
768
        $scriptSigChunks = [];
769
        $witness = [];
770
        if ($this->scriptPubKey->canSign()) {
771
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
772
        }
773
774
        $solution = $this->scriptPubKey;
775
        $p2sh = false;
776
        if ($solution->getType() === ScriptType::P2SH) {
777
            $p2sh = true;
778
            if ($this->redeemScript->canSign()) {
779
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
780
            }
781
            $solution = $this->redeemScript;
782
        }
783
784
        if ($solution->getType() === ScriptType::P2WKH) {
785
            $witness = $this->serializeSolution(ScriptType::P2PKH);
786
        } else if ($solution->getType() === ScriptType::P2WSH) {
787
            if ($this->witnessScript->canSign()) {
788
                $witness = $this->serializeSolution($this->witnessScript->getType());
789
                $witness[] = $this->witnessScript->getScript()->getBuffer();
790
            }
791
        }
792
793
        if ($p2sh) {
794
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
795
        }
796
797
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
798
    }
799
}
800