Completed
Pull Request — master (#518)
by thomas
58:22 queued 55:50
created

InputSigner::extract()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0585

Importance

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