Completed
Push — master ( a10313...fbfc3d )
by thomas
9s
created

InputSigner::doSignature()   D

Complexity

Conditions 10
Paths 12

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 10.1953

Importance

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

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

class MyClass { }

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

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

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
159 234
        $this->publicKeys = [];
160 234
        $this->signatures = [];
161 234
        $this->solve($signData);
162 204
        $this->extractSignatures();
163 168
    }
164
165
    /**
166
     * @param TransactionSignatureInterface[] $stack
167
     * @param PublicKeyInterface[] $publicKeys
168
     * @return \SplObjectStorage
169
     */
170 36
    private function sortMultiSigs($stack, array $publicKeys)
171
    {
172 36
        $sigSort = new SignatureSort($this->ecAdapter);
173 36
        $sigs = new \SplObjectStorage;
174
175 36
        foreach ($stack as $txSig) {
176 36
            $hash = $this->getSigHash($txSig->getHashType());
177 36
            $linked = $sigSort->link([$txSig->getSignature()], $publicKeys, $hash);
178 36
            foreach ($publicKeys as $key) {
179 36
                if ($linked->contains($key)) {
180 33
                    $sigs[$key] = $txSig;
181 15
                }
182 18
            }
183 18
        }
184
185 36
        return $sigs;
186
    }
187
188
    /**
189
     * @param ScriptInterface $script
190
     * @return \BitWasp\Buffertools\BufferInterface[]
191
     */
192 174
    private function evalPushOnly(ScriptInterface $script)
193
    {
194 174
        $stack = new Stack();
195 174
        $interpreter = new Interpreter();
196 174
        $interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue()));
197 174
        return $stack->all();
198
    }
199
200
    /**
201
     * @param BufferInterface[] $buffers
202
     * @return ScriptInterface
203
     */
204
    private function pushAll(array $buffers)
205
    {
206 168
        return ScriptFactory::sequence(array_map(function ($buffer) {
207 144
            if (!($buffer instanceof BufferInterface)) {
208
                throw new \RuntimeException('Script contained a non-push opcode');
209
            }
210
211 144
            $size = $buffer->getSize();
212 144
            if ($size === 0) {
213 18
                return Opcodes::OP_0;
214
            }
215
216 144
            $first = ord($buffer->getBinary());
217 144
            if ($size === 1 && $first >= 1 && $first <= 16) {
218
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
219
            } else {
220 144
                return $buffer;
221
            }
222 168
        }, $buffers));
223
    }
224
225
    /**
226
     * @param int $flags
227
     * @param ScriptInterface $scriptSig
228
     * @param ScriptInterface $scriptPubKey
229
     * @param ScriptWitnessInterface|null $scriptWitness
230
     * @return bool
231 90
     */
232
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
233 90
    {
234
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
235
    }
236
237
    /**
238
     * @param ScriptInterface $scriptPubKey
239
     * @param array $chunks
240
     * @param int $sigVersion
241
     * @return bool
242 132
     */
243
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
244 132
    {
245 132
        $stack = new Stack($chunks);
246 6
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
247
            return false;
248
        }
249 126
250
        if ($stack->isEmpty()) {
251 45
            return false;
252
        }
253 126
254 6
        if (false === $this->interpreter->castToBool($stack[-1])) {
255
            return false;
256
        }
257 120
258
        return true;
259
    }
260
261
    /**
262
     * Called upon instance creation.
263
     * This function must throw an exception whenever execution
264
     * does not yield a signable script.
265
     *
266
     * It ensures:
267
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
268
     *  - the P2SH script covers signable types and P2WSH/P2WKH
269
     *  - the witnessScript covers signable types only.
270
     *  - violating the above prevents instance creation
271
     * @param SignData $signData
272
     * @return $this
273
     * @throws \Exception
274 234
     */
275
    private function solve(SignData $signData)
276 234
    {
277 234
        $scriptPubKey = $this->txOut->getScript();
278 234
        $solveFlags = Interpreter::VERIFY_SIGPUSHONLY;
279 234
        $sigVersion = SigHash::V0;
280 234
        $solution = $this->scriptPubKey = $this->classifier->decode($scriptPubKey);
281 6
        if ($solution->getType() !== OutputClassifier::PAYTOSCRIPTHASH && !in_array($solution->getType(), self::$validP2sh)) {
282
            throw new \RuntimeException('scriptPubKey not supported');
283
        }
284 228
285 90
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
286 90
            $redeemScript = $signData->getRedeemScript();
287 6
            if (!$this->verifySolution($solveFlags, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
288
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
289 84
            }
290 84
            $solution = $this->redeemScript = $this->classifier->decode($redeemScript);
291 6
            if (!in_array($solution->getType(), self::$validP2sh)) {
292
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
293 39
            }
294
        }
295 216
296 24
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
297 24
            $sigVersion = SigHash::V1;
298 207
            $solution = $this->witnessKeyHash = $this->classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
299 66
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
300 66
            $sigVersion = SigHash::V1;
301 66
            $witnessScript = $signData->getWitnessScript();
302 6
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
303
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
304 60
            }
305 60
            $solution = $this->witnessScript = $this->classifier->decode($witnessScript);
306 6
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
307
                throw new \RuntimeException('Unsupported witness-script-hash script');
308 27
            }
309
        }
310 204
311 204
        $this->sigVersion = $sigVersion;
312
        $this->signScript = $solution;
313 204
314
        return $this;
315
    }
316
317
    /**
318
     * This function is strictly for $canSign types.
319
     * It will extract signatures/publicKeys when given $outputData, and $stack.
320
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
321
     * @param OutputData $outputData
322
     * @param array $stack
323
     * @param $sigVersion
324
     * @return mixed
325 186
     */
326
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
327 186
    {
328 186
        $type = $outputData->getType();
329 186
        $size = count($stack);
330 108
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
331 108
            $this->requiredSigs = 1;
332 102
            if ($size === 2) {
333 6
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
334
                    throw new \RuntimeException('Existing signatures are invalid!');
335 96
                }
336 96
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
337 48
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
338 51
            }
339
        }
340 180
341 36
        if ($type === OutputClassifier::PAYTOPUBKEY && count($stack) === 1) {
342 36
            $this->requiredSigs = 1;
343 36
            if ($size === 1) {
344 6
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
345
                    throw new \RuntimeException('Existing signatures are invalid!');
346
                }
347 30
348 30
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
349 15
                $this->publicKeys = [PublicKeyFactory::fromHex($outputData->getSolution())];
350 15
            }
351
        }
352 174
353 36
        if ($type === OutputClassifier::MULTISIG) {
354 36
            $info = new Multisig($outputData->getScript());
355 36
            $this->requiredSigs = $info->getRequiredSigCount();
356 36
            $this->publicKeys = $info->getKeys();
357 36
            if ($size > 1) {
358 36
                $vars = [];
359 36
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
360 18
                    $vars[] = TransactionSignatureFactory::fromHex($stack[$i], $this->ecAdapter);
361
                }
362 36
363 36
                $this->signatures = array_fill(0, count($this->publicKeys), null);
364 36
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
365 36
                $count = 0;
366 36
                foreach ($this->publicKeys as $idx => $key) {
367 30
                    if (isset($sigs[$key])) {
368 33
                        $this->signatures[$idx] = $sigs[$key];
369 15
                        $count++;
370 18
                    }
371
                }
372 36
373 6
                if (count($vars) !== $count) {
374
                    throw new \RuntimeException('Existing signatures are invalid!');
375
                }
376 15
                // Don't evaluate, already checked sigs during sort. Todo: fix this.
377 15
            }
378
        }
379 168
380
        return $type;
381
    }
382
383
    /**
384
     * High level function for extracting signatures from a pre-signed
385
     * transaction.
386
     *
387
     * @return $this
388 204
     */
389
    public function extractSignatures()
390
    {
391 204
392 204
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
393 204
        $witnesses = $this->tx->getWitnesses();
394
        $witness = isset($witnesses[$this->nInput]) ? $witnesses[$this->nInput]->all() : [];
395 204
396 204
        $solution = $this->scriptPubKey;
397 204
        $sigVersion = SigHash::V0;
398 204
        $chunks = [];
399 102
        if ($solution->canSign()) {
400 51
            $chunks = $this->evalPushOnly($scriptSig);
401
        }
402 204
403 72
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
404 72
            $chunks = $this->evalPushOnly($scriptSig);
405 66
            if (count($chunks) > 0) {
406 6
                if (!$chunks[count($chunks) - 1]->equals($this->redeemScript->getScript()->getBuffer())) {
0 ignored issues
show
Documentation introduced by
$this->redeemScript->getScript()->getBuffer() is of type object<BitWasp\Buffertools\Buffer>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
407
                    throw new \RuntimeException('Extracted redeemScript did not match script-hash');
408
                }
409 60
410 60
                $solution = $this->redeemScript;
411 30
                $chunks = array_slice($chunks, 0, -1);
412 33
            }
413
        }
414 198
415 24
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
416 24
            $solution = $this->witnessKeyHash;
417 24
            $sigVersion = SigHash::V1;
418 195
            $chunks = $witness;
419 54
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
420 54
            if (count($witness) > 0) {
421 12
                if (!end($witness)->equals($this->witnessScript->getScript()->getBuffer())) {
422
                    throw new \RuntimeException('Extracted witnessScript did not match witness-script-hash');
423
                }
424 42
425 42
                $solution = $this->witnessScript;
426 42
                $sigVersion = SigHash::V1;
427 21
                $chunks = array_slice($witness, 0, -1);
428 21
            }
429
        }
430 186
431
        $this->extractFromValues($solution, $chunks, $sigVersion);
432 168
433
        return $this;
434
    }
435
436
    /**
437
     * @param ScriptInterface $scriptCode
438
     * @param int $sigHashType
439
     * @param int $sigVersion
440
     * @return BufferInterface
441 174
     */
442
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
443 174
    {
444
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
445
            throw new \RuntimeException('Invalid sigHashType requested');
446
        }
447 174
448 66
        if ($sigVersion === SigHash::V1) {
449 33
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
450 114
        } else {
451
            $hasher = new Hasher($this->tx);
452
        }
453 174
454
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
455
    }
456
457
    /**
458
     * @param int $sigHashType
459
     * @return BufferInterface
460 36
     */
461
    public function getSigHash($sigHashType)
462 36
    {
463
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
464
    }
465
466
    /**
467
     * @param PrivateKeyInterface $key
468
     * @param ScriptInterface $scriptCode
469
     * @param int $sigHashType
470
     * @param int $sigVersion
471
     * @return TransactionSignature
472 168
     */
473
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
474 168
    {
475 168
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
476 168
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
477
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
478
    }
479
480
    /**
481
     * @return bool
482 168
     */
483
    public function isFullySigned()
484 168
    {
485
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
486
    }
487
488
    /**
489
     * @return int
490 150
     */
491
    public function getRequiredSigs()
492 150
    {
493
        return $this->requiredSigs;
494
    }
495
496
    /**
497
     * @return TransactionSignatureInterface[]
498 150
     */
499
    public function getSignatures()
500 150
    {
501
        return $this->signatures;
502
    }
503
504
    /**
505
     * @return PublicKeyInterface[]
506 150
     */
507
    public function getPublicKeys()
508 150
    {
509
        return $this->publicKeys;
510
    }
511
512
    /**
513
     * @param PrivateKeyInterface $key
514
     * @param int $sigHashType
515
     * @return $this
516
     */
517
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
518
    {
519 168
        if ($this->isFullySigned()) {
520
            return $this;
521 168
        }
522
523
        if ($this->signScript->getType() === OutputClassifier::PAYTOPUBKEY) {
524
            if (!$key->getPublicKey()->getBuffer()->equals($this->signScript->getSolution())) {
525 168
                throw new \RuntimeException('Signing with the wrong private key');
526 36
            }
527
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
528
            $this->publicKeys[0] = $key->getPublicKey();
529 36
            $this->requiredSigs = 1;
530 36
        } else if ($this->signScript->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
531 36
            if (!$key->getPubKeyHash()->equals($this->signScript->getSolution())) {
532 153
                throw new \RuntimeException('Signing with the wrong private key');
533 102
            }
534
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
535
            $this->publicKeys[0] = $key->getPublicKey();
536 102
            $this->requiredSigs = 1;
537 102
        } else if ($this->signScript->getType() === OutputClassifier::MULTISIG) {
538 102
            $info = new Multisig($this->signScript->getScript());
539 87
            $this->publicKeys = $info->getKeys();
540 36
            $this->requiredSigs = $info->getRequiredSigCount();
541 36
542 36
            $myKey = $key->getPublicKey()->getBuffer();
543
            $signed = false;
544 36
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
545 36
                if ($myKey->equals($publicKey->getBuffer())) {
0 ignored issues
show
Documentation introduced by
$publicKey->getBuffer() is of type object<BitWasp\Buffertools\BufferInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
546 36
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
547 36
                    $signed = true;
548 36
                }
549 36
            }
550 18
551 18
            if (!$signed) {
552
                throw new \RuntimeException('Signing with the wrong private key');
553 36
            }
554 18
        } else {
555
            throw new \RuntimeException('Cannot sign unknown script type');
556 18
        }
557
558
        return $this;
559 168
    }
560
561
    /**
562
     * @param int $flags
563
     * @return bool
564
     */
565
    public function verify($flags = null)
566 168
    {
567
        $consensus = ScriptFactory::consensus();
568 168
569 168
        if ($flags === null) {
570 168
            $flags = $this->flags;
571 60
        }
572 30
573
        $flags |= Interpreter::VERIFY_P2SH;
574 168
        if ($this->sigVersion === 1) {
575 24
            $flags |= Interpreter::VERIFY_WITNESS;
576 24
        }
577 159
578 42
        $sig = $this->serializeSignatures();
579 87
580 21
        $mutator = TransactionFactory::mutate($this->tx);
581
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
582 168
        if ($this->sigVersion === 1) {
583 168
            $witness = [];
584 168
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
585
                if ($i === $this->nInput) {
586
                    $witness[] = $sig->getScriptWitness();
587
                } else {
588
                    $witness[] = new ScriptWitness([]);
589
                }
590
            }
591
592
            $mutator->witness($witness);
593
        }
594 150
595
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
596 150
    }
597
598 150
    /**
599 150
     * @param string $outputType
600 75
     * @return BufferInterface[]
601
     */
602 150
    private function serializeSolution($outputType)
603 150
    {
604 66
        $result = [];
605 33
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
606
            if (count($this->signatures) === 1) {
607 150
                $result = [$this->signatures[0]->getBuffer()];
608
            }
609 150
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
610 150
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
611 150
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
612 66
            }
613 66
        } else if ($outputType === OutputClassifier::MULTISIG) {
614 66
            $result[] = new Buffer();
615 66
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
616 33
                if (isset($this->signatures[$i])) {
617 6
                    $result[] = $this->signatures[$i]->getBuffer();
618
                }
619 33
            }
620
        } else {
621 66
            throw new \RuntimeException('Cannot serialize this script sig');
622 33
        }
623
624 150
        return $result;
625
    }
626
627
    /**
628
     * @return SigValues
629
     */
630
    public function serializeSignatures()
631 168
    {
632
        static $emptyScript = null;
633 168
        static $emptyWitness = null;
634 168
        if (is_null($emptyScript) || is_null($emptyWitness)) {
635 36
            $emptyScript = new Script();
636 36
            $emptyWitness = new ScriptWitness([]);
637 18
        }
638 153
639 102
        $scriptSigChunks = [];
640 102
        $witness = [];
641 51
        if ($this->scriptPubKey->canSign()) {
642 87
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
643 36
        }
644 36
645 36
        $solution = $this->scriptPubKey;
646 36
        $p2sh = false;
647 18
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
648 18
            $p2sh = true;
649 18
            if ($this->redeemScript->canSign()) {
650
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
651
            }
652
            $solution = $this->redeemScript;
653 168
        }
654
655
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
656
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
657
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
658
            if ($this->witnessScript->canSign()) {
659 168
                $witness = $this->serializeSolution($this->witnessScript->getType());
660
                $witness[] = $this->witnessScript->getScript()->getBuffer();
661 168
            }
662 168
        }
663 168
664 6
        if ($p2sh) {
665 6
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
666 3
        }
667
668 168
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
669 168
    }
670
}
671