Completed
Push — master ( fceb66...595279 )
by thomas
73:29 queued 71:27
created

InputSigner::serializeSolution()   C

Complexity

Conditions 11
Paths 6

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 11.0207

Importance

Changes 0
Metric Value
cc 11
eloc 19
nc 6
nop 1
dl 0
loc 28
ccs 17
cts 18
cp 0.9444
crap 11.0207
rs 5.2653
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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