Completed
Pull Request — master (#403)
by thomas
73:13 queued 70:38
created

InputSigner::getSigHash()   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 1
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\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 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 156
    private function evalPushOnly(ScriptInterface $script)
193
    {
194 156
        $stack = new Stack();
195 156
        $interpreter = new Interpreter();
196 156
        $interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue()));
197 156
        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 126
            if (!($buffer instanceof BufferInterface)) {
208
                throw new \RuntimeException('Script contained a non-push opcode');
209
            }
210
211 126
            $size = $buffer->getSize();
212 126
            if ($size === 0) {
213 30
                return Opcodes::OP_0;
214
            }
215
216 126
            $first = ord($buffer->getBinary());
217 126
            if ($size === 1 && $first >= 1 && $first <= 16) {
218
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
219
            } else {
220 126
                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 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 102
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
243
    {
244 102
        $stack = new Stack($chunks);
245 102
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
246 6
            return false;
247
        }
248
249 96
        if ($stack->isEmpty()) {
250
            return false;
251 32
        }
252
253 96
        if (false === $this->interpreter->castToBool($stack[-1])) {
254 6
            return false;
255
        }
256
257 90
        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 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 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 200
        } 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 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 84
            $this->requiredSigs = 1;
331 84
            if ($size === 2) {
332 66
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
333 6
                    throw new \RuntimeException('Existing signatures are invalid!');
334
                }
335 60
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
336 60
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
337 20
            }
338 26
        }
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 10
            }
350 10
        }
351
352 174
        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 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 78
            $chunks = $this->evalPushOnly($scriptSig);
400 26
        }
401
402 204
        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 198
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
415 24
            $solution = $this->witnessKeyHash;
416 24
            $sigVersion = SigHash::V1;
417 24
            $chunks = $witness;
418 190
        } 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 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 78
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
449 26
        } else {
450 96
            $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 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 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 84
    public function getRequiredSigs()
491
    {
492 84
        return $this->requiredSigs;
493
    }
494
495
    /**
496
     * @return TransactionSignatureInterface[]
497
     */
498 84
    public function getSignatures()
499
    {
500 84
        return $this->signatures;
501
    }
502
503
    /**
504
     * @return PublicKeyInterface[]
505
     */
506 84
    public function getPublicKeys()
507
    {
508 84
        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 144
        } else if ($solution->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
533 78
            if (!$key->getPubKeyHash()->equals($solution->getSolution())) {
534
                throw new \RuntimeException('Signing with the wrong private key');
535
            }
536 78
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
537 78
            $this->publicKeys[0] = $key->getPublicKey();
538 78
            $this->requiredSigs = 1;
539 80
        } 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 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 66
            $solution = $this->redeemScript;
572 22
        }
573
574 168
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
575 24
            $solution = $this->witnessKeyHash;
576 24
            $sigVersion = SigHash::V1;
577 152
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
578 54
            $solution = $this->witnessScript;
579 70
            $sigVersion = SigHash::V1;
580 18
        }
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
     * @return bool
592
     */
593 84
    public function verify()
594
    {
595 84
        $consensus = ScriptFactory::consensus();
596
597 84
        $flags = $this->flags;
598 84
        $flags |= Interpreter::VERIFY_P2SH;
599 84
        if ($this->sigVersion === 1) {
600 48
            $flags |= Interpreter::VERIFY_WITNESS;
601 16
        }
602
603 84
        $sig = $this->serializeSignatures();
604
605 84
        $mutator = TransactionFactory::mutate($this->tx);
606 84
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
607 84
        if ($this->sigVersion === 1) {
608 48
            $witness = array_fill(0, count($this->tx->getInputs()), null);
609 48
            $witness[$this->nInput] = $sig->getScriptWitness();
610 48
            $mutator->witness($witness);
611 16
        }
612
613 84
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
614
    }
615
616
    /**
617
     * @param string $outputType
618
     * @return BufferInterface[]
619
     */
620 168
    private function serializeSolution($outputType)
621
    {
622 168
        $result = [];
623 168
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
624 36
            if (count($this->signatures) === 1) {
625 36
                $result = [$this->signatures[0]->getBuffer()];
626 12
            }
627 144
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
628 78
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
629 78
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
630 26
            }
631 80
        } else if ($outputType === OutputClassifier::MULTISIG) {
632 54
            $result[] = new Buffer();
633 54
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
634 54
                if (isset($this->signatures[$i])) {
635 54
                    $result[] = $this->signatures[$i]->getBuffer();
636 18
                }
637 18
            }
638 18
        } else {
639
            throw new \RuntimeException('Cannot serialize this script sig');
640
        }
641
642 168
        return $result;
643
    }
644
645
    /**
646
     * @return SigValues
647
     */
648 168
    public function serializeSignatures()
649
    {
650 168
        static $emptyScript = null;
651 168
        static $emptyWitness = null;
652 168
        if (is_null($emptyScript) || is_null($emptyWitness)) {
653 6
            $emptyScript = new Script();
654 6
            $emptyWitness = new ScriptWitness([]);
655 2
        }
656
657 168
        $scriptSigChunks = [];
658 168
        $witness = [];
659 168
        if ($this->scriptPubKey->canSign()) {
660 60
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
661 20
        }
662
663 168
        $solution = $this->scriptPubKey;
664 168
        $p2sh = false;
665 168
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
666 66
            $p2sh = true;
667 66
            if ($this->redeemScript->canSign()) {
668 30
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
669 10
            }
670 66
            $solution = $this->redeemScript;
671 22
        }
672
673 168
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
674 24
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
675 152
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
676 54
            if ($this->witnessScript->canSign()) {
677 54
                $witness = $this->serializeSolution($this->witnessScript->getType());
678 54
                $witness[] = $this->witnessScript->getScript()->getBuffer();
679 18
            }
680 18
        }
681
682 168
        if ($p2sh) {
683 66
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
684 22
        }
685
686 168
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
687
    }
688
}
689