Completed
Pull Request — master (#403)
by thomas
31:50
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 306
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData)
146
    {
147 306
        if (!isset($tx->getInputs()[$nInput])) {
148 6
            throw new \RuntimeException('No input at this index');
149
        }
150
151 300
        $this->ecAdapter = $ecAdapter;
152 300
        $this->tx = $tx;
153 300
        $this->nInput = $nInput;
154 300
        $this->txOut = $txOut;
155 300
        $this->classifier = new OutputClassifier();
156 300
        $this->interpreter = new Interpreter();
157 300
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue());
158 300
        $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 300
        $this->publicKeys = [];
160 300
        $this->signatures = [];
161 300
        $this->solve($signData);
162 270
        $this->extractSignatures();
163 234
    }
164
165
    /**
166
     * @param TransactionSignatureInterface[] $stack
167
     * @param PublicKeyInterface[] $publicKeys
168
     * @return \SplObjectStorage
169
     */
170 60
    private function sortMultiSigs($stack, array $publicKeys)
171
    {
172 60
        $sigSort = new SignatureSort($this->ecAdapter);
173 60
        $sigs = new \SplObjectStorage;
174
175 60
        foreach ($stack as $txSig) {
176 60
            $hash = $this->getSigHash($txSig->getHashType());
177 60
            $linked = $sigSort->link([$txSig->getSignature()], $publicKeys, $hash);
178 60
            foreach ($publicKeys as $key) {
179 60
                if ($linked->contains($key)) {
180 58
                    $sigs[$key] = $txSig;
181 18
                }
182 20
            }
183 20
        }
184
185 60
        return $sigs;
186
    }
187
188
    /**
189
     * @param ScriptInterface $script
190
     * @return \BitWasp\Buffertools\BufferInterface[]
191
     */
192 222
    private function evalPushOnly(ScriptInterface $script)
193
    {
194 222
        $stack = new Stack();
195 222
        $interpreter = new Interpreter();
196 222
        $interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue()));
197 222
        return $stack->all();
198
    }
199
200
    /**
201
     * @param BufferInterface[] $buffers
202
     * @return ScriptInterface
203
     */
204
    private function pushAll(array $buffers)
205
    {
206 234
        return ScriptFactory::sequence(array_map(function ($buffer) {
207 192
            if (!($buffer instanceof BufferInterface)) {
208
                throw new \RuntimeException('Script contained a non-push opcode');
209
            }
210
211 192
            $size = $buffer->getSize();
212 192
            if ($size === 0) {
213 30
                return Opcodes::OP_0;
214
            }
215
216 192
            $first = ord($buffer->getBinary());
217 192
            if ($size === 1 && $first >= 1 && $first <= 16) {
218
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
219
            } else {
220 192
                return $buffer;
221
            }
222 234
        }, $buffers));
223
    }
224
225
    /**
226
     * @param ScriptInterface $scriptSig
227
     * @param ScriptInterface $scriptPubKey
228
     * @param ScriptWitnessInterface|null $scriptWitness
229
     * @return bool
230
     */
231 108
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
232
    {
233 108
        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 162
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
243
    {
244 162
        $stack = new Stack($chunks);
245 162
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
246 6
            return false;
247
        }
248
249 156
        if ($stack->isEmpty()) {
250
            return false;
251 36
        }
252
253 156
        if (false === $this->interpreter->castToBool($stack[-1])) {
254 6
            return false;
255
        }
256
257 150
        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 300
    private function solve(SignData $signData)
275
    {
276 300
        $scriptPubKey = $this->txOut->getScript();
277 300
        $solveFlags = Interpreter::VERIFY_SIGPUSHONLY;
278 300
        $sigVersion = SigHash::V0;
279 300
        $solution = $this->scriptPubKey = $this->classifier->decode($scriptPubKey);
280 300
        if ($solution->getType() !== OutputClassifier::PAYTOSCRIPTHASH && !in_array($solution->getType(), self::$validP2sh)) {
281 6
            throw new \RuntimeException('scriptPubKey not supported');
282
        }
283
284 294
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
285 108
            $redeemScript = $signData->getRedeemScript();
286 108
            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 102
            $solution = $this->redeemScript = $this->classifier->decode($redeemScript);
290 102
            if (!in_array($solution->getType(), self::$validP2sh)) {
291 6
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
292
            }
293 32
        }
294
295 282
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
296 36
            $sigVersion = SigHash::V1;
297 36
            $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 262
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
299 84
            $sigVersion = SigHash::V1;
300 84
            $witnessScript = $signData->getWitnessScript();
301 84
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
302 6
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
303
            }
304 78
            $solution = $this->witnessScript = $this->classifier->decode($witnessScript);
305 78
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
306 6
                throw new \RuntimeException('Unsupported witness-script-hash script');
307
            }
308 24
        }
309
310 270
        $this->sigVersion = $sigVersion;
311 270
        $this->signScript = $solution;
312
313 270
        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 252
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
326
    {
327 252
        $type = $outputData->getType();
328 252
        $size = count($stack);
329 252
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
330 144
            $this->requiredSigs = 1;
331 144
            if ($size === 2) {
332 126
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
333 6
                    throw new \RuntimeException('Existing signatures are invalid!');
334
                }
335 120
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
336 120
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
337 40
            }
338 46
        }
339
340 246
        if ($type === OutputClassifier::PAYTOPUBKEY && count($stack) === 1) {
341 42
            $this->requiredSigs = 1;
342 42
            if ($size === 1) {
343 42
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
344 6
                    throw new \RuntimeException('Existing signatures are invalid!');
345
                }
346
347 36
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
348 36
                $this->publicKeys = [PublicKeyFactory::fromHex($outputData->getSolution())];
349 12
            }
350 12
        }
351
352 240
        if ($type === OutputClassifier::MULTISIG) {
353 60
            $info = new Multisig($outputData->getScript());
354 60
            $this->requiredSigs = $info->getRequiredSigCount();
355 60
            $this->publicKeys = $info->getKeys();
356 60
            if ($size > 1) {
357 60
                $vars = [];
358 60
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
359 60
                    $vars[] = TransactionSignatureFactory::fromHex($stack[$i], $this->ecAdapter);
360 20
                }
361
362 60
                $this->signatures = array_fill(0, count($this->publicKeys), null);
363 60
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
364 60
                $count = 0;
365 60
                foreach ($this->publicKeys as $idx => $key) {
366 60
                    if (isset($sigs[$key])) {
367 54
                        $this->signatures[$idx] = $sigs[$key];
368 58
                        $count++;
369 18
                    }
370 20
                }
371
372 60
                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 18
            }
377 18
        }
378
379 234
        return $type;
380
    }
381
382
    /**
383
     * High level function for extracting signatures from a pre-signed
384
     * transaction.
385
     *
386
     * @return $this
387
     */
388 270
    public function extractSignatures()
389
    {
390
391 270
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
392 270
        $witnesses = $this->tx->getWitnesses();
393 270
        $witness = isset($witnesses[$this->nInput]) ? $witnesses[$this->nInput]->all() : [];
394
395 270
        $solution = $this->scriptPubKey;
396 270
        $sigVersion = SigHash::V0;
397 270
        $chunks = [];
398 270
        if ($solution->canSign()) {
399 132
            $chunks = $this->evalPushOnly($scriptSig);
400 44
        }
401
402 270
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
403 90
            $chunks = $this->evalPushOnly($scriptSig);
404 90
            if (count($chunks) > 0) {
405 84
                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 78
                $solution = $this->redeemScript;
410 78
                $chunks = array_slice($chunks, 0, -1);
411 26
            }
412 28
        }
413
414 264
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
415 36
            $solution = $this->witnessKeyHash;
416 36
            $sigVersion = SigHash::V1;
417 36
            $chunks = $witness;
418 256
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
419 72
            if (count($witness) > 0) {
420 72
                if (!end($witness)->equals($this->witnessScript->getScript()->getBuffer())) {
421 12
                    throw new \RuntimeException('Extracted witnessScript did not match witness-script-hash');
422
                }
423
424 60
                $solution = $this->witnessScript;
425 60
                $sigVersion = SigHash::V1;
426 60
                $chunks = array_slice($witness, 0, -1);
427 20
            }
428 20
        }
429
430 252
        $this->extractFromValues($solution, $chunks, $sigVersion);
431
432 234
        return $this;
433
    }
434
435
    /**
436
     * @param ScriptInterface $scriptCode
437
     * @param int $sigHashType
438
     * @param int $sigVersion
439
     * @return BufferInterface
440
     */
441 240
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
442
    {
443 240
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
444
            throw new \RuntimeException('Invalid sigHashType requested');
445
        }
446
447 240
        if ($sigVersion === SigHash::V1) {
448 96
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
449 32
        } else {
450 150
            $hasher = new Hasher($this->tx);
451
        }
452
453 240
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
454
    }
455
456
    /**
457
     * @param int $sigHashType
458
     * @return BufferInterface
459
     */
460 60
    public function getSigHash($sigHashType)
461
    {
462 60
        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 234
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
473
    {
474 234
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
475 234
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
476 234
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
477
    }
478
479
    /**
480
     * @return bool
481
     */
482 234
    public function isFullySigned()
483
    {
484 234
        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 234
    private function doSignature(PrivateKeyInterface $key, OutputData $solution, $sigHashType, $sigVersion = SigHash::V0)
520
    {
521 234
        if ($this->isFullySigned()) {
522
            return;
523
        }
524
525 234
        if ($solution->getType() === OutputClassifier::PAYTOPUBKEY) {
526 42
            if (!$key->getPublicKey()->getBuffer()->equals($solution->getSolution())) {
527
                throw new \RuntimeException('Signing with the wrong private key');
528
            }
529 42
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
530 42
            $this->publicKeys[0] = $key->getPublicKey();
531 42
            $this->requiredSigs = 1;
532 210
        } else if ($solution->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
533 138
            if (!$key->getPubKeyHash()->equals($solution->getSolution())) {
534
                throw new \RuntimeException('Signing with the wrong private key');
535
            }
536 138
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
537 138
            $this->publicKeys[0] = $key->getPublicKey();
538 138
            $this->requiredSigs = 1;
539 106
        } else if ($solution->getType() === OutputClassifier::MULTISIG) {
540 60
            $info = new Multisig($solution->getScript());
541 60
            $this->publicKeys = $info->getKeys();
542 60
            $this->requiredSigs = $info->getRequiredSigCount();
543
544 60
            $myKey = $key->getPublicKey()->getBuffer();
545 60
            $signed = false;
546 60
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
547 60
                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 60
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
549 60
                    $signed = true;
550 20
                }
551 20
            }
552
553 60
            if (!$signed) {
554 40
                throw new \RuntimeException('Signing with the wrong private key');
555
            }
556 20
        } else {
557
            throw new \RuntimeException('Cannot sign unknown script type');
558
        }
559 234
    }
560
561
    /**
562
     * @param PrivateKeyInterface $key
563
     * @param int $sigHashType
564
     * @return bool
565
     */
566 234
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
567
    {
568 234
        $solution = $this->scriptPubKey;
569 234
        $sigVersion = SigHash::V0;
570 234
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
571 78
            $solution = $this->redeemScript;
572 26
        }
573
574 234
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
575 36
            $solution = $this->witnessKeyHash;
576 36
            $sigVersion = SigHash::V1;
577 214
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
578 60
            $solution = $this->witnessScript;
579 96
            $sigVersion = SigHash::V1;
580 20
        }
581
582 234
        if ($solution->canSign()) {
583 234
            $this->doSignature($key, $solution, $sigHashType, $sigVersion);
584 234
            return true;
585
        }
586
587
        return false;
588
    }
589
590
    /**
591
     * @param int $flags
592
     * @return bool
593
     */
594 204
    public function verify($flags = null)
595
    {
596 204
        $consensus = ScriptFactory::consensus();
597
598 204
        if ($flags === null) {
599 150
            $flags = $this->flags;
600 50
        }
601
602 204
        $flags |= Interpreter::VERIFY_P2SH;
603 204
        if ($this->sigVersion === 1) {
604 96
            $flags |= Interpreter::VERIFY_WITNESS;
605 32
        }
606
607 204
        $sig = $this->serializeSignatures();
608
609 204
        $mutator = TransactionFactory::mutate($this->tx);
610 204
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
611 204
        if ($this->sigVersion === 1) {
612 96
            $witness = [];
613 96
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
614 96
                if ($i === $this->nInput) {
615 96
                    $witness[] = $sig->getScriptWitness();
616 32
                } else {
617 6
                    $witness[] = new ScriptWitness([]);
618
                }
619 32
            }
620
621 96
            $mutator->witness($witness);
622 32
        }
623
624 204
        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 234
    private function serializeSolution($outputType)
632
    {
633 234
        $result = [];
634 234
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
635 42
            if (count($this->signatures) === 1) {
636 42
                $result = [$this->signatures[0]->getBuffer()];
637 14
            }
638 210
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
639 138
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
640 138
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
641 46
            }
642 106
        } else if ($outputType === OutputClassifier::MULTISIG) {
643 60
            $result[] = new Buffer();
644 60
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
645 60
                if (isset($this->signatures[$i])) {
646 60
                    $result[] = $this->signatures[$i]->getBuffer();
647 20
                }
648 20
            }
649 20
        } else {
650
            throw new \RuntimeException('Cannot serialize this script sig');
651
        }
652
653 234
        return $result;
654
    }
655
656
    /**
657
     * @return SigValues
658
     */
659 234
    public function serializeSignatures()
660
    {
661 234
        static $emptyScript = null;
662 234
        static $emptyWitness = null;
663 234
        if (is_null($emptyScript) || is_null($emptyWitness)) {
664 6
            $emptyScript = new Script();
665 6
            $emptyWitness = new ScriptWitness([]);
666 2
        }
667
668 234
        $scriptSigChunks = [];
669 234
        $witness = [];
670 234
        if ($this->scriptPubKey->canSign()) {
671 114
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
672 38
        }
673
674 234
        $solution = $this->scriptPubKey;
675 234
        $p2sh = false;
676 234
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
677 78
            $p2sh = true;
678 78
            if ($this->redeemScript->canSign()) {
679 30
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
680 10
            }
681 78
            $solution = $this->redeemScript;
682 26
        }
683
684 234
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
685 36
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
686 214
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
687 60
            if ($this->witnessScript->canSign()) {
688 60
                $witness = $this->serializeSolution($this->witnessScript->getType());
689 60
                $witness[] = $this->witnessScript->getScript()->getBuffer();
690 20
            }
691 20
        }
692
693 234
        if ($p2sh) {
694 78
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
695 26
        }
696
697 234
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
698
    }
699
}
700