Completed
Pull Request — 0.0.35 (#657)
by thomas
27:45 queued 21:38
created

InputSigner::getSignScript()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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