Completed
Push — master ( 2a5c77...6a30c1 )
by thomas
52:06 queued 14:02
created

InputSigner::verifySolution()   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
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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 ScriptInterface $scriptSig
227
     * @param ScriptInterface $scriptPubKey
228
     * @param ScriptWitnessInterface|null $scriptWitness
229
     * @return bool
230
     */
231 90
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
232
    {
233 90
        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 132
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
243
    {
244 132
        $stack = new Stack($chunks);
245 132
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
246 6
            return false;
247
        }
248
249 126
        if ($stack->isEmpty()) {
250
            return false;
251 45
        }
252
253 126
        if (false === $this->interpreter->castToBool($stack[-1])) {
254 6
            return false;
255
        }
256
257 120
        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 234
    private function solve(SignData $signData)
275
    {
276 234
        $scriptPubKey = $this->txOut->getScript();
277 234
        $solveFlags = Interpreter::VERIFY_SIGPUSHONLY;
278 234
        $sigVersion = SigHash::V0;
279 234
        $solution = $this->scriptPubKey = $this->classifier->decode($scriptPubKey);
280 234
        if ($solution->getType() !== OutputClassifier::PAYTOSCRIPTHASH && !in_array($solution->getType(), self::$validP2sh)) {
281 6
            throw new \RuntimeException('scriptPubKey not supported');
282
        }
283
284 228
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
285 90
            $redeemScript = $signData->getRedeemScript();
286 90
            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 84
            $solution = $this->redeemScript = $this->classifier->decode($redeemScript);
290 84
            if (!in_array($solution->getType(), self::$validP2sh)) {
291 6
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
292
            }
293 39
        }
294
295 216
        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 207
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
299 66
            $sigVersion = SigHash::V1;
300 66
            $witnessScript = $signData->getWitnessScript();
301 66
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
302 6
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
303
            }
304 60
            $solution = $this->witnessScript = $this->classifier->decode($witnessScript);
305 60
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
306 6
                throw new \RuntimeException('Unsupported witness-script-hash script');
307
            }
308 27
        }
309
310 204
        $this->sigVersion = $sigVersion;
311 204
        $this->signScript = $solution;
312
313 204
        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 186
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
326
    {
327 186
        $type = $outputData->getType();
328 186
        $size = count($stack);
329 186
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
330 108
            $this->requiredSigs = 1;
331 108
            if ($size === 2) {
332 102
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
333 6
                    throw new \RuntimeException('Existing signatures are invalid!');
334
                }
335 96
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
336 96
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
337 48
            }
338 51
        }
339
340 180
        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 15
            }
350 15
        }
351
352 174
        if ($type === OutputClassifier::MULTISIG) {
353 36
            $info = new Multisig($outputData->getScript());
354 36
            $this->requiredSigs = $info->getRequiredSigCount();
355 36
            $this->publicKeys = $info->getKeys();
356 36
            if ($size > 1) {
357 36
                $vars = [];
358 36
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
359 36
                    $vars[] = TransactionSignatureFactory::fromHex($stack[$i], $this->ecAdapter);
360 18
                }
361
362 36
                $this->signatures = array_fill(0, count($this->publicKeys), null);
363 36
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
364 36
                $count = 0;
365 36
                foreach ($this->publicKeys as $idx => $key) {
366 36
                    if (isset($sigs[$key])) {
367 30
                        $this->signatures[$idx] = $sigs[$key];
368 33
                        $count++;
369 15
                    }
370 18
                }
371
372 36
                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 15
            }
377 15
        }
378
379 168
        return $type;
380
    }
381
382
    /**
383
     * High level function for extracting signatures from a pre-signed
384
     * transaction.
385
     *
386
     * @return $this
387
     */
388 204
    public function extractSignatures()
389
    {
390
391 204
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
392 204
        $witnesses = $this->tx->getWitnesses();
393 204
        $witness = isset($witnesses[$this->nInput]) ? $witnesses[$this->nInput]->all() : [];
394
395 204
        $solution = $this->scriptPubKey;
396 204
        $sigVersion = SigHash::V0;
397 204
        $chunks = [];
398 204
        if ($solution->canSign()) {
399 102
            $chunks = $this->evalPushOnly($scriptSig);
400 51
        }
401
402 204
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
403 72
            $chunks = $this->evalPushOnly($scriptSig);
404 72
            if (count($chunks) > 0) {
405 66
                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 60
                $solution = $this->redeemScript;
410 60
                $chunks = array_slice($chunks, 0, -1);
411 30
            }
412 33
        }
413
414 198
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
415 24
            $solution = $this->witnessKeyHash;
416 24
            $sigVersion = SigHash::V1;
417 24
            $chunks = $witness;
418 195
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
419 54
            if (count($witness) > 0) {
420 54
                if (!end($witness)->equals($this->witnessScript->getScript()->getBuffer())) {
421 12
                    throw new \RuntimeException('Extracted witnessScript did not match witness-script-hash');
422
                }
423
424 42
                $solution = $this->witnessScript;
425 42
                $sigVersion = SigHash::V1;
426 42
                $chunks = array_slice($witness, 0, -1);
427 21
            }
428 21
        }
429
430 186
        $this->extractFromValues($solution, $chunks, $sigVersion);
431
432 168
        return $this;
433
    }
434
435
    /**
436
     * @param ScriptInterface $scriptCode
437
     * @param int $sigHashType
438
     * @param int $sigVersion
439
     * @return BufferInterface
440
     */
441 174
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
442
    {
443 174
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
444
            throw new \RuntimeException('Invalid sigHashType requested');
445
        }
446
447 174
        if ($sigVersion === SigHash::V1) {
448 66
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
449 33
        } else {
450 114
            $hasher = new Hasher($this->tx);
451
        }
452
453 174
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
454
    }
455
456
    /**
457
     * @param int $sigHashType
458
     * @return BufferInterface
459
     */
460 36
    public function getSigHash($sigHashType)
461
    {
462 36
        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 168
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
473
    {
474 168
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
475 168
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
476 168
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
477
    }
478
479
    /**
480
     * @return bool
481
     */
482 168
    public function isFullySigned()
483
    {
484 168
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
485
    }
486
487
    /**
488
     * @return int
489
     */
490 150
    public function getRequiredSigs()
491
    {
492 150
        return $this->requiredSigs;
493
    }
494
495
    /**
496
     * @return TransactionSignatureInterface[]
497
     */
498 150
    public function getSignatures()
499
    {
500 150
        return $this->signatures;
501
    }
502
503
    /**
504
     * @return PublicKeyInterface[]
505
     */
506 150
    public function getPublicKeys()
507
    {
508 150
        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 168
    private function doSignature(PrivateKeyInterface $key, OutputData $solution, $sigHashType, $sigVersion = SigHash::V0)
520
    {
521 168
        if ($this->isFullySigned()) {
522
            return;
523
        }
524
525 168
        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 153
        } else if ($solution->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
533 102
            if (!$key->getPubKeyHash()->equals($solution->getSolution())) {
534
                throw new \RuntimeException('Signing with the wrong private key');
535
            }
536 102
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
537 102
            $this->publicKeys[0] = $key->getPublicKey();
538 102
            $this->requiredSigs = 1;
539 87
        } else if ($solution->getType() === OutputClassifier::MULTISIG) {
540 36
            $info = new Multisig($solution->getScript());
541 36
            $this->publicKeys = $info->getKeys();
542 36
            $this->requiredSigs = $info->getRequiredSigCount();
543
544 36
            $myKey = $key->getPublicKey()->getBuffer();
545 36
            $signed = false;
546 36
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
547 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...
548 36
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
549 36
                    $signed = true;
550 18
                }
551 18
            }
552
553 36
            if (!$signed) {
554 18
                throw new \RuntimeException('Signing with the wrong private key');
555
            }
556 18
        } else {
557
            throw new \RuntimeException('Cannot sign unknown script type');
558
        }
559 168
    }
560
561
    /**
562
     * @param PrivateKeyInterface $key
563
     * @param int $sigHashType
564
     * @return bool
565
     */
566 168
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
567
    {
568 168
        $solution = $this->scriptPubKey;
569 168
        $sigVersion = SigHash::V0;
570 168
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
571 60
            $solution = $this->redeemScript;
572 30
        }
573
574 168
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
575 24
            $solution = $this->witnessKeyHash;
576 24
            $sigVersion = SigHash::V1;
577 159
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
578 42
            $solution = $this->witnessScript;
579 87
            $sigVersion = SigHash::V1;
580 21
        }
581
582 168
        if ($solution->canSign()) {
583 168
            $this->doSignature($key, $solution, $sigHashType, $sigVersion);
584 168
            return true;
585
        }
586
587
        return false;
588
    }
589
590
    /**
591
     * @param int $flags
592
     * @return bool
593
     */
594 150
    public function verify($flags = null)
595
    {
596 150
        $consensus = ScriptFactory::consensus();
597
598 150
        if ($flags === null) {
599 150
            $flags = $this->flags;
600 75
        }
601
602 150
        $flags |= Interpreter::VERIFY_P2SH;
603 150
        if ($this->sigVersion === 1) {
604 66
            $flags |= Interpreter::VERIFY_WITNESS;
605 33
        }
606
607 150
        $sig = $this->serializeSignatures();
608
609 150
        $mutator = TransactionFactory::mutate($this->tx);
610 150
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
611 150
        if ($this->sigVersion === 1) {
612 66
            $witness = [];
613 66
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
614 66
                if ($i === $this->nInput) {
615 66
                    $witness[] = $sig->getScriptWitness();
616 33
                } else {
617 6
                    $witness[] = new ScriptWitness([]);
618
                }
619 33
            }
620
621 66
            $mutator->witness($witness);
622 33
        }
623
624 150
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
625
    }
626
627
    /**
628
     * @param string $outputType
629
     * @return BufferInterface[]
630
     */
631 168
    private function serializeSolution($outputType)
632
    {
633 168
        $result = [];
634 168
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
635 36
            if (count($this->signatures) === 1) {
636 36
                $result = [$this->signatures[0]->getBuffer()];
637 18
            }
638 153
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
639 102
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
640 102
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
641 51
            }
642 87
        } else if ($outputType === OutputClassifier::MULTISIG) {
643 36
            $result[] = new Buffer();
644 36
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
645 36
                if (isset($this->signatures[$i])) {
646 36
                    $result[] = $this->signatures[$i]->getBuffer();
647 18
                }
648 18
            }
649 18
        } else {
650
            throw new \RuntimeException('Cannot serialize this script sig');
651
        }
652
653 168
        return $result;
654
    }
655
656
    /**
657
     * @return SigValues
658
     */
659 168
    public function serializeSignatures()
660
    {
661 168
        static $emptyScript = null;
662 168
        static $emptyWitness = null;
663 168
        if (is_null($emptyScript) || is_null($emptyWitness)) {
664 6
            $emptyScript = new Script();
665 6
            $emptyWitness = new ScriptWitness([]);
666 3
        }
667
668 168
        $scriptSigChunks = [];
669 168
        $witness = [];
670 168
        if ($this->scriptPubKey->canSign()) {
671 84
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
672 42
        }
673
674 168
        $solution = $this->scriptPubKey;
675 168
        $p2sh = false;
676 168
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
677 60
            $p2sh = true;
678 60
            if ($this->redeemScript->canSign()) {
679 24
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
680 12
            }
681 60
            $solution = $this->redeemScript;
682 30
        }
683
684 168
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
685 24
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
686 159
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
687 42
            if ($this->witnessScript->canSign()) {
688 42
                $witness = $this->serializeSolution($this->witnessScript->getType());
689 42
                $witness[] = $this->witnessScript->getScript()->getBuffer();
690 21
            }
691 21
        }
692
693 168
        if ($p2sh) {
694 60
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
695 30
        }
696
697 168
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
698
    }
699
}
700