Completed
Pull Request — master (#403)
by thomas
86:27 queued 84:05
created

InputSigner::calculateSigHash()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 3
dl 0
loc 14
ccs 7
cts 8
cp 0.875
crap 3.0175
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\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 288
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData)
146
    {
147 288
        if (!isset($tx->getInputs()[$nInput])) {
148 6
            throw new \RuntimeException('No input at this index');
149
        }
150
151 282
        $this->ecAdapter = $ecAdapter;
152 282
        $this->tx = $tx;
153 282
        $this->nInput = $nInput;
154 282
        $this->txOut = $txOut;
155 282
        $this->classifier = new OutputClassifier();
156 282
        $this->interpreter = new Interpreter();
157 282
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue());
158 282
        $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 282
        $this->publicKeys = [];
160 282
        $this->signatures = [];
161 282
        $this->solve($signData);
162 252
        $this->extractSignatures();
163 216
    }
164
165
    /**
166
     * @param TransactionSignatureInterface[] $stack
167
     * @param PublicKeyInterface[] $publicKeys
168
     * @return \SplObjectStorage
169
     */
170 54
    private function sortMultiSigs($stack, array $publicKeys)
171
    {
172 54
        $sigSort = new SignatureSort($this->ecAdapter);
173 54
        $sigs = new \SplObjectStorage;
174
175 54
        foreach ($stack as $txSig) {
176 54
            $hash = $this->getSigHash($txSig->getHashType());
177 54
            $linked = $sigSort->link([$txSig->getSignature()], $publicKeys, $hash);
178 54
            foreach ($publicKeys as $key) {
179 54
                if ($linked->contains($key)) {
180 52
                    $sigs[$key] = $txSig;
181 16
                }
182 18
            }
183 18
        }
184
185 54
        return $sigs;
186
    }
187
188
    /**
189
     * @param ScriptInterface $script
190
     * @return \BitWasp\Buffertools\BufferInterface[]
191
     */
192 204
    private function evalPushOnly(ScriptInterface $script)
193
    {
194 204
        $stack = new Stack();
195 204
        $interpreter = new Interpreter();
196 204
        $interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue()));
197 204
        return $stack->all();
198
    }
199
200
    /**
201
     * @param BufferInterface[] $buffers
202
     * @return ScriptInterface
203
     */
204
    private function pushAll(array $buffers)
205
    {
206 216
        return ScriptFactory::sequence(array_map(function ($buffer) {
207 174
            if (!($buffer instanceof BufferInterface)) {
208
                throw new \RuntimeException('Script contained a non-push opcode');
209
            }
210
211 174
            $size = $buffer->getSize();
212 174
            if ($size === 0) {
213 30
                return Opcodes::OP_0;
214
            }
215
216 174
            $first = ord($buffer->getBinary());
217 174
            if ($size === 1 && $first >= 1 && $first <= 16) {
218
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
219
            } else {
220 174
                return $buffer;
221
            }
222 216
        }, $buffers));
223
    }
224
225
    /**
226
     * @param ScriptInterface $scriptSig
227
     * @param ScriptInterface $scriptPubKey
228
     * @param ScriptWitnessInterface|null $scriptWitness
229
     * @return bool
230
     */
231 96
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
232
    {
233 96
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
234
    }
235
236
    /**
237
     * @param ScriptInterface $scriptPubKey
238
     * @param array $chunks
239
     * @param int $sigVersion
240
     * @return bool
241
     */
242 150
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
243
    {
244 150
        $stack = new Stack($chunks);
245 150
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
246 6
            return false;
247
        }
248
249 144
        if ($stack->isEmpty()) {
250
            return false;
251 32
        }
252
253 144
        if (false === $this->interpreter->castToBool($stack[-1])) {
254 6
            return false;
255
        }
256
257 138
        return true;
258
    }
259
260
    /**
261
     * Called upon instance creation.
262
     * This function must throw an exception whenever execution
263
     * does not yield a signable script.
264
     *
265
     * It ensures:
266
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
267
     *  - the P2SH script covers signable types and P2WSH/P2WKH
268
     *  - the witnessScript covers signable types only.
269
     *  - violating the above prevents instance creation
270
     * @param SignData $signData
271
     * @return $this
272
     * @throws \Exception
273
     */
274 282
    private function solve(SignData $signData)
275
    {
276 282
        $scriptPubKey = $this->txOut->getScript();
277 282
        $solveFlags = Interpreter::VERIFY_SIGPUSHONLY;
278 282
        $sigVersion = SigHash::V0;
279 282
        $solution = $this->scriptPubKey = $this->classifier->decode($scriptPubKey);
280 282
        if ($solution->getType() !== OutputClassifier::PAYTOSCRIPTHASH && !in_array($solution->getType(), self::$validP2sh)) {
281 6
            throw new \RuntimeException('scriptPubKey not supported');
282
        }
283
284 276
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
285 96
            $redeemScript = $signData->getRedeemScript();
286 96
            if (!$this->verifySolution($solveFlags, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
287 6
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
288
            }
289 90
            $solution = $this->redeemScript = $this->classifier->decode($redeemScript);
290 90
            if (!in_array($solution->getType(), self::$validP2sh)) {
291 6
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
292
            }
293 28
        }
294
295 264
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
296 24
            $sigVersion = SigHash::V1;
297 24
            $this->signCode = $this->witnessKeyHash = $this->classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
0 ignored issues
show
Bug introduced by
The property signCode 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...
298 248
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
299 78
            $sigVersion = SigHash::V1;
300 78
            $witnessScript = $signData->getWitnessScript();
301 78
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
302 6
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
303
            }
304 72
            $solution = $this->witnessScript = $this->classifier->decode($witnessScript);
305 72
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
306 6
                throw new \RuntimeException('Unsupported witness-script-hash script');
307
            }
308 22
        }
309
310 252
        $this->sigVersion = $sigVersion;
311 252
        $this->signScript = $solution;
312
313 252
        return $this;
314
    }
315
316
    /**
317
     * This function is strictly for $canSign types.
318
     * It will extract signatures/publicKeys when given $outputData, and $stack.
319
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
320
     * @param OutputData $outputData
321
     * @param array $stack
322
     * @param $sigVersion
323
     * @return mixed
324
     */
325 234
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
326
    {
327 234
        $type = $outputData->getType();
328 234
        $size = count($stack);
329 234
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
330 132
            $this->requiredSigs = 1;
331 132
            if ($size === 2) {
332 114
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
333 6
                    throw new \RuntimeException('Existing signatures are invalid!');
334
                }
335 108
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
336 108
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
337 36
            }
338 42
        }
339
340 228
        if ($type === OutputClassifier::PAYTOPUBKEY && count($stack) === 1) {
341 36
            $this->requiredSigs = 1;
342 36
            if ($size === 1) {
343 36
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
344 6
                    throw new \RuntimeException('Existing signatures are invalid!');
345
                }
346
347 30
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
348 30
                $this->publicKeys = [PublicKeyFactory::fromHex($outputData->getSolution())];
349 10
            }
350 10
        }
351
352 222
        if ($type === OutputClassifier::MULTISIG) {
353 54
            $info = new Multisig($outputData->getScript());
354 54
            $this->requiredSigs = $info->getRequiredSigCount();
355 54
            $this->publicKeys = $info->getKeys();
356 54
            if ($size > 1) {
357 54
                $vars = [];
358 54
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
359 54
                    $vars[] = TransactionSignatureFactory::fromHex($stack[$i], $this->ecAdapter);
360 18
                }
361
362 54
                $this->signatures = array_fill(0, count($this->publicKeys), null);
363 54
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
364 54
                $count = 0;
365 54
                foreach ($this->publicKeys as $idx => $key) {
366 54
                    if (isset($sigs[$key])) {
367 48
                        $this->signatures[$idx] = $sigs[$key];
368 52
                        $count++;
369 16
                    }
370 18
                }
371
372 54
                if (count($vars) !== $count) {
373 6
                    throw new \RuntimeException('Existing signatures are invalid!');
374
                }
375
                // Don't evaluate, already checked sigs during sort. Todo: fix this.
376 16
            }
377 16
        }
378
379 216
        return $type;
380
    }
381
382
    /**
383
     * High level function for extracting signatures from a pre-signed
384
     * transaction.
385
     *
386
     * @return $this
387
     */
388 252
    public function extractSignatures()
389
    {
390
391 252
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
392 252
        $witnesses = $this->tx->getWitnesses();
393 252
        $witness = isset($witnesses[$this->nInput]) ? $witnesses[$this->nInput]->all() : [];
394
395 252
        $solution = $this->scriptPubKey;
396 252
        $sigVersion = SigHash::V0;
397 252
        $chunks = [];
398 252
        if ($solution->canSign()) {
399 126
            $chunks = $this->evalPushOnly($scriptSig);
400 42
        }
401
402 252
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
403 78
            $chunks = $this->evalPushOnly($scriptSig);
404 78
            if (count($chunks) > 0) {
405 72
                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...
406 6
                    throw new \RuntimeException('Extracted redeemScript did not match script-hash');
407
                }
408
409 66
                $solution = $this->redeemScript;
410 66
                $chunks = array_slice($chunks, 0, -1);
411 22
            }
412 24
        }
413
414 246
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
415 24
            $solution = $this->witnessKeyHash;
416 24
            $sigVersion = SigHash::V1;
417 24
            $chunks = $witness;
418 238
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
419 66
            if (count($witness) > 0) {
420 66
                if (!end($witness)->equals($this->witnessScript->getScript()->getBuffer())) {
421 12
                    throw new \RuntimeException('Extracted witnessScript did not match witness-script-hash');
422
                }
423
424 54
                $solution = $this->witnessScript;
425 54
                $sigVersion = SigHash::V1;
426 54
                $chunks = array_slice($witness, 0, -1);
427 18
            }
428 18
        }
429
430 234
        $this->extractFromValues($solution, $chunks, $sigVersion);
431
432 216
        return $this;
433
    }
434
435
    /**
436
     * @param ScriptInterface $scriptCode
437
     * @param int $sigHashType
438
     * @param int $sigVersion
439
     * @return BufferInterface
440
     */
441 222
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
442
    {
443 222
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
444
            throw new \RuntimeException('Invalid sigHashType requested');
445
        }
446
447 222
        if ($sigVersion === SigHash::V1) {
448 78
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
449 26
        } else {
450 144
            $hasher = new Hasher($this->tx);
451
        }
452
453 222
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
454
    }
455
456
    /**
457
     * @param int $sigHashType
458
     * @return BufferInterface
459
     */
460 54
    public function getSigHash($sigHashType)
461
    {
462 54
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
463
    }
464
465
    /**
466
     * @param PrivateKeyInterface $key
467
     * @param ScriptInterface $scriptCode
468
     * @param int $sigHashType
469
     * @param int $sigVersion
470
     * @return TransactionSignature
471
     */
472 216
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
473
    {
474 216
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
475 216
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
476 216
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
477
    }
478
479
    /**
480
     * @return bool
481
     */
482 216
    public function isFullySigned()
483
    {
484 216
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
485
    }
486
487
    /**
488
     * @return int
489
     */
490 132
    public function getRequiredSigs()
491
    {
492 132
        return $this->requiredSigs;
493
    }
494
495
    /**
496
     * @return TransactionSignatureInterface[]
497
     */
498 132
    public function getSignatures()
499
    {
500 132
        return $this->signatures;
501
    }
502
503
    /**
504
     * @return PublicKeyInterface[]
505
     */
506 132
    public function getPublicKeys()
507
    {
508 132
        return $this->publicKeys;
509
    }
510
511
    /**
512
     * The function only returns true when $scriptPubKey could be classified
513
     *
514
     * @param PrivateKeyInterface $key
515
     * @param OutputData $solution
516
     * @param int $sigHashType
517
     * @param int $sigVersion
518
     */
519 216
    private function doSignature(PrivateKeyInterface $key, OutputData $solution, $sigHashType, $sigVersion = SigHash::V0)
520
    {
521 216
        if ($this->isFullySigned()) {
522
            return;
523
        }
524
525 216
        if ($solution->getType() === OutputClassifier::PAYTOPUBKEY) {
526 36
            if (!$key->getPublicKey()->getBuffer()->equals($solution->getSolution())) {
527
                throw new \RuntimeException('Signing with the wrong private key');
528
            }
529 36
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
530 36
            $this->publicKeys[0] = $key->getPublicKey();
531 36
            $this->requiredSigs = 1;
532 192
        } else if ($solution->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
533 126
            if (!$key->getPubKeyHash()->equals($solution->getSolution())) {
534
                throw new \RuntimeException('Signing with the wrong private key');
535
            }
536 126
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
537 126
            $this->publicKeys[0] = $key->getPublicKey();
538 126
            $this->requiredSigs = 1;
539 96
        } else if ($solution->getType() === OutputClassifier::MULTISIG) {
540 54
            $info = new Multisig($solution->getScript());
541 54
            $this->publicKeys = $info->getKeys();
542 54
            $this->requiredSigs = $info->getRequiredSigCount();
543
544 54
            $myKey = $key->getPublicKey()->getBuffer();
545 54
            $signed = false;
546 54
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
547 54
                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...
548 54
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
549 54
                    $signed = true;
550 18
                }
551 18
            }
552
553 54
            if (!$signed) {
554 36
                throw new \RuntimeException('Signing with the wrong private key');
555
            }
556 18
        } else {
557
            throw new \RuntimeException('Cannot sign unknown script type');
558
        }
559 216
    }
560
561
    /**
562
     * @param PrivateKeyInterface $key
563
     * @param int $sigHashType
564
     * @return bool
565
     */
566 216
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
567
    {
568 216
        $solution = $this->scriptPubKey;
569 216
        $sigVersion = SigHash::V0;
570 216
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
571 66
            $solution = $this->redeemScript;
572 22
        }
573
574 216
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
575 24
            $solution = $this->witnessKeyHash;
576 24
            $sigVersion = SigHash::V1;
577 200
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
578 54
            $solution = $this->witnessScript;
579 86
            $sigVersion = SigHash::V1;
580 18
        }
581
582 216
        if ($solution->canSign()) {
583 216
            $this->doSignature($key, $solution, $sigHashType, $sigVersion);
584 216
            return true;
585
        }
586
587
        return false;
588
    }
589
590
    /**
591
     * @param int $flags
592
     * @return bool
593
     */
594 186
    public function verify($flags = null)
595
    {
596 186
        $consensus = ScriptFactory::consensus();
597
598 186
        if ($flags === null) {
599 132
            $flags = $this->flags;
600 44
        }
601
602 186
        $flags |= Interpreter::VERIFY_P2SH;
603 186
        if ($this->sigVersion === 1) {
604 78
            $flags |= Interpreter::VERIFY_WITNESS;
605 26
        }
606
607 186
        $sig = $this->serializeSignatures();
608
609 186
        $mutator = TransactionFactory::mutate($this->tx);
610 186
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
611 186
        if ($this->sigVersion === 1) {
612 78
            $witness = array_fill(0, count($this->tx->getInputs()), null);
613 78
            $witness[$this->nInput] = $sig->getScriptWitness();
614 78
            $mutator->witness($witness);
615 26
        }
616
617 186
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
618
    }
619
620
    /**
621
     * @param string $outputType
622
     * @return BufferInterface[]
623
     */
624 216
    private function serializeSolution($outputType)
625
    {
626 216
        $result = [];
627 216
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
628 36
            if (count($this->signatures) === 1) {
629 36
                $result = [$this->signatures[0]->getBuffer()];
630 12
            }
631 192
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
632 126
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
633 126
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
634 42
            }
635 96
        } else if ($outputType === OutputClassifier::MULTISIG) {
636 54
            $result[] = new Buffer();
637 54
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
638 54
                if (isset($this->signatures[$i])) {
639 54
                    $result[] = $this->signatures[$i]->getBuffer();
640 18
                }
641 18
            }
642 18
        } else {
643
            throw new \RuntimeException('Cannot serialize this script sig');
644
        }
645
646 216
        return $result;
647
    }
648
649
    /**
650
     * @return SigValues
651
     */
652 216
    public function serializeSignatures()
653
    {
654 216
        static $emptyScript = null;
655 216
        static $emptyWitness = null;
656 216
        if (is_null($emptyScript) || is_null($emptyWitness)) {
657 6
            $emptyScript = new Script();
658 6
            $emptyWitness = new ScriptWitness([]);
659 2
        }
660
661 216
        $scriptSigChunks = [];
662 216
        $witness = [];
663 216
        if ($this->scriptPubKey->canSign()) {
664 108
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
665 36
        }
666
667 216
        $solution = $this->scriptPubKey;
668 216
        $p2sh = false;
669 216
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
670 66
            $p2sh = true;
671 66
            if ($this->redeemScript->canSign()) {
672 30
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
673 10
            }
674 66
            $solution = $this->redeemScript;
675 22
        }
676
677 216
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
678 24
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
679 200
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
680 54
            if ($this->witnessScript->canSign()) {
681 54
                $witness = $this->serializeSolution($this->witnessScript->getType());
682 54
                $witness[] = $this->witnessScript->getScript()->getBuffer();
683 18
            }
684 18
        }
685
686 216
        if ($p2sh) {
687 66
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
688 22
        }
689
690 216
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
691
    }
692
}
693