Completed
Pull Request — master (#514)
by thomas
72:28
created

InputSigner::parseStepPublicKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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